diff --git a/.gitignore b/.gitignore index c95b6dbc19..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 -################### -## Windows detritus -################### +# Microsoft Fakes +FakesAssemblies/ + +# 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,12 +291,11 @@ _[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/Licenses.lic @@ -207,6 +303,7 @@ Kopie von* # 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 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 9652a45276..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.2.1 has been released on May 15, 2015. The highlights are: - -* Overall performance increase -* Added multistore support for forums -* New option to display product thumbnails in instant search -* New mobile theme: _MobileLight_ (a light variant of the default mobile theme) -* Quantity unit management -* Limit country settings to stores -* Web API: Support for file upload and multipart mime -* More reliable mobile device detection -* Performance: product list rendering up to 10x (!) faster now -* Lots of bug fixes +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 980f2060e3..f184eaa7eb 100644 --- a/SmartStoreNET.Tasks.Targets +++ b/SmartStoreNET.Tasks.Targets @@ -2,7 +2,7 @@ - + @@ -44,7 +44,7 @@ x86 $(BUILD_NUMBER) - 2.2.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 b456e5ffb2..0dac88a9fd 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,248 @@ -# Release Notes +# Release Notes + +## 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 diff --git a/lib/SmartStore.Licensing/SmartStore.Licensing.dll b/lib/SmartStore.Licensing/SmartStore.Licensing.dll index 4c4579257f..8acdf39c24 100644 Binary files a/lib/SmartStore.Licensing/SmartStore.Licensing.dll and b/lib/SmartStore.Licensing/SmartStore.Licensing.dll differ diff --git a/src/AssemblySharedInfo.cs b/src/AssemblySharedInfo.cs index f4e2566c4e..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 2015")] +[assembly: AssemblyCopyright("Copyright © SmartStore AG 2016")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/src/AssemblyVersionInfo.cs b/src/AssemblyVersionInfo.cs index 8cf3480af0..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.2.0.0")] +[assembly: AssemblyVersion("2.6.0.0")] -[assembly: AssemblyFileVersion("2.2.0.0")] -[assembly: AssemblyInformationalVersion("2.2.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 0186e041c1..b02ea8dd58 100644 --- a/src/Libraries/SmartStore.Core/Async/AsyncRunner.cs +++ b/src/Libraries/SmartStore.Core/Async/AsyncRunner.cs @@ -5,7 +5,6 @@ using System.Web.Hosting; using Autofac; using SmartStore.Core.Infrastructure; -using SmartStore.Utilities; namespace SmartStore.Core.Async { @@ -14,11 +13,18 @@ public static class AsyncRunner { private static readonly BackgroundWorkHost _host = new BackgroundWorkHost(); + /// + /// Gets the global cancellation token which signals the application shutdown + /// + public static CancellationToken AppShutdownCancellationToken + { + get { return _host.ShutdownCancellationTokenSource.Token; } + } /// - /// Execute's an async Task method which has a void return value synchronously + /// Executes an async Task method which has a void return value synchronously /// - /// Task method to execute + /// Task method to execute public static void RunSync(Func func) { var oldContext = SynchronizationContext.Current; @@ -46,10 +52,10 @@ public static void RunSync(Func func) } /// - /// Execute's an async Task method which has a T return type synchronously + /// Executes an async Task method which has a TResult return type synchronously /// - /// Return Type - /// Task method to execute + /// Return Type + /// Task method to execute /// public static TResult RunSync(Func> func) { @@ -113,7 +119,7 @@ public static Task Run(Action action, Cancell var t = Task.Factory.StartNew(() => { var accessor = EngineContext.Current.ContainerManager.ScopeAccessor; - using (var scope = accessor.BeginContextAwareScope()) + using (accessor.BeginContextAwareScope()) { action(accessor.GetLifetimeScope(null), ct); } @@ -136,7 +142,7 @@ public static Task Run(Action action, var t = Task.Factory.StartNew((o) => { var accessor = EngineContext.Current.ContainerManager.ScopeAccessor; - using (var scope = accessor.BeginContextAwareScope()) + using (accessor.BeginContextAwareScope()) { action(accessor.GetLifetimeScope(null), ct, o); } @@ -185,7 +191,7 @@ public static Task Run(Func { var accessor = EngineContext.Current.ContainerManager.ScopeAccessor; - using (var scope = accessor.BeginContextAwareScope()) + using (accessor.BeginContextAwareScope()) { return function(accessor.GetLifetimeScope(null), ct); } @@ -208,7 +214,7 @@ public static Task Run(Func { var accessor = EngineContext.Current.ContainerManager.ScopeAccessor; - using (var scope = accessor.BeginContextAwareScope()) + using (accessor.BeginContextAwareScope()) { return function(accessor.GetLifetimeScope(null), ct, o); } @@ -222,10 +228,11 @@ public static Task Run(Func> _items = new Queue>(); + public Exception InnerException { get; set; } - readonly AutoResetEvent workItemsWaiting = new AutoResetEvent(false); - readonly Queue> items = new Queue>(); public override void Send(SendOrPostCallback d, object state) { @@ -234,28 +241,28 @@ public override void Send(SendOrPostCallback d, object state) public override void Post(SendOrPostCallback d, object state) { - lock (items) + lock (_items) { - items.Enqueue(Tuple.Create(d, state)); + _items.Enqueue(Tuple.Create(d, state)); } - workItemsWaiting.Set(); + _workItemsWaiting.Set(); } public void EndMessageLoop() { - Post(_ => done = true, null); + Post(_ => _done = true, null); } public void BeginMessageLoop() { - while (!done) + while (!_done) { Tuple task = null; - lock (items) + lock (_items) { - if (items.Count > 0) + if (_items.Count > 0) { - task = items.Dequeue(); + task = _items.Dequeue(); } } if (task != null) @@ -268,7 +275,7 @@ public void BeginMessageLoop() } else { - workItemsWaiting.WaitOne(); + _workItemsWaiting.WaitOne(); } } } @@ -283,14 +290,19 @@ public override SynchronizationContext CreateCopy() internal class BackgroundWorkHost : IRegisteredObject { - private CancellationTokenSource _shutdownCancellationTokenSource = new CancellationTokenSource(); - private int _numRunningWorkItems = 0; + 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; 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 e50f69ed08..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) @@ -62,7 +50,7 @@ public void Set(string key, object value, int? cacheTime) 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) @@ -83,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/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 1029a802bf..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 { @@ -26,7 +26,7 @@ public MostRecentlyUsedList(IEnumerable collection, int maxSize) public MostRecentlyUsedList(string collection, int maxSize) { _maxSize = maxSize; - _mru = (collection.SplitSafe(Delimiter) as IEnumerable).ToList(); + _mru = collection.SplitSafe(Delimiter).Cast().ToList(); Normalize(); } @@ -42,7 +42,7 @@ public MostRecentlyUsedList(IEnumerable collection, T newItem, int maxSize) public MostRecentlyUsedList(string collection, T newItem, int maxSize) { _maxSize = maxSize; - _mru = (collection.SplitSafe(Delimiter) 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 index 6349fec29c..2289488b95 100644 --- a/src/Libraries/SmartStore.Core/Data/IQueryableExtensions.cs +++ b/src/Libraries/SmartStore.Core/Data/IQueryableExtensions.cs @@ -3,8 +3,9 @@ using System.Linq; using System.Data.Entity; using System.Linq.Expressions; +using SmartStore.Core; -namespace SmartStore.Core.Data +namespace SmartStore { public static class IQueryableExtensions { diff --git a/src/Libraries/SmartStore.Core/Data/IRepository.cs b/src/Libraries/SmartStore.Core/Data/IRepository.cs index 905705c289..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. /// @@ -63,7 +70,7 @@ public partial interface IRepository where T : BaseEntity /// /// Marks the changes of existing entities to be saved to the store. /// - /// A list of entity instances that should be updated in the database. + /// A list of entity instances that should be updated in the database. /// Implementors should delegate this to the current void UpdateRange(IEnumerable entities); @@ -119,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 2e6e8ac414..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, 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 dd7d67edea..12f9fa8587 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/CatalogSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/CatalogSettings.cs @@ -16,6 +16,7 @@ public CatalogSettings() { FileUploadAllowedExtensions = new List(); AllowProductSorting = true; + DefaultSortOrder = ProductSortingEnum.Position; AllowProductViewModeChanging = true; DefaultViewMode = "grid"; CategoryBreadcrumbEnabled = true; @@ -32,12 +33,16 @@ public CatalogSettings() CompareProductsEnabled = true; FilterEnabled = true; MaxFilterItemsToDisplay = 4; + 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; @@ -123,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; } @@ -167,7 +177,12 @@ 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 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 /// @@ -284,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 int SearchPageProductsPerPage { get; set; } + public bool ShowManufacturersOnHomepage { get; set; } + + /// + /// Gets or sets a value indicating whether to show manufacturer pictures or names on home page + /// + public bool ShowManufacturerPictures { get; set; } + + /// + /// Gets or sets a value indicating whether to hide manufacturer pictures in product detail + /// + 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 @@ -348,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 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 61f332eb11..999f66bad3 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/Product.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/Product.cs @@ -18,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; @@ -104,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 /// @@ -185,6 +191,7 @@ public string Sku /// Gets or sets the manufacturer part number /// [DataMember] + [Index] public string ManufacturerPartNumber { [DebuggerStepThrough] @@ -202,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] @@ -246,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; } @@ -387,7 +395,7 @@ public int StockQuantity [DebuggerStepThrough] get { - return this.GetMergedDataValue("StockQuantity", _stockQuantity); + return this.GetMergedDataValue("StockQuantity", _stockQuantity); } set { @@ -559,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] @@ -1040,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 9ecb41ae4f..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 { @@ -14,6 +13,7 @@ public CommonSettings() SitemapIncludeTopics = true; FullTextMode = FulltextSearchMode.ExactMatch; AutoUpdateEnabled = true; + EntityPickerPageSize = 48; } public bool UseSystemEmailForContactUsForm { get; set; } @@ -29,21 +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/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 ea0bb24d01..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,7 +32,8 @@ 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"; } } 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/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/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 933790f578..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,12 +8,18 @@ namespace SmartStore.Core.Domain.Media /// Represents a download /// [DataContract] - public partial class Download : BaseEntity + public partial class Download : BaseEntity, ITransient { - /// + public Download() + { + this.UpdatedOnUtc = DateTime.UtcNow; + } + + /// /// Gets or sets a GUID /// [DataMember] + [Index] public Guid DownloadGuid { 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 282fc0407d..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 @@ -47,5 +48,20 @@ public partial class MessageTemplate : BaseEntity, ILocalizedEntity, IStoreMappi /// 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 573231b382..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 /// @@ -91,5 +94,14 @@ public partial class QueuedEmail : BaseEntity /// 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 a317879baa..63cbf58ea8 100644 --- a/src/Libraries/SmartStore.Core/Domain/Orders/CheckoutAttribute.cs +++ b/src/Libraries/SmartStore.Core/Domain/Orders/CheckoutAttribute.cs @@ -1,14 +1,15 @@ 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() @@ -60,11 +61,16 @@ public CheckoutAttribute() /// Gets or sets whether the checkout attribute is active /// public bool IsActive { get; set; } - - /// - /// Gets the attribute control type - /// - public AttributeControlType AttributeControlType + + /// + /// 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 { @@ -75,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 a8fae7b574..9db5f14733 100644 --- a/src/Libraries/SmartStore.Core/Domain/Orders/Order.cs +++ b/src/Libraries/SmartStore.Core/Domain/Orders/Order.cs @@ -320,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 /// @@ -452,19 +451,25 @@ protected virtual SortedDictionary ParseTaxRates(string taxRat public int? RewardPointsRemaining { get; set; } /// - /// Gets or sets a value indicating whether a new payment notification (IPN) arrived + /// Gets or sets a value indicating whether a new payment notification arrived (IPN, webhook, callback etc.) /// [DataMember] public bool HasNewPaymentNotification { get; set; } - #endregion + /// + /// Gets or sets a value indicating whether the customer accepted to hand over email address to third party + /// + [DataMember] + public bool AcceptThirdPartyEmailHandOver { get; set; } - #region Navigation properties + #endregion - /// - /// Gets or sets the customer - /// - [DataMember] + #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/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 f96d5d35db..e403d7cdb1 100644 --- a/src/Libraries/SmartStore.Core/Domain/Orders/ShoppingCartSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Orders/ShoppingCartSettings.cs @@ -1,9 +1,10 @@ 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; @@ -17,6 +18,7 @@ public ShoppingCartSettings() ShowDiscountBox = true; ShowGiftCardBox = true; ShowCommentBox = true; + ShowEsdRevocationWaiverBox = true; CrossSellsNumber = 8; EmailWishlistEnabled = true; MiniShoppingCartEnabled = true; @@ -97,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 @@ -132,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/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 fb13ce342b..348b5fac89 100644 --- a/src/Libraries/SmartStore.Core/Events/CommonMessages/AppInitScheduledTasksEvent.cs +++ b/src/Libraries/SmartStore.Core/Events/CommonMessages/AppInitScheduledTasksEvent.cs @@ -9,7 +9,6 @@ namespace SmartStore.Core.Events /// /// to initialize scheduled tasks in Application_Start /// - /// codehint: sm-add public class AppInitScheduledTasksEvent { 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/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 20ff82f9a2..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) @@ -452,25 +203,6 @@ public static bool ToBool(this string value, bool defaultValue = false) return null; } - 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; - } - - public static byte[] ToByteArray(this string value) - { - return Encoding.Default.GetBytes(value); - } - [DebuggerStepThrough] public static Version ToVersion(this string value, Version defaultVersion = null) { @@ -486,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 @@ -569,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"); @@ -623,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 da7f1483e3..033c5d4230 100644 --- a/src/Libraries/SmartStore.Core/Extensions/MiscExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/MiscExtensions.cs @@ -11,25 +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) { - if (!sb.ToString().EmptyNull().Contains(exc.Message)) - sb.Grow(exc.Message, " "); - exc = exc.InnerException; + if (!sb.ToString().EmptyNull().Contains(exception.Message)) + { + sb.Grow(exception.Message, " * "); + } + exception = exception.InnerException; } return sb.ToString(); } @@ -44,52 +46,6 @@ 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()); @@ -115,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 fd92e44f83..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)) { @@ -551,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)) { @@ -568,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]; } /// @@ -626,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))) @@ -672,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 index a770ab6007..d44109c7f2 100644 --- a/src/Libraries/SmartStore.Core/Extensions/XPathExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/XPathExtensions.cs @@ -27,7 +27,7 @@ public static class XPathExtensions if (xpath.IsEmpty()) { if (node.Value.HasValue()) - return (T)TypeDescriptor.GetConverter(typeof(T)).ConvertFromString(null, culture == null ? CultureInfo.InvariantCulture : culture, node.Value); + return node.Value.Convert(culture); return defaultValue; } @@ -38,7 +38,7 @@ public static class XPathExtensions var n = node.SelectSingleNode(xpath); if (n != null && n.Value.HasValue()) - return (T)TypeDescriptor.GetConverter(typeof(T)).ConvertFromString(null, culture == null ? CultureInfo.InvariantCulture : culture, n.Value); + return n.Value.Convert(culture); } } catch (Exception exc) diff --git a/src/Libraries/SmartStore.Core/Extensions/XmlNodeExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/XmlNodeExtensions.cs index b167b43c85..601c4cef6d 100644 --- a/src/Libraries/SmartStore.Core/Extensions/XmlNodeExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/XmlNodeExtensions.cs @@ -17,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(); } } } @@ -43,13 +43,13 @@ public static string GetAttributeText(this XmlNode node, string attributeName) if (node != null) { if (xpath.IsEmpty()) - return (T)TypeDescriptor.GetConverter(typeof(T)).ConvertFromString(node.InnerText); + return node.InnerText.Convert(); var n = node.SelectSingleNode(xpath); if (n != null && n.InnerText.HasValue()) - return (T)TypeDescriptor.GetConverter(typeof(T)).ConvertFromString(null, culture == null ? CultureInfo.InvariantCulture : culture, n.InnerText); - } + return n.InnerText.Convert(culture); + } } catch (Exception exc) { diff --git a/src/Libraries/SmartStore.Core/Extensions/XmlWriterExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/XmlWriterExtensions.cs index 5ead943146..ca4e1c2c71 100644 --- a/src/Libraries/SmartStore.Core/Extensions/XmlWriterExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/XmlWriterExtensions.cs @@ -21,16 +21,6 @@ public static void WriteCData(this XmlWriter writer, string name, string value, } } - public static void WriteNode(this XmlWriter writer, string name, Action content) - { - if (name.HasValue() && content != null) - { - writer.WriteStartElement(name); - content(); - writer.WriteEndElement(); - } - } - /// /// Created a simple or CData node element /// 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 d1fda9b4e8..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 @@ -118,16 +128,5 @@ public partial interface IWebHelper /// Redirect URL; empty string if you want to redirect to the current page URL /// Usually true after a new plugin was installed (nukes the MVC cache) void RestartAppDomain(bool makeRedirect = false, string redirectUrl = "", bool aggressive = false); - - /// - /// 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; } - } } 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 56410ec9a3..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|^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 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/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/ContainerManager.cs b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs index 7bac9acb62..f39bbeedeb 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs @@ -1,8 +1,11 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Reflection; using Autofac; using Autofac.Builder; +using SmartStore.ComponentModel; using SmartStore.Core.Caching; namespace SmartStore.Core.Infrastructure.DependencyManagement @@ -10,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) { @@ -21,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(); } @@ -45,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(); } @@ -61,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); diff --git a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/DefaultLifetimeScopeAccessor.cs b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/DefaultLifetimeScopeAccessor.cs index 79626e5775..03e0fbbba7 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/DefaultLifetimeScopeAccessor.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/DefaultLifetimeScopeAccessor.cs @@ -71,17 +71,17 @@ public ILifetimeScope GetLifetimeScope(Action configurationAct return scope; } - private void OnScopeBeginning(object sender, LifetimeScopeBeginningEventArgs args) - { - bool isWeb = System.Web.HttpContext.Current != null; - Debug.WriteLine("Scope Begin, Web: " + isWeb); - } + //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 void OnScopeEnding(object sender, LifetimeScopeEndingEventArgs args) + //{ + // bool isWeb = System.Web.HttpContext.Current != null; + // Debug.WriteLine("Scope END, Web: " + isWeb); + //} private ILifetimeScope BeginLifetimeScope(Action configurationAction) { 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/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 ec0292c390..42b53f80c4 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/ObjectSignatureAttribute.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/ObjectSignatureAttribute.cs @@ -1,11 +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 782fd45b52..a9143bed56 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/RegularExpressions.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/RegularExpressions.cs @@ -21,7 +21,7 @@ public static class RegularExpressions //public static readonly Regex IsWebUrl = new Regex(@"(http|https)://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?", RegexOptions.Singleline | RegexOptions.Compiled); /// see https://msdn.microsoft.com/en-us/library/ms998267.aspx - public static readonly Regex IsWebUrl = new Regex(@"^(ht|f)tp(s?)\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(\/?)([a-zA-Z0-9\-\.\?\,\'\/\\\+&%\$#_]*)?$", RegexOptions.Singleline | RegexOptions.Compiled); + public static readonly Regex IsWebUrl = new Regex(@"^(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); @@ -30,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/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 9a65536cc2..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 @@ -69,7 +69,6 @@ public override IList GetAssemblies(bool ignoreInactivePlugins = false { _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 922ba78a78..fb8a10be35 100644 --- a/src/Libraries/SmartStore.Core/Logging/LoggingExtensions.cs +++ b/src/Libraries/SmartStore.Core/Logging/LoggingExtensions.cs @@ -9,43 +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); } public static void Error(this ILogger logger, Exception exception, Customer customer = null) { - FilteredLog(logger, LogLevel.Error, exception.Message, exception, customer); + FilteredLog(logger, LogLevel.Error, exception.ToAllMessages(), null, exception, customer); } - private static void FilteredLog(ILogger logger, LogLevel level, string message, Exception exception = null, Customer customer = null) + 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 2a77aef5a3..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; @@ -26,6 +27,7 @@ public sealed class AppUpdater : DisposableObject #region Package update + [SuppressMessage("ReSharper", "RedundantAssignment")] public bool InstallablePackageExists() { string packagePath = null; @@ -112,15 +114,13 @@ private IPackage FindPackage(bool createLogger, 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]); + IPackage package = new ZipPackage(files[0]); if (createLogger) { _logger = CreateLogger(package); diff --git a/src/Libraries/SmartStore.Core/PagedList.cs b/src/Libraries/SmartStore.Core/PagedList.cs index 25fd3af9ca..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.PageSize == 0 ? 0 : 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/ILicensable.cs b/src/Libraries/SmartStore.Core/Plugins/ILicensable.cs deleted file mode 100644 index b2c4b60eb8..0000000000 --- a/src/Libraries/SmartStore.Core/Plugins/ILicensable.cs +++ /dev/null @@ -1,11 +0,0 @@ - -namespace SmartStore.Core.Plugins -{ - /// - /// Marks a plugin as a licensed piece of code where the user has to enter a license key that has to be activated. - /// Note that a license key is only valid for the IP address that activated the key. - /// - public interface ILicensable - { - } -} 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 367040dc86..a14afb2ba4 100644 --- a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj +++ b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj @@ -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,7 +98,6 @@ - ..\..\packages\System.Linq.Dynamic.1.0.0\lib\net40\System.Linq.Dynamic.dll @@ -153,24 +149,58 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + @@ -197,10 +227,6 @@ - - - - @@ -222,7 +248,6 @@ - @@ -286,7 +311,7 @@ - + @@ -330,15 +355,12 @@ - - - @@ -352,11 +374,11 @@ + - @@ -418,23 +440,17 @@ - - - - - - @@ -549,17 +565,19 @@ - - + - + + + + @@ -580,6 +598,7 @@ + @@ -588,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 + + + 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 + + + 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 + + + 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 + + + H4sIAAAAAAAEAOy92XIcObIo+D5m8w8yPd07dq5UUneZ9W2rmmskRUq0o4VNUtI554UGRoLMaEZGZMXCpcfmy+ZhPml+YQDEktj3iMjMyhcpGXA4AIe7w+FwOP6//+f//e1/Pa+yV4+wrNIi//31uze/vH4F86RYpPn976+b+u5//O31//o///f/7bfTxer51Y8e7i8YDtXMq99fL+t6/fe3b6tkCVegerNKk7Koirv6TVKs3oJF8fb9L7/8z7fv3r2FCMVrhOvVq98um7xOV5D8gf48KfIErusGZF+KBcyq7jsquSJYX30FK1itQQJ/f321AmV9VRclfPMB1OD1q6MsBagbVzC7e/0K5HlRgxp18u/fK3hVl0V+f7VGH0B2/bKGCO4OZBXsOv/3DbjtOH55j8fxdlOxR5U0VV2sHBG++0tHmLd8dS/yvh4Ih0h3ikhcv+BRE/L9/vofDSCfvudp/foV3+DfT7ISAzMUPiFkRmBp/uZDWsIEfX15QyP6t1cc+L8NXPL+zfs3v7z55d9enTRZ3ZTw9xw2dQmyf3t10dxmafLv8OW6eID573mTZXTXUedRGfMBfbooizUs65dLeNcN6Hzx+tVbtt5bvuJQjarTDvM8r//y/vWrr6hxcJvBgTMokpBRfYQ5LEENFxegrmGZYxyQjF9onWsL/9u3hlgRidTrV1/A82eY39fL31//imToLH2Gi/5D1wFEViSAqE5dNlDSQX2jH2CVlOm65aHQtg1NpdU6Ay+fiwRkEQZq1di3cgFL0xTqMZ1XH+AdQDzZozkuigyC3IDot7cbiTLIGWzg4hQJTXZU1yBZrhC7eAjcF1hV4B5Wb6QID4KnbosimLlZPSrcEzQJmMcZkUKrzRtuRgRQx6bQMJcaKXr3yy8x5OgszaBAFIPokXW2k5U0B+UL0zH0U96vIO343nK0jkT+gswO3OYELX8Fj+k9YQXJFLx+dQkzUlgt03XHUFJBv2nBz8pidVlkKv1CoG6uiqZM8NgKI+g1KO9hbd9pCpVD35lapiFQXy1HQteQDchaZ38onvKsAAsvNb1IwZsewUEta/RIR6OPTTq02v52FGJk2ve4vpeZ2zKu7haFSqIVlFrOzYrp2urVaGylinZSNZoMg4aLMxasRnK9Eo/T0OkzGlOlN2jjtHRefYVP45uFkYzBg65Rt3VRpkVJ4EKsP7xiaTc2o9gouFXj3jGGLXhdTD+462KSoV3CdfYSZXxW7UwyppOT0Zs4TpLR27hqbv8Jk3r8rc5xsXgZfck4KSFWUN/y7/VAOrRhgtfpyl04rvDaXaawCty0IjSKDhlWWmJfJ0nR5HXwzhnmiy8gb0CWvTibaMptyMb4r6bYhvSbCvttSL9xsR0QTXLTiG5YYOlAGBjdNooFDNo9sd3yt21oPAfjxiCmOg/Gr7+O4lpuXbAmx41l4/q2PhWVboUYaYQXRVmHaT20NS0Nm6Kx+g6q6qkoF9O3fJpj4KsqeCeO9/Wtax6tqkQWUK2RtmMX4AXr7i+wXhY+bp+ufvWGQXTQWjoGpQh19VLV0LTL+autHejKsM9J1izg4oRMOizx2ni+qEa3fId28QJXvuibfB/HCO7bvEIWxRq10ZJ/itF2oyQdwBJAfFPPwXbllzRPV82KHAQerVqTo7NyYZKuANJCFyX61UU0/O31qyt8Pom4yYgZPI+EucV2CStE66SOR4szBG534GstSn7KtCwWTVIfN/kCSRISbGQcl+ltU8OzNKvxga2zfj0BNciK+zcm1AeNq25rQ7RQRhuIHg3RD5A1wdjOKySQaJcEE0SeeJvMDd2kOzITS97Q9TebNIdqwr7NpW7QVk5oKKboHmRV3VZHrFCJaIkdCVkf6BWGBW0bk5iLaY/vAiIRyWtwD0P3HWYzNIbbc4k2l3aLdbTzOcSCg6Jw3EhJtuTpAl4vm9VtTnkffJH9QBN/mwXPHFEO1dJD+Y8WXhbVIfx9vQhApvOi0quH3JU6wTLHu1d9lki3Rb1rwW7AN1wdzSBZUPP6zcG7RuO4DcNqAPZd13Xa1dAINy8ORoVGPbYkws2F2gIXoERtfiyLZo06Ese+6BaB83yRPqYLv5OicZZy17Ouydd2641/nPaOFqs0PylWbSjzyI31XAtXaEkO33KiyXn6ln8qVvAigq3Y44lnMHyBNUBKA/vudU65OFYobmxKxsHtXae19n5AnJEdZVnxNHh04WMKn4JN3qP1uiwe4eISaeP8/qpZhc3016KOjLFHd40WjIwbdHAf4yHtQj6ui6MkeOfwOV2lNe4cWRuDp/jqoRlf6EDe3IEEmyAlWkfrr2gTtdEaozX7EbHY6I2cVx/Tu/oElMFbsB5PDGvlEv7RpCX8Vi8RxdvlJJhTOpwb40enrSOFMB012Dau0wTbRkeLBdeH4DGdV5vI9zhR1KEz9z3PWgHvEQaPEc1KK3Df7gScnh6tDs3p8zotiaH+AbzwOO1QHCV1+khQxGD7T6C6AshqgrFmlcXmeG8I9QaHQxzdlxDShmNAeMAG2TV8Ht8SPa8uYdKULfZQ7dEhOnlJMth2KlTH0RgvYJkWwdI34CRrP0EcKCvnFT55bkNEgtnxvDpD098fZQdbd4tFiiUPZD3GkyV2adg7qp27fw2eT5/RviZYFs6r0yqYnKg3J2iDdV/gWITATQzI0aboPH9EoonQ9VEGgWZjXSQP0Q4h8HaNYDx6BCmqm2YU1kC/sbSn3gtWmkcc+efiqR01XmkQttBJQRuE9O6FOATOirLv4zFEu6/AoyuQPBR4O42TQASfM+PdIMZ43tIS7UCGTW+wRUE2/V0cSpxJajG28SdxMBICwEWHjAopH89YRbKAO3jcvBw3db1xLgTIFob+mVbLLK3qOEg74c8gYl6k1xn/jS/OE2SdE3RpEn6iRSOJvgJ9yxbjNtBtTE6o8N3obVytER6QOQ7EHudVjfbo/Smbxy0KGtdpvgjA1LuzTnN8SheFvzq1xWCGIzNFp9gmbRNtPq5T2MpksMJHayis6uEw8qRY3aZ5dy4XkQlRn/vghupovc7ScIv5J0zvl+OJIruPiY7+Z7oYEfuncWkzrDSh+mRAFKZM4p1YxIu6gBn0idzb3pALcYDpIyxfcGVH7wmdZcux6jGoYLf6RNl2D/i+QFA1JWwziCnNySj5noY240ZiD2jxDxa1HWlJGAQ50TJ4oOK417swEVjiyItYjg8GKaZGbJxXy6L1p4DS3dOhjlpql8RhiZRGv/SlNx30dbHxlW/CX9RQQkySBtQ1BInWBrrQnRsWUIjaYcpVATsskGeA0SbqxzJGzD9kyhgLpgixchxR5/AiG2L1gHq3mCZ0igdRDUCA8+w4fYio7TsNqOm/DEw1Bims5zguUoJHO4QORtN7DkLVcR7Ms8/DIbi6yy2IpscsgKrDHJRnf8kO9C5tE9HREcDq/suraMajr6Aan6GW53ivwb12cDcMnKhPqWKlOqVhPLXpD1CmyKiT7SO13dfU00yQRS3VLNlU9ZwqHrXTwB1G6zpE73GxaXU1PMgCikxIlyu5kAFyZUP+bFjTWR5U7C4LoewwB+baZdqHI+nuUCzjDKFQ4AURQjb79lkEh5suHtmVO6N2+HWIa1a3ZYjyHSl3aD8zMSI1vld4rxTp/pL6RlR0p1bflOuu3Pnkt3OZ+SQxar1kHjW76KrqpGjW+I72InhmREzy47c4p29kZkh8JJGyUB7dYMLEc40DMW3jrwvDVkzceNMVdHv5DZywLGmBXRcniYthDI+EeQyC88LaI9Gh6jhfnVdraFNeQzIOGaB6KFLooAtG/b474IZRj+KwFKvbMizFI124wfdfIrVscuBPd1fiuKgRn056rQe/nTL6ytSLUawLPYcLM1FG1t7oixUA2HnaHE+q8E2qq/RfMKxt5upPdV20GUF43N4ROR2eb2zwWMC+wyIC6BLk99qYrWhJCWLejYsfv7DFN4e27RR8L1MYCKQi6QF/pPDpi35T5amNYx8+emxZzAeQkv2NtRe2VdPS3m9OqnqgTY/5MsGSFwDCsgP0PfB8jqOrfzDcNeqrJdFYL1FYPLTz13Hu4sPC4mWKv0S6/Oj8XkTsE1JRVOUQxhNSTq79xJbBGZ7b4yDGU+UNU5nv09kgXkuWtzQojqtUQuMowXFjG0x9jZGOR+J/i3KKReE7SLFWbqI8uiAh+2WTQatsy3H8F1drOPrp0HGaZTi6sE+jHHjLsL/8GQddn+WXGOpMgmknz8gGzaDhde6AOJNHNUon656y4X42zN6W2C13fgHHeTp9RoqKfiFzgr3oJs5gvJMa5S5Ue67jt/Yw0a4BD1PTiA6rjbotw0IwzsPUJ0VWlJ/gM0n8PHnrO/FWtes+K8IRJ4fpIDXqtiLttGIduJxXZxDgiegNhBn9x8qVasOhFrH2G2B1sP3wQ7EpEgFH2ML53A4w9TfKLu5CDNcP1w00toN+GF0/0OT+U+gIllst77WwlfSXW5g/FHKorjCC/gi5oWPT/yi6JJISOWiPbY6jmjSyheKEQyjQdoUCHYJ3djB45xDPsnvxLF6nXOziLp5xycoFQ0EKFGO30V5ZDd9ntHii2QivcIaHrn0S3dKpGPL0Xl6/nsKWN2nvWC5sTLhJEqK2acvDiPIJZuu7JvtPWJEko1GQfS18cIXY8K73vRV2u+JWuGNHWywdLXJYVUhWTSkOhDo3nDwrRqSsob/Srq4WdHbQS7WP+ultjuHXYX+ibqun0ccmHVptf3tkcTYEZ0XKR2l6Jj1SMxYPb0d6F6Vr6KwoVyA84qfDdgWy8deNSd+PiZhm+ejuLs3SCBtVkn03eDcTybI+r9qAkKOEeZjRO0bfJrokzpvSn0FVn6+PFgu03xk/NiDqvgF3/XNxn+ael5Nx/T6HsxKF/6XclqQKk6FX/jcUGBUbLpSK0eEiiKup0wX9DHOv6yYPK+krC6LuMAfn2mt+16Hrdgdzs7FpxH7zMILRpQR0vSlAxy4ZyM2BSqjNQKiJzYK50vr0GZs0IDtq6iU2atokQ5cwab2DkhHoakinwaqCMCd2tVwnCJkBzeqiqBR3UYZi6UDEUqHXEhCvLl4X6zTR9JGUqzvJFst7ycG4dpO4mOQ9JEXSzrElQr+4YtcuXUK0y8hxjBVUTTADIu2iHELoqgLMvctPoFxcFGleV5/SqlYd+0vgFL1Xw0nGoAF2HUkfg2i11gjAEvXHwagVIA/onOGJSrypztrIQ0nJrwQSaK+GDNraDwR139mjjUaRv+kQHHb16rbO0rKqJ4kLx+brJA2Zdv2RtgbFag3ylwnakQelG3ZkiDtwotTHNE9cT/VOqKdHRvQHENF8N1VD70dv6L/SNbaVQGZI9hTJFbQscmh8RjLSW8P9+3lbvt9W72RaGZIuhf1CO8BsFkCuSFi0+XLntZoWUm3nOEixiwyAsqMsVFhypZ5cAdH6HY7D8qxuy7Bg2qbn8YnIqDrHR7AHlSCLlbn++qn4DDERz6vCoGjfR1BI18sSQtsG/xKhQaRoYZkmXGOBjy7/AMEu3nixIdGCOkKjXzT7WP4u2nALTrqXomFuNpVb9cYl/jBDS3ZXxirO+1t6IVAMi1krJKujFEDsvBQqaFvI0sNjBeoRcFN3WIc0amnvg1Pj6aXz+xzRun1JNjgm7/Q5yZoFknXmIrHu5CvOOVsnqaR5LALkNOHZ/czZQslSenIK9cobxw4a2U9dsbZ7gL3MYDpoKyPzBj90O4/xfXtbwscUGLRenMyc22DWeW3bPc0TQfi1RkxQQNpw4BshLq3DdZB5jcx3pAq+MWcRXBLnpsA5TpGJWG+vnj80RhPED4BQnaUpIyW8xPo4K+6H+DVnkca1qzcUju2Icu86M0nkOB48dpDHMyF7jFKWomh9swHcsJOsXGAlKVAwG7VdCeAhjOCwFqjb+gzy+wbcB8dsxrq94ZrdqljEOIDzuobXMnvwtrVf2Tp8J5JnVl2N8aKOj7R9Dm5kxTfPUzaHi7HbeMVypANOap2Se1G8VkPel6tdMm272utmZTcJ1g0U20emULpcsxBBa/WmE85rNUkzlf6LAL/p8RyWbHVb8/ha+pnpqK7d8o3RPqr9RwOv4BRHmWcZuD9focHi5NjGuxoxrorW+/tgwwjerjYzXTsZl7Aiqk2uzGWQUo2pBRTUux466MhOhjpYq0pwHjSsWddFSG9LaG1QISM9wdm3bkoiOdIm6bzC0nWRNfdpLigR46XEoklkyieK6RRJKfAmlZ0K8VIKF4rb4bESSVAot8PbxvQuVAx/gqobYLzAlwv2/v+UqQO02RHMGQe8GPArfKr8Xbq49hsKxXbwWO/RnSTvyJTuY0xpfLcknvu4xyhldGpibzaAG7aWlQtMLAUKZtm2K/78iusfDCV1WzvuPb5aFmUMcXRs9oyorqlbjbajm8dJ+2f0uW+VR/XgNI8yMr8ty7AkyrYpQqF0dWUhXC/gUOuz3NPiZQXwfhWtqeC3cyqy7EdRky1ZQhIPOVsDGEX1hke0HTYs7tVRXj15BPOotzYDTvm+hiPEDQ1PbWbUYOIORgMbtm+muhYw7y2KgyGo58ORY0hHMova65rf7hD/bVbYLXrQr8AqQiGGnYi0MKzoUUVScaPLnRMpcuKqSJ0YrieEJIkWOsVbTwRpiINuULcVa5M4j35oE7Z1lu74d9WjbdKiPlZPNl8fSXqd6wJryu252jjPbnS0C5XaXQDR1rIdAFMg1ff+lv9Gx6r1vOVCJNPnsoUq6DIBRh3lJgFGdNDr6rZMp5q//jqK86yEMNoN+VipTxGiTqNhPNwUGFRrlGSnfZbSlvkDlw6bFKW2s2utZWC5SqvKkIOQh1JnU9RDippIDx5mXHK4fa5hw6QpEVaBAAf95K2fRrUX59GNm0cBR285IBVqNCEW7Bw7mfcSYl1a0iBrQ4f4IN0aXo90iXGaFHj9LLeju0snSBDWN9kZJhO2/A3zMuG+aZo6ShJYVdM0eIEvQKNNq5Waj/2WufZi6Ej5mXkl65bV2UvXDsmUPRQrqVttEjofVKi6LZIJOviJ5kh6OFJklOu+6cJ8hT1O+oidv3junCOd1xyaNOq2HdwkMDd0kQaUdZLKhK7uJgUUrs+6zgQpNILjoNHUbREqbY1Gw7OFEYQi6pIT6mJRLG8huZ+Ndm8mhHT/RwqfAlHghNheCREUeGJNcY+v1btuRwj7sSC4PUgh1bWKRyuc1gRTBzsgae/IT13XWoDwJSBQ+x8Uv7otQqCPZdGsRz5lHumu1pTBjV83z8wEESrK4oA1aJR9yD4uEX+uV503MqzW5jc0EKfNqTK5NqcBwrV514kglU5wHPS6RsnsvTbeSxmfKlqmlaVwdSF9KE2mT7zURTe3zpqifWqt/e+gJNRtEQJN9OwKaSv4XWcy4G3xU7CvdgY7K9hX2RwfVCLkxZlmm2gdiYPtArzgWylRkbVptMc8SVJwzElTljBPXiZ5fqlv7BJswkY/wCRdgez1q4sS/SJa7PW7v71+dYUzKPz++m/essAGYoXO0w9QT6lWrprbGqnE7DxPsmuM1opYf3UeFtMYTls/RWPXuDE0N+QV7alGyDQ6zUg7rTPNCLvGJh0ZashBlN0bY5QjWkVSbCKA7AzCsWmqbnlsAqtbHpvaHf7xU3sSHhqdSXtZH7cVolZGawIZ3k2+gIuj1agjoV8B/glLiNguQnr1JUweiqY+qhEf3TY1nHIrLTRe/cdqgtdNO9Mj1jWno7u7NEuRTEbbH6xHpwG5H4Q3Nfhl4xIi/XWCeIu1nbxZCmGio+rHm0jc5UnMcIE24yUyANUDXKimZNQRnjw+jv+SLG7o9HmdlsSR8KXI6+XEbf4nBOPTk5av9pGjD/A2Db62QqE6Ssi6+anIFhPwh9jwRIxJNXwM8odJNsBcm5OoGLrN85MpmxveF5quyfNbMIFx0a2mxAAcUuGNLfcN2hCUXRrX6xLkFUjwz41pMHnTk4iMqvFLWDXZ+GQ/AWvsSJ+W4GKjE432qrkdbPRph3zRlMkSVHBKB/4FSH0vffOvqY49L11zeH+OFM66aU9LJvcaf4AZrPfq5S3NTvgS4qM36m6x1bHFJ1B9hU+dF+drge/ZJMyrhOFHnewpjfTIk4jRDQ+4OfaUlQuRElIg15g8bdxg24IsZJAtUXTNO1Cw9w19r9Au/VNa1YXiwUQZ4E13iLvprBpKOE7WgLq+CfIxvSMbOeMgZIA33yu4+JnWS2EwZmhhUBZVXAdHauGcbxr+JinhhP5zRUJn+XKvnn1tU0SpeoaLFT3bFMl7RpW79uwSLiBcwQWtxE6Vb4HSUEamMAILgzHXcH5+HfVdnXSwLxXJzpZIH4qnin16tbbSxgIkr+04AIXS46GCwtfkitDjteUWTSVVmIeAFXVbwzFooIe31WqhbuJx3tnanMTYrrCbGoZFtv8oyIoe2tVe6HTKGAaCVcdZzeUl6XJrwTcQTYbtIObqtnp6hUoos4LGQMa8CBP9YHEkhdJT097a3dQwGLr9R0Eu9dCuCoUztKYw2q0GJLfpghROuJI5KBZ1W73fjNIKsuc+7LRTjEC5kUMVzqu+syRbGojgjeoRnhTNeiKn9iUixjrFDqIpvHZDa9MkuLmCOd7HTjGytqlphvUFbbao/KmjtXNebbiDuA/n9reO5IByX5Kt3E7CAm47Grkm1Y7nRlVHHJECVLkqq+CDlmSqh0E3UA5vIdlsxhGRgm+HRLHzL9rH30LR/KMB3eiDNh15Wl+UaTJ6CO/Q0OgRu1OMZpKRjB1h3Ps2WhtxbJKxrY1Nu1nCXKcNb/1QPOVZARYRnrM6r3pk0cz4z0hA8gr2eB23QFhb/4Tp/dJh62K4S9fkiwwiMwuMH8jQKvgTkicvFn8rDaWjqioSHJW86I0V+alHRCtJZfmZrKpw/67DQaP0uEZyEGltkLazaujWACXrWFeo6VoPEW5XfiVvQQTZlRjFwa5UtxXFGvxKvdkxXXbF/ipmsQn22Mo9rUET2B3sS8VNcvDvJWySg/WgrNMSfAcZ1PBdpKv2LcFj4DgGGcgTGLgtq0a/ZTaVA20kzaANZJNFu8jC2nRwgtrQAsc/cYocEGQzmojHTX0Yj8+TGl1EzRBDdNB/6rai2CDXJUgeEMUnCvQml3Tj7u4Iz0Df8PEPMEsfYfniWX1y28c6uI4XekXsnWvInzoQlIa42egAsYMMgDIIkIUKSvVEo4yhlA5Od7NEEjpFiaSTHaJP4y83CkNMOVDJq1xaPPcriB3zS/gHfjPS30HAoDnIgUYOYiQUiyYEsbZLcQ6fLiGoivysKFtumt4N0vEvJG7vKAcEYR3QX56ym9ruBuz4eWOuanB397WYIkXN0WKV5uarvX/9JcqDIIxui5NXbouu2PnuqCmaKPbSMgjJvlMKFrjZLIhxdoIWobB4Ch7TYWXTyX+Ele0ClJ1943hC2J7neVSkpzhGhGU0D2ScsJB5UiwhkYQl4iEcFjGaxzKO0bELqnjD3IrdBaulbmh4eo+hBJPsNNSwUe8YCw3J1hIlkLnf3h5Z3fmu0IrkmFcFY+5ylEPfDsnVGslaf8d90AU+J1KgBllx/0aL97A8jq7P5YT/tmYTgXiuFTh51VmaoYFFeLMd8fXTt7wb9gV1tOSfBMs7Ab+XlGtZXSbyVhXEx5Ctajn7ZjV84jFaPTpbEuiwONJFiyps96Adqrfi1KE96E1XhTfy41HjhtmMocHkZFK86z63qPPnOxG0RphelFt52nYVeCgD0L26aBt64BhB+UVXeweF92dQTTruVZwXTy9ywllzgNiG7NSuwX34tgwhOYiWt2j9NdIjZaa1Wrsq32ymspIst3Sxah1lYGLw5g9QpiDfpGs/KVa3aQ4CLWIz8gMvq9u6emgisLLh7n2d6g5B4zTyBeTNHUjwJJYXaLaNUWdxmnX0TRtuN5GOxML2M13EQ/Yp7rUpUEFCOddoZEu0+AeL2uoQ56iq0vscSVFK+Oh8oTuDfhfnWLiPEsS+fcczp/7kAl+Cdb0JV5ErecHOtJ08XkIqM3mIc+xDfJ3fmvrbHUHanvc6E1WdVI1iDt1qr1n8blgcgiVgW1XlWLOuH/PkxKJttWPVopbPYH1drLQU+w6WxeE0Yrqqz7CZ+jHOmvjG4puGB3twdIXeoYnmaL2GzzX6tFqPnx4fp+b5o0nL8MvqlLzkdVlkMeJCZjq94kXIQb266lRvRcrznM94pP4g2zquI9Q7Wy2GyiMmeR61bgFpjRul5jXSQFtf5VhwQxLD8xBxMTmsIharyFGWAsPeKYKmnsnFPVrql1BLqUvzGttcImgP3K5uS0G38DiaHRIiQ3RkkRXl1R8NKGF1eX87+qBa39Lin0gS6OD+6LGj7XXTCRo6rxAmtJDDJEISpQAbksNEVEMMs/Zzmj/gQN85c9i5WlvbY2dZ2p12xprXinSdwrJzhXsvPwOOw1qj02xx4j9j3LLog6Ix03m6rkO9DFEvBhhDz/FApVI/sO4NC7qRazmEILkKsJi+000Tkj28UKjpoW6fbh8phBnR5+Y8/lK1/x30hbqtiWIVuFa/l7pTnJEavaqy0xzDBxtIVzBBvBJnEIZj1aLS3qmNdMj4ubgvhkPN2RyQ/NjrVXZcLF6meGu1yGskL/151FdYPxXlwxTBAUgxvRARPmnKEubJS7hnneA8fU6WIL+H5LFQX9Sa9UPZiDzgDo/wRluLCq8zAYvxq8Ya7gukODPmkXHgiiExUPqxsKBBC+mmWx4Pu+GntYvy5U2P5LCiqtsyrKi/jrK29RNjeLjk11GySdhnC/+b73LyucAIwslqs2E5K8oVmnTqbtpY7X1OVyliseuitVaDg22wgFXLbXIAbdG1Z36MRLed5gs0s9EtLLftzRdAbuYG7nI6LAfVrG6rJX+ohdVimWfj5OGXsffRLeEKErffbeaz5Sb1qzcMmgM3qtsihBp9jxGJTy38/JpW3sfZpo7J/ccgeTjPSZTmVXNLHeF6+6sVGA8iEXWCR3OBx8qwM0cmagXrSbOemGCFHaqxQkxvtKoxiW/aAGo9jih+a/q6yTVcIXM6KLhIhu6gSNRtzWOd/UjhE+rqcvqWfXds9u4jNA33RfkSgZd5VAc+PvDxZHzcKfcIbMxhOnDxgYsn42IyFWkSFLZcrFZF/obHdGBjdVtx3EdoRB/LollPz5ao5Rmk0OAaGCkKe0xXwVGSXcKkwK9Wu3tscegAQvdmQHKQuLElbk6HrTYGzjnI93yxOQQLvz1sDFsbeFQZtiaHELbaCrCgHfb3MkQKizdD/YMA7rMAXmWN7hx3pFZj5U/4DPL7BtyPtZJdJUu4aDJ4DaoHnxMfVK16QyM5yJK6rUg3adxjFYt8EfjGJK47fddjRWvWxfpbflqWRfB7s58BeZqjrD1eSsF1T/OFZ82rJklgVfm2Sw9+8iCHo7u7NEv9vC1D3Wrz86Bk1G0dLRYl4pPgKF6YwQg3yTwXQfWr8+3o5MZqzx43AxRlp/KFookqQISd/0BQIf7r35sMiLlkMB1YX92WYX21vTnqurq8VDVcITo8ka3I1M1fYtKNHX05niOzY+4PaB3LK79wC0FSBmQHYTkIyz4Jy1lRNqvA4CSCo3ojoDrIioZrKTp9bNKh5fb3TGFFZAaD02YV6zTZxQAngX+loU1qKMH804AGJq1LHxEtviCDkrxN4yuwLJ6DtGqkNUYMIeahWJJ6XcTChPjznzDRvkU6zvUinF5vev8PzrQHgveg51W3mT1+OWrqZbgTiEJ4iSyHdUoldvHFOZIOpdlYkY6EVis3LDyddUQJJkkuooZ1jQzdiI5N72loZd83QKaeU5BBSwBxgqBPnwufKz6o1j2StzcUloP2V7dFkQk3uTVB4MZXpEfSoSMpFo7MitPjAeZGgKePkJVgknNkNWzU90LphmRKRVau7W10VdKSMYY6wfAHlaJua2anhcFT836cVr3O3hyiyVdrkN77ODC61bl60+M4sK4374x15GzcpozUMM4Ws2tLLH9aG5ISwMHtToQoIAp+EEMO1UEavaVxJE1+nCSnaMqy7lgT6pId2HYhVP4jpa2KJO/ThHK1k5Ak+NWh8HSoUROHXMF8gW8Ygix7GUnjfIVP1WeIBSbwAGNQPHKMB/2j0T9SigWfaBDG1imVX0dJOxRHKqMu3mNeOjhZwuShaIKeKmifn3ojoDoIjUZoZola3sn3bK6W6XqN4burkbHwnlfX4Pn0GVLU8EWFEPXXj6O9Sbcdr/fEslrU/jJea2hek5HD3kh02MalZltHeDXGumLQSzHyViIq4cMzGqalmqdYqLTNo9oPb0+MpAHtNdf0Oos/GnBWdl466xJfNsV8fQFeMAt8SiscKOmvtBQID1pL3RZPslClRWZiS0O2+LFK5UzBQzdi5Y2c2dYR5My6YlQ5iydgB8nSMPFLkkH2SXRPacCILmCZFovwWK8aZARf4I07cssMi2QMv0QsF2akW0HneVqnINtmTUZ30UqL3bA11KqLATTqKxbaNcRBuf5PrZb5LZuzPvdSyyRpePov8m4YIYtXvATBQYDfCAgP6lndVpzUAX4X4CV4SAL5+XLvDO3P1fRUeXiUyqifSan2ESTrZgO+0TdqKEGRakADTb2MSEOfftQ7fRyL6KBI1G1d9Pl834WpgAHP+3mctPauxrKoKiQWWTiX8agOfLatfOZg1YSEkR/Cxw1r5f1n+Aiz4Js6y6Ksh0tfU0eknSHweI0bNnPrId3CyGFFylh8gzgiKuif6otDCMTksCxhOUVbEz6rY7onB/9o2mfFwq5HkPf3PoFKn63Vh3R+FwBUgf/agP94gf5XsHuAyiOva36X3jdlu2Pt8BxUvrqteaIy9zAtKhVb4ZVL7vkNheHAsd4cu6tJrUkyAh/OwfWq9r8D22hkn9xjinQlxOThT7JmAc/zqxQ1CQZ/m38E2AWoKnz/ChXUUQINeoSj0+I6rbUvV8ZpZZJQ+C+wBt09OJ25H6+xD5AKFZ+gvWnmKnL0/iXMkUo+qn6mi/aQJQhbi+W/inx8OrRN/SzBurP/hb7b1Edb7Sdm3sJGflzgBS6CfinToiRaOeg8GQ/sGugyGPvMhXpNblbUitxeWalrkCzxaRxmV7SZxq71ViLPq7MM3FdDi/6XOfQtRVvVEU3Q5GYviIb0rLBT8AWubmHZ69WsuH39iljrv7/+RZguBvYszSDxAqANMRwqvdNXal8o6WDfi9PUTgj98aiqiiQlhOlw/KOBDVxwNLzB3eGGdpovXrVbVXWVza62nTm8G5ZCIy5ENE/XiMqIlX5//X8IA7Vqbjgb3jT3oXjKswKnfmFb+OXNm3eveUPsW96GJbzCMQ44mPEEVAlYiKKFiLlgv3SThQ/YkQaqEN+keS0aemmepGuQWQ+Fw2BpLOIeDm3xJR/gGmv5vLaePptOYHh5R4b2OBKaKPbbW4o7rZn2hr4bZ8OyTAUDw3qyKduEhEmZPnNtbBWXSkcyLY9K58umC/ylybk5lRI2msXc1B5NmOmULd2qhJ01ErNV3Kwf0WyqVzKnNn2hqs3G3t155UVKnhe96f5X8rQcXMbILKQLByvakLCtAvn8LKsfwgR8qp8nmw50VbaGMbtoCFum4Z/9HYMxuaeCxTa2nzHZIczAmOw8WTHm5oXvWRizP6Qw6koeUMaMmzMTezYU8NprRrKH4jF7MZKqExOwkIquu6DVPqRVawYfrdGkYDdgN5oUVkpG0lWSMVUP78JU2jYkDGbJuEGk6STdhTB9lfHIMrRgr/Yj0KT/0d2qx1anBVlktVwoE3W9su2fzCUkwpkn0UutWZFuAh1nRSKbfvT157bjbhDfpI+wfCGBRQYLiwHW2HAexhuLWsZrdD9HWz51vZnOCpPS2YqrqHpzc9ZgDgxLkoEFhAoaDvOxz5TNOCyjW7NTUI5iOi5VzpdNF+iEL1vBpZYbWR5+ZB7d4b2sagwzMOgO7mZx5r87QDZFpXFHKwOWMSYN58KVUvxz7G51HZmAr3R03oVdbsfUzDAYnjCoJWVFjRb05TljkxL+07W1NXrROKLpFKRxPm26QtfbKs62XNFldSbi5x1e3XXjmImBd3CVJ8kZ0/z+C6yXxeLmEiJsKQ6kPcG+Aa3z0VxVxsRsLRf+tWhPto8ihRG9kVc1MurQtD0iRoE3PXplp2XQUrrQgE5kkbZgT4n5BVo7gglEWTtHVlvJtspsQtw/TW1iRg5OxobDrU17BuSx2rNePLNc0YcJmEdB011iG1b1mKaZlZa4LMTiljCSVkvGZydpfyZkKimtbdpnKs53IN5fet08MqHiABFUeig+XL11OBQXMUsYy45pfcZ+nGYZslr6BozdZOGjU4FDb08KUbo8qNHd6NjcoTb1l6+go8dwXcSdLEIzskVMQfb5DSjTKCbQWKb5sloPqUwOs+irNiVWe5kGbS9IzzcZp1Q8pK0l41dZBRem1Tco4dxN0qxt41yroUzAvlZzaNMPOjHdLDx8nBX3F0VVm/lWgJTxag/kwp8i4p3iSWX3J+BD5ZzsDO91T9reDIyj4xIeWMWBwzu5bkwooJfwoYrBt4MPVSOYiBVV82PTfF9nbo/8JXxM4dMnmK3vmizHNi9TYHLOG6pr/PRCTQ9nval1td++G93WcbXbwKZz5FvOs4NPv604G/vjZ93Oa7gyGwICpIypeyAXJhYR75QhoOz+BFypnJOdMARw7/u1Y2AcHZfwwCoO9DAEpOglfKhi8O3gQ9UIJmJF1fzYNN/Xmc8QKLLMrAUZKOmyXmROt8ZZhDul+aRdn2Itls3BTmg83POjvHrCUQCFkCiMZQoKTsVnLYgrt9GIZZahhIG3g9ckHZ+I2yRzYWXiIfhZee1HURNXVVKUixuKY3Tsoaij4kEa3JUTVU0puFLO7tvBm4ahTMSnhrmz5dm21nycC8tVWuEMTd1Iepc/Hqj6bExfTcq/XA0n/tW3pjkZwgAjnCDaEUkOHv38cCZinD7jrJYgO2rqJebFNgMYN0FK2ljVlpFKV9GFfHYd2KlDR6chTaAlnebYpj+zH0eeFWWzIm5XI4OLoDJuHqBcWFeC2olPowTkqDsxAWepibs7bETSIFvyEQurZKQuMbMjJ3HIZ2IleS+m4iU5gXeHmW7Iv+2TadrJpgCVbOTMQTRSCftQfdu6NVPV/6kYTzIfNk1vam2DEmu5xkLJtEMeQ321mFXMt6V8J+n6tAqPmQ9rvtsC84viF7OVRA04vglGIVdxn5Svt4QFFWOY1IYT58emeVJhNlZs3/m1jCSWActYsXuyw54NpYjtA4gj2W+6XkzASDrq2jTP1pyZo4z7ARYsIhftps9D3vfJuG4ndwx9nqnvOKV//6L3kFdMxSXaWrpMZXQFn3xu8gY1qce2j0uthjIB01rNoU0/Zk9VJh1Jq/mc+KkV46m4t21NwrpSnb2lfMsMYi6mZebNphOkwryLOwnT0fMoB6dc3l3j/ni8u8OCip5PtcKLc7FLzGZKdyJAjsBwu5jQRNn7SdluB5OXfEzv6hOAAyJQl5eggoufab3ccJCKXQz1ZGzZV3HhSlMzKrUo4/54d9stezUB71lOgxUnSjHMzpiMETGwkIlfpLV0TOlrNeoblLCnSgrm16FWQ5mQp7VzaNOPvs528fB3WsTcGJmpOhk3s63ujiFqP5i5mFo6nzadYSrOa7Z+LWpos0fawClNVgzibLJSeHeHNRU9n8pYFedi+/dIl/AJic9FgRBUvfwYfe+6SjI2lMC7MKS2uZ3y0tuMZAJutZm/nfDgywZiZwgYa1qrPen+x6UhP4EJSnqEE1aSW3N6IrFgqpyZrtceOay7s7zIOz6BvMrnYfsXl77f7Z65ZxUTXzDQOqZz9cXJG5Dl8lNw9fawoHQIE3KidI5s2h8QzBte0GfstYtY4aAjBhvwmEOS3gVZzaqeTGU7q2hsy1PrLYhcuYR1U+b41StoczNCDi43ByhIN8tZ2sSO2cy6MUxiLevmaSfs5E2nLfWeqkL0S3sxFaCTZVKQZk9A2a5lx02+yKD2bEZTR26lsOBuloq6KfX5ITWG0RYLi55NYoYYp8KmF5taM5rI3EiMy4ayxuhMuJvrh3EYc/DrTq4iwihMYRWqCqNz6i7GWpgGMQeb7mDkBZsbz8ShUmhj5kTvdIk7yZjaEUzAldo52iGWvFrDJL3rbv8f1XWZ3jY1tGVRfW0Ny8orerCwoQc7yNJ2I5qOxe3meBdYXj6Sb2v8542CI5WruzsuqYGhQeNkbHh0R+ZjthLL+UUlYLhTWCz+vGHlWZRi2NKFRMvgfrpdT97Zlhxtt6xlTS7680tc+NhnX7Fs+MZf/lo8c0thvyxfg3tNfjgRViM3HhLCYFabYKg4XsazDucPUKYgr4dpOSlWt2lOAG+QxKSPsHy5TleavLZueDSE06DwoKl1h2R3L+mBj+aF9ezodHrBdU5tekbXm1v4deOz3NBZoNgKjt/h/Z3DsLZDNHZwp2cxqn80qARxxvc8DZIKGs9WiAbTIYl8MAOfczGQdXQ7OF42pzY9o+ttG+/7rgAeaj8CQ++Tgt8erb5HqrwfitlTZ4tgHgbXuuOEMe4ay8/hfXOdbwch2BpnGz+4HyBrhhEK3OrImlpkDlJC8EQQFX1/1GJjktqtlR6rAc8nSVb84SBVPIq5hctpa2Br//t40bbKkp/ZXN8Lm/zmCqzWGfxQPOVZAdSZDeTgkbmLQy5zG/b9HJ235H2ZkLvklLY6DWBqzsZh1yks0agTyLw4ouQwObiMwwZIFx5ToHd8/oTw2dxrtH4oE7CofqpsOkDX2wIGNTkiBMgR2HIX3QvK3k/KhLvrQqDC8duflh4xVT3Neqy+9GCxMivb2yVmtRzLdEu8aRZtesJU3CJ2dmfk6VjYh3lj2pbqrszCe/ugPoed+lma1Ti7u/mamgMOK57kqgexqKYrPtfatlD3mkc4hyyYWcBeJ897Xa4uSmwYpStQvpw+J0uQ38NLUMOTpkRNJC/qKGJTTWnMMK7kFBxsbEW6Mev6Po52tu7TBJxpPQt22lqJZjsYlPzhxplMlfgsyaKfmRelnZmaCaUEd+A+pv5sbHcMkofzHPUlebhqbqukTNtYW+NtYlNFGQsq6rgwpbHZnbpibDuaCXjbdj5dnFlbx9WmTZih3sQ8vYs+BcuxzMjQO7i7O0oyybvtSi6Wg8uYd4B0YVcFeseDg/lZVT+OCThUP087c2pwdHeXZugLvDElBxIgpTzZAznxpIB58kf8lF2YgpNUhLVpe+7sZ+T1SjfjU11F+UCp7+KsaWqmh77nthTNxN8JGxGN9xFJzBfE+uAe3pyVxcrId5o6cpcoDe7mAFU3ND3bWfRmEuekkfg2vaDrbQvzXReurLepMSrjUc3MznZiX6ZnOpHsNn3Y1JpxC1Gnj2hyPhf3N9RvPJmafYSyjnwzMYC4bSfUrciMOK7zW7itMI5nkr2Fce6sbEO26lawr1FVyoBHYtjddDvqRjAxb+6k6XiyhMlD0fDh9sJndZZcSwTSrLnSuk45dG1blzG1MMat427X4U3A8a7zbSUFfOUZ05cnTYlIcX8BXkha//M8xXhND6loasmTmbMV3PKZ6xqzf0kkil1r1ZkJuNJqBqz6QdXbGi7s38wR2MaWR1QIbHjT67Eqy+Yl3GoSjfmVsuvoZmB/03zbdImvO5s0fC4SkKX/gou+jzefQX7fYG+AigHVVWQcL0C78LqmKQl3Dz3fOq42j2MCPjbPm00n+jozcqzF1s60pXPcyvlu4bbi1tNMu7cd2bWdkhulqE6NasCyj7JbgbL+dvtPmNS4CD6j+U5Ix0CeFzXB8vfvFTzJSswG1e+v67IRVSZGfQVryfXV6vWrtlh5B1hgRh4ZbODidAXSDFn2IFniZUSOVQJoRN9fgpRh3Nwlte+joWdGVG3/k6Ro5KOky43IukX3C6yXhXSEDIAZnSEaWtqCMQretVGrVmzRapCZWSetlNPUlxmRnIAa3hdlCmVYusIX82jSpG5KKY6uyJYgOkwMhDV58HtPaQlVYisBM6OmEhhKcTK5I+0Grp0IFsZiPlr4LyBv7gAhl044aDAjahNOJ2T9GiRlvmGtNyDp4iWkhBtCTEwdwSygkgJSZiZ6/wKWWt2xEGaEaP3DN+cekSUg7RkDYE1sstDKhYEDMaI8zor7i6KS4urLjEh6S1eGZLPVMCHBRje8qvFe7xJWxAySIpTAGZF/hU+VSvX3ZWaZLLLsKK+eFKI4lFohUqGwZgJsO+o4oA0RNPUElqu0qpCN1sbNSXvFwZgtkGe0LOcgO2rqJTY025zR6gZ08MbGSOyMioOHQjs018U6TZR4SKkNIqDCYNeLj2XRrJW9IKVGRMRxKMPReWEtV97v+Ii+9eLINasE0KxmP6Z3aCEsF0bsEkB77DqEdvRTKYyh0A7N16KWjm4oNKIR3yCX00v6VrnFmqeyrDaPL1siUVGMfSbaOFrqkU/5MJk3WI19Y1/SkvePf/HMzjyTJ8XXGGqq5xhMY9Ak35eOR/vKgldj9s3YEo9k61dSiqTJt0OkSWqraUCbm9ivYYfWbJuwwe2MVJrbzqHz3fm4obEhe4oMM5XJxmhDF/LdZHf90mgukfuAio3BcMXSphNfAFESyr505WbCLNEmldD0Vm7CMQBmE15+LUdq0auuVjlsGa/hap0BOUPK4GwdGC86xDyMtY7R4ORAzIYFzCHiWK1E8jDmrW9/Y0W6991cJjKg+V5q0AyFZjZPlnDRZPAaVA9SNqfKzUPrr1BIh7a5k2LiPQgqxE8/YXq/lJoDDIAtug8pskcqhZjwMHZ2u0n4JJcmjExMh8rKeZgNSzZy2xAnJuc3KnzPHhXx6+vRtYGlRkWwWoP0Xkq8vsxifgkptFqKBbFyHnyGNdqkmqZYDmkeOB94JKWAGCfmila5zqsi4IyWuuy8X7U3UcRzOLZhg9vO10Qd8Mq7LDmQN3YWM9RCczTAQpinsCyqClXMNCh5GIvRS6XfRurRr1plArVFZusHPGs95ZtyM72VDhu5r4Y62DQcD96cpdjyoirozghbaP4EdnMorq40nLgPI1OfVwpHvFYN9GfwVAPUiSV/js5SyI16N+zJopZ2LKzVwJgqBrpZU4tFKqEVd5oak17UTDHnu7ZMR1dyZQ2aBGOyIN2OhLq6KfMgLnu+eDOcM4oUVUCqhyevIKOdcAyqIZoCq4RSKnQRqNSvGmYqyXJ9aMfDJfmIRCUuiYeINQaV+jVIx0UCjHoMPKiMJvSyqCGHgGpcduld/DdH63WWwsV1QfdTJIoWXj0qXTUZsajYCQ2ttFgldFNPQRTKbfjThm4DtMv4+koxaTbgHFfkhoblgR8akkkrWIxQVi8C4aRoZYaYdJyxFDz78qtavWteiJWpYflDsBYcYUYmIxEbqBOLNoMO3Ww1lPQRYY3DEqpo6GSp9ZWYx9ZifMNme0EAtR+V2WLwJNc0RgPt/9YZDlI49WBk4DIScZFdGvJIMU5idjIts6FmSoZS1zEygLKqhsXsyWhsREJSLfZIdDXLqBTcbaBmWQ0j5DQiy0b03VzCqi4R08MFFVAoUtGilnqw5soymgrBiRpyWrQgWzY2Iw4nKx3YeDMEYEooKQXUDE0GL6UXF3upI5cU58gU6vOKaWjDg6hHwEHK6EEF2WpIwSOaiAhcHKyaFCygeRzs1AaThUUnIY6e63z8EP3FIKqjEk+ECKVxIAjAUm/EJrJb54wQcUmooiRyCEGO0yxDOm4IENdQhQO1GA5bIwJ9OIQTEakLA99cA9RQSYA1j4qvoqPTJmjdglwCYpkeUk2AB71kQeWb+70SoukrqAeorScjnyIsXkNDfRMSQlLh+8GE7O8MaIknAqlHI8DKiETdYtAQRkQ1ATFOihU5CdlcppDTQ4DTj4MHD5Y8KVIJfZSk9t+6XMLHFD59gtn6rslyrB2ZAs0uxlTTuOEwIAimqmUzGu2masaD3P1FF61sikDq0QmwMnpRV280hBJRjSybuMGe3zc3gOT0EOD04+DBg7lIilRCHyWpfWSzyDIto7AAGhGg4aTOgvY2lE6KGBQjM8bmHtdNex9LPnQaRN9zClI1/OFWmYEINCqZW0RGSU8S/ChqYj+QbOr03TY5OVTg+vEoaoWrXQ1uBd0UU+BDPe6+3A13Y09CQH0NzTi1FaXcJl7409FRj1+zbHVjjbfbMxJRAWmxNTMSzXGrNzmRdBcotVs/u4rqIVvVl1HUcEVUQ2W7JkfeLg73SrXUlUCpxyUCy+hG33bVEEmCbAqKkKhLM0k4MMMwWGglUfpQUBNVOHRTkOWGvriroAkNYxgBBaqgBjCTgUYioQFzETkmc3T3oLWc0cJYzWM7ljg80eKSE0OgaJDSoK+T69QGBWcj69RYYqgOCp2KQ+QE9qBNmyDT7NGVwqkHIwOX0aa/Lq+hixTVyF7ctk2dPuUgTN3XaVFrGkylO2W5BW42aZHU4WDyCuphaevpwsH4BAYWsWHyNjSxYaNRsssEYUlGSeZh8/jYxMMjEJBNNmxkZl8RJP4WJb14EIP8bCCVUmh2l/GYpiGBJqxEBLLpvCaExJkQ04SL9BlEbi5Qj5eggoufab2kMoKIpDFVUQ/OUFNGNir7iYZqJsQqdorlXJQldrnZJGdR01BewTxQaT0d/Rw0k74NCSmVcxSLkt/pCbUmJ1vLcbxM5TEJyzY0ut7D+XkMqp8CMeipDaRS43XJgkwaj8I0JgkkCYW05qgWXj0kXTUZpRT5kDRE07Ywsh0ra9sooeZKboP1FBtHvL5z5Rm3SQ7ElCTkINTjYgFVsZfmK5AcnjEFk06odbNJ06UmAgtoHgMDryOJ2USTo5TFzqmo7L117uNgjR4FHtC0B+bgg/bTPK6RnQpM9jSDNpdC6hSErIJcK7A53rRaR4p0ZKW96byZhZSwFmdFFozkePo0MTvxqfNu6BTaMn2kBtepEGUtuW4SUv5p9ZMauXozyaQTj09FnVyqgR0GqZPOQPpNJaNCwxr/hBLWYVwab0UgxabxXbAxZ+Y7QhygJkxABh83dm5aEskTW1qQzFDROFh9fc1VK2XSTjOBDW2OfP1Kk6n0RpVfVCLeHmg08umOTaoU9FlbdQrCowMyg9qOKaLLiT79rKvwaLH5creewhOKmbYj1nOqYKqA5AFMkl7llDFgRhLQ0MGZA2hcah3V9j8WVTQ5g22zLVijMFLAFpOG0vrMyeZJsO7CdJkddF0yr982tYOoYl7Jx5yTSY0oXUfYB7y8poNBEUQVGtNME8N0QZoojXkQbawp8pAQf7HwkAUvOs/L9X3rWvPVuq734LWG6hjU1hqmPMyI9G/fBVcUOsyFHo8zmbToHOaoT+nrPlH6HqgnzcglAUan5bLgqPttFbyd3TmTqr65Aqt1BjcvSqqpw0Gah8RWCKYQh27kDLTDSxLMvQsZfRSQ6gHJK8joQ791oaGQAuEEV0Q2LWuWeBHIZiiaZdyZLJMu1ZSfv/1ptn6UVYxSoaqpkTbNEYNTC1MT04mMPgQck3QzEY171NZwiuZS3WHwaixWpBYf83WhvKbxyU7h8Cs+iAPSFShfTp+TJcjv4SVAW7XhnSCJ69lYSePnNdWVJ7YqTBmTzXilS87mpaS4pCR/WNOQhbYcJFMpBtVYhGOTS/Ewk/YY2FhHPVJTVWmOFeUzU7qsIqaGRj4iVrWvWaJMVdwHq1muohJ1mqVreJTKaG8rINUDk1eQZm2jns3SpW2TI5zA3h7et7rRBA6JQJqh8LBSslBPbunIIuAaOUhIePrKfEnYVaWpKykvQ9pLnAb5yAqMfeDr5qwsVjrS6cB1tpiyltzw454l05p5atTTku66cCAcBWw9tk2dyESjEI9MMurFthvh9TaZgleD65SyspZc1dNv1mmVvRqvTL8JD9hFJZ+O2aRwdgPTsZgXpSYLqJU+MXcjedVOEmBrW1c9ZFsU0qg11Tt6unBc2/ZkZJc8Bxgh7Jt9I+/mPE/rFGSaGyi6CuqRa+vJY8GFF/604eA69ONehpK+YHgjvj5oJqayrv3AVShsSGx7acqyRQnVjZPqm6SVfo/RnKFVDq0esrqSMjcr/4ykhqAa7CMntTOtSJYrkWkFMq48Y6w4v71tEeCgYpDmsBzKfnuLHyxege7Db28RSALXdQOyL8UCZlVf0L/svanZfXl1tQYJVuf/4+r1q+dVlle/v17W9frvb99WBHX1ZpUmZVEVd/WbpFi9BYvi7ftffvmfb9+9e7tqcbxNmH3db1xvh5aQbKHZ5kpxuvUFPEvLqv4AanALKkTxk8VKALtagbL+dvtPmNQkuvqZ0yW/DVTuG+wfJKSPK8WZw2XYPupr4d+diw+3SNxkb3DX3ugOZTcUPUODxAqBjBcqzk3F6gjBFRIeUPai03X/fIHIUWTNKt/8zfOfujb+l63ffrHH8AFSz/zSiJgCB3xptc7AS5vDmcPIFjnj7FZ6CcquxB7jefUB3oEmqznibz6LuH57y3EAz2tvBWbjNADPwZb8LX3KMozRJSi9ON4KzzisTzXOI+KK7HF2KgxzqCgQQqE93gtQL1lk7Rd7DPgpXH6Y/TcHQUKzzwkQ+TKlwvmSrmC7H6exbL5ujeBtYjxCZE0VxWIhXuqq40hU397HJuXwsCX2GL9XQxTN9zJjcfJl7v0UUAbiO05zgN+xkaHsy+yxdlfERFZnCtw0QC5I3+arPSacgTSvBP1GfXZZSL/iBPfsIko+bY0cM280R1o2wxbLqQT6okzRJrnmWHrz1YH5ymLFMR754oZBXDw2X+0xXRcsDvy3S22xF/03eyyXcJ298B0ZPjrjEbvEFDgonRNO15y41D5OErY6+eBgPjVk+8aZTf1Hh34UC45n2y8OdCghqOHiW/695obEljiMDavs1uPEjG7z2Q2XpGvUZwdlTqzvhKQW5LUFX+bUwwV+yxBk2YvQSapka5Q8PdQwLU9j8lDz+urj6Pl2URGn3mOPLWoipsAe3yfyVBCNqP3isHoVJYeh/eJkfpaiybT56rKBq6qnouRmafPVYa5ycJvBq4qfr81nN/O69VcgrbZA3JiCrBKtbAnI1sht513v3wQNEVwGlYfkGuqPZKLRjV69VDWUWElKIBebP8maBVzQYULni4rfASiAPNppn99UN0GVu2Nnn4dVNiIBc9m/kR4SXHhX1Dml+enWgLn4RPJ01ayIG/No1aaHZr0jYrkDdvCsxy4pt8fe1uhf6FUTSgfnsI1oskzpshYKt0fNGUPNgzSfAbuPMnRGOY5+3HSAx8OWOPBrPxCBQekCD3wkQEKJdCh18a1clPAKZhA/fM37WJii7eXzyIwdg5On876Qhnkk1GdXMVBgFApdDlHaM0v+/KT/6rR/SMSlZfPVHdMFLBM0oSQ6QoaTLp/yAONqibYhyjVILHVy8SLVSkdNca5ertRhN5Yu4PWyWd3mwk6RK7LH+SOt0lv+jHf46CAnzW2WVktexVGf5zwpju9J+r5eKDCyJdum1KOocn8FPrHaxiNRqO6+yGWXV6KRkeeoSOyUdF1QwDgL5Hm+SB/ThcR3JwXYE9UZuC9QWneLVZp3T9dylh1T4s5fcIX0kWiFSoqd6Pv0Lf+E9u4XwrLJl7m48to6auUqh3DYocIa/Dt8eWrvQzF7U6bEDaOSH4RCN7zXac0ve9RnB97KsuJp8LWQLKHc6OUQDi2s12XxCBeXAD8le9VwZ3iSYgdtUNQG9HII995fFzXI5PSRQniNQd2IEsj5YOy6OEo464stscf4OV2lNe5Qfy2YRioUOvT0oeE6iD+4+Jry5g4kdVPCEq1o9VdkWPLaQgVj38pHxE0szvaLy95681oPu7HefHfoT1dHZjTwZS6Hw380aQm/1UucXaS//8qeEssgnFvYmBty/Ey5g/w2SG8B/MItMjSOFgsOGy/LRmin8M4hzRAX3zl8d48P4meW/u5g/udZK55UDiFmEyApd/L1tuL07U6BXw7hTo3T53VakkjID+ClklOGh3Fvhdz0IhhksqWGcrBuQMVnpmJMG7HYQZsyNfnei6VOvcbnmEf3JYSibSqWuh2aDhWvcRi+cHrKFbvI5XCthhdMqsBFf3WVTl6SDH6G+T0fZyuH8G3hApZpIcyjCsajFWJgEDSCJpZBuFAeH4a1p8yCUmSKXHCeIT7oD9l4pGyZy45rkWJJBllf+2SJ79nwuy8VlEv/r8Hz6TPaaQnXAKgCF3ynlUBb8skhPA08n6Bt332Bz0a5KDW2yMkmQ3uz8/wRSSyq3J+GCkaZHMgpej55kHuUuSJnhx6pf/QI0gzcppmAXg3l15J8EHIIp1NeDXax1GE3UDy1Y+/uRfOTKyt32iWldy/E33FWlH3/jiHamwo7JTWgw2EHSB4K7ELAd9yEww6+0HG/rU5YI2y87XPbqNskrpDu+F4+73II1xbaI3xdCzyEI93goqsqhEBKip2kDq87x83LcVPXwh00odQZ88+0WmZpVWvQ8yAOlGl1TQYRu1+gfZjoGZNDOBw/oN0QqZom/DUCpsQpKJxH5YzjW7aQoNl8dfaGngjRgkyBwxqEX5cAmaR3bIkfxqsalPgaK8Q5/8X4XS2kX4un+cKqPR7OgcM69+JpjuNQZIwmA3CObWKwQBn7aAGdo50s2tMBOu3E6Cyx3DZMl0BWjROt17CSJtqXjMMI7DSW/nS9Olqvs5TfK0gB7PH/hOn9khP0/psDdST7PPed3c90wSPpPjnQSzKeT87jGdYIvXrRgHm0pVMsSqA5D+KjhgnADAoxVcPH3QwO0Ix1eKZG8J9xZe6BQzjZgXj9mi1zsfwr2C1nEkeFWOqB+QsEVVPCNkG/FDsD4dGCLMRWKPTAi39ocdMArqFl5PBS9PYJhc4ha7DEMXdyJ5ECxLMNTAN9EwOE9yg2z15qR0KDbU0Yz7Bwh91n7yP63AN51FXHieTB/4bFq/Q9lp49cGVOHnZVbCNXtC1Bk31tmfrhy1z8hZ0dI+5smBJ7jJ29IuCjvzufjlYnRbPG1xUWwh1dsdzBdlBg9cPWzwI5+ifGv4pfWQjHiIP2jK198E6IOKALt0brdS5z7JYKUXu9591D7amrbqvaw/FyIpbNVxfjM252qeOiRpt1JVZJsYubMwWCa5N8cnHXtXOtiu6TlR/C5PS42sBY1amYWOqAOSVBSALKzWeXXt7Dq/Rfws3N/qtnWGB1XbTXfOT4TbDu/f8mOw4RCh3dzZf4+Q4eJ10we4jryI6v3QkA3EY3y37f6lBTj9zb/5HCpy+ClSgUbo3B1WnPwNsiLRKf2yKqmuMYW11zstRhXJHLiYlrhjyl2oGFPG0YU+AS4bL12b66c8M4XMjg8r+6NDVPRrx4GtNE8lfjM3vMuo1+eOJVCcIAP5oWyzis1Tetci74YaMGctlkUJUGxQLc6ZBd7s9iChx2pmmWYZ9vn9eE25gKpS4WbxfgKEctKXZxcbXpOLCtyGZ94V1dajif1i6UFw5kEF4tsImFVK1wUD4t9RMg3TlpwPxmiSgD3fQMAA5u0+e6BGJuYOrz9qhk6sQyUBdTmHyUsLb6tnr2UN2i/ASfSTYS3u/MFDkv61uS8X1eszOSt5nF5uN0NmLYftNT5fHz8/WdV2doY99sLpQJ9xX44v3nVvoiZhSGpRH6M60ey/YzLt1/yTULpuzAwF4MHI9zA1l2Dl4NNwNiH8vRVFAdfalgDsdfelyHQ6rdO6Q6HM7sy+HMXFErnbR0b4VEMc7avCX+Zpmq/vYbZBJV7KyG29GLEbL0dwdsJB8Oh6n75nL9IFvfNdl/worcCufvIXCFzni/Fmq0Q1l8kTFw93mFf3+7+29CiBYrM//dJ1SLRTEVr/fNCjtd6rvDRmF91L58z3Vp89ll09GnPuJ3G5vvf07F7qiDj7Pivk8W586X2toj8WTbnKjwmAKH8xE0hIuiEpPXUt9nnyVmpes0XY5FJnDR1KIadf1s21esoptCe7w/QdWNhkVJf599Hr/Cp8pf2rS1x5U20VBhS+aTX0wUWRp2+vvs835RZNmPAptRlzDBD7N4yK0RxUjyito9yqsn0QpgS7ZuixIYV694s9vBWJvaShOfx2RLnC4CRXqfKMYLUPHeOOrrnBXlCkgSHfOl7pivAP90M1vi4MiKnuA3dlqpo7u7NEslHlWmwAEfTgLEu+i6b9O7j86rNn6nfzKNoxhX6BA1owgi8osW+gyQhSrfV3FFc+6GcFc+F/dpLr0EJ5a6Ye6TRymRCwBbs1Z1sxN4Kt/PsftSpaw5zkp1lpZVLbI+9dlt5kVUm6/TrlFIHa9B/iJYtO1HFzzSWDq/+Lwa8Tv69pjmiaCjhUKHPgqJvE5cE3e1bPeOX9f6r86Y3ksxvXfB9F/pGm/3QSbefOWKHKyCZZFDWaZppsBBfvoUuZz8bD5PoeXnsuuJDATfl20lycesV9Xc1sN0crxZdRG+kpPPTZErTnnWDL7M4RzkqfgM6xptJKtCFD+x1AHzsoRQh1tS7jBHDdpcpYkUM1/mcSfvB5A/Wd2VzHQ8G/1YNfRQeiZ9xD7iGaaVWFweysmEYFt1VOyAn/jceX6fI1K3SZz5kyu2yCWufZpHb8d4KnYuaaPt1UBho1H5yJq+/liO+Hgbgwimxe1tCR9TIMotW7JrS9RMzN2fNYbxdY/F8xxXXnUcbv4M8vsGp3XnsNDfpw2jOS4W3Ka2/eIaedi6ZaXPZQ1FDji7YIau7onoiZRDOOiCzSNS6kaUQA5zBO45orRfnNwZW52V688ZBTxWROnOuSd61RWmw3ssHjpcXXVbjf++xydNRtIQSJeCodDh+DJP/2jgFZTsyrkiB9dbBu7PV6g/OD2FOHRJsUPQZc25n8mH+WXwT22TtXdDr+qSRFxURVOG7jtkGH3k3ArNLthtfe9FcWJL3DFKrhFzRS4HwWdlsbrImnv+bUe2xOkgvmgSQa6oz1sjBX0UVRjn91g8Y97kVXeBw2PsTMjr05zB235yWL2ajFti2i/zrAXbbskfdnK6drbP4v9z7sjmuqI/RH0GXv4a8HgGw6oqj7Mu4BZlAbBT75n6J3J/FDUve1zRHtvmmPDh3OfJd7tpiYRzXhug2GlNWeziUDSTVTFKAl6y4H9sYFUj6xxLlsQeYIvnPKzcdstqR4/66SPYONcKMKaAqwXy6tvq51O/xuz7FrMikN0rjB1V6sRNzG3Ll00dzN7HmZP5lkagtyVjh5/Ptc7DcpVWOCShvfMUuOZz2HzWfyOKbZXBuNcOhvRt7D5t+Lo1HHT6XONLS9lRUy9Ro2lCAhNicJMOswdnuaEbh8viXvePEVbfU+V8gWlyl/J2kqzcHXun4k2NSMDs2/qGJ/a6eICc04D+7ojtKElgValwMqUOFjmOa0JWp0pfyMq3RtrPirJZhQfSDGg85FhTdxyhvS7WacKjGD7OJfzi9W7Xe93nF/JsIRdbcaNt5xI3Eb4kbBFBNggeX+FQVB5HOkiLPIrh42zSgUkge7uRKXC+MSC9LODoXGy1J+9X7L7aY8Lvv3Bouk8uDrCqlmVmob+7Y1PNpKzcHTvJui3F25YcdJabzgLhyspXT02qoj6WRbOW6qmhZJevZXwd1h5es/Sf51BSWDSl5hNTcFBYljxzyLc5ktlGVEAEs43g8VWHisrb6u/a/otl28Tde3ZeQ+YiTGDa6XSXFUW9ccSENCbLCMAUOOIT00hRn12OIiXvAXm8ARR359Vdee98CELOR6HU5fi5PcZSoJYUO84LvnbYCHi5Ivf+ytGKpQ4uxPbtKjliodAZb3vFWemfVAG5c9xJU5YwT17EWwRyCJcW2nqXgI8nYEvc+8weWKpkRoRy2NmDWqZxqM+ufN3c1jhl83meZKhjMvbmITxbwBec9S0MEO4tkLTTw3ODurHIIQNb1I5NDunaYqcSNGPjITxb0IyFh/BsAdUVZU8O4amfkJ5PsY0JsjMIpSSzAI/RtpSYFuAx2paS2QLcKXgDVxGulvZfHflDznU+nNZLmwTdpsi1d2LWe/q7yzWZuyZfwIXsmVG+zAXrEygXF0Wa19VPWEI0t3xgnwLEYZ1bwuShaOqjui7T26aGyn2eHjKgxeo/VpmhqRbEffVWhXzKyh1CkSLn6Rws77XCIl87B1jifQFSumjji9TBCWIRmYmhh3QJXikXYqjX5qsbJtEE3Xx1xCQZs98Iv4DqAS701FTBuPX55PHxvdjj9qsbptPndVqSoJcvRV4vRaQCgC/+/4RAQmW+3I+DP6QlTOoP8Dblb/eogFxcUEO1Lu/spyKTuKNUUCEtyThIDeXV0jHIH8StlhTAG78orFIAP/znJ2rUuMwLa591UIV5KPfCfn4LePcoX+i+LhCbRJq5Wg7hIGkNshnL9F9ETK9LkFcgwT+FhU0DF96ayKR6yPAWL2HV8HnFTbAu2nGNMyxo6CmHCGlBNiI1lFMkxGDlaQakAXO5vFImS1BBpRdWCuCyr0rldy+YAnePX5cjUOrv68vcseINF0543NTtGq310llXcjnwiZP3fReOZeht1CVcgTQXblYoQOzb+ASqr/Cp26x/LXDEayLJ96YB25rjoH7D/b1Ce6dPaVWHP4QuQemTU9gOzTiHR4MHkpcZ6rujg0J6OOG4VOxa3qeP6R3ZP0VkLglKH+ayQzMOc/Vt81jo7w4atIKLn2m9lDKZUOiGV5Iqhvr8J2DcOLwawJ+TXeHvzDGKWyTvhqmh3LlfdgbHlznsDiTeWncv7XnV94BcRwSCzSQFcB872piuZXslWbmL5ZOk6xSbGoJNyRV54JTcWOLLHOximGOrXzR9qe+u2CQdZAocPISwqoRb8sNHF27akJ0YfyI3SQD2WKsOGiNC+JFnyixN3RHDkHCDitChTdH09uRFxOe6/9EAQmUW1earg+GRp/VFmSbyk2Gx1AOz9NxXLHWhpKq/vn1V99O3j9IDZ49j5X4P1C6v0kErQHzbkJJBAeJgMhiPSUOPRzXHot7HoR+KpzwrwEKSv4wrclmo+qpKs0cC4BIpm8C8gj0OIVhfLHboO9KfP2F6v+RfIaS+O4RANvkig3iV4YIfqe/O+vUE33iVadi2YLuW568kQVbw8ozR+C7P8rpjLs/hS+lXIfvRV7+ER9fF8ESuLJKeLt5jS5H20MbxG4kYfdxGVljG4dS4sd7tEDit1H1zxXKMSvBrLxJkQ5Gb30m2m6e/T7+X2zkJwqdIbSLWEKnpsXi+uiWvut2a/LoEyQMamOzQki9zwIrDEGV2ClPgeLII5UegfJnTOWH6CEv5a8FC4Z9AesKdFTSmACma0mUxtCk5B++/ezhA5LLp7D92dzTMZsPUTZlfwj9IBsxA44VC5WW2aOuPxEaRLrnFZqC4hlQ8v9clBFWRnxVlO1u8r50rdMFLph2SvTvv4hAKvfGqIzm0gO7zJk8+L5a6cCq4u/sqJo2mvzt4fRarNJeG+LElLpSmxFd+dVABspuhPbMt+gX58wQp3xgLP4vNa/E3odhuzX0Bys50EW+60iWu/jkZRrbExdjZ0Fh2JC0rn23THvG8ZgzfdD8qxMKwhAtyUCEfNwsxx2p70Hc931ytYTJEJm7mPiydsA61T25hN3zjqMWY4icfybe1NCDbAOt4G+cszZDwCUGxfJmTCn36lndkUL0vwBQ7e8V35/kN3WQFGhQazD7GhRO6kQwNaR/s+H/ypEn7wYlj8GA07tuhFFq7xg2d/iUPS0dY2REe/2VcWnnumZ53Xn6AMkVG7CAJJ8XqNs1B+Kphxu8/j05IR1pBHhpuscAfHCJya/7h0vaLwwkjyJs7kODbeSXa1kpvA6tgHMPBJHFgTmE4ML/nL4/33+yx/EwXPJLuk8P9KclB2Cf3YB1QQUIE2XGxUOiBF//Q4qYBHKzwqkrvc7T7TQlHnPMPMMrK3c/wcIpS4foSV+a+8cZBkjxWvswl8Ez2FNDm6/64RpAGTR7k3guuyHE3962pv90RFF1iR3FTJ4Js++o3ypIXb53bRYdDV0m5yZKVO4Q3wOcafRIfHaO+u12c+KNJS9l9if67hzifFHldFpnMy6uC2f8NQVyZiyBsk0vZUZYCQW2ST5NuKD1zMm+X4iY3Q0fR3u2d02gqXIFuVD3O90GhgmVgu8bNqG5RXv3RgBJWl/e33AELX+i4DTpa/LOpavF4XSh02NSQzYcKsVjqspZdlPAKZjCR3KpgiqZYa1QYiTjIFkamwOXGR/6Aj/ek9otQuIfBX9cp2uljfgxThgMaD82nqbv95mrsDOn4KVjVIXxfNscxtItfZ65zhPYFg6BjA/zT55RAXm9uV7EyAqDk3AXkgwPTV9lpDm4zfqGgvztgg0lTQqFP1GcHh534qNAn5xeFivticGlxKwJbNOdK+KleZcfF4oXvIv3dxQ7Ka8TYvd/tK6yfivKBN4fkME5KBAncC5GWPkm+qIXlMM6tnD4nS5DfQ5KeTN+YCnRrVFvXqdC7Zf3YvB6tV1XdViWne/nB78UH8Va6x5V0LOyfC1Qo5DBlilwth7OiXIG6FqJlxFIX4zjs4SClgDa3WVot+dWD+jynYt2mgDvlqAucDPA0X6AZ5eaFK9oaFUbYpPsUwUjrwHxtNWX1cbRZ2zyPY/PVFZOoHenv4+1e5tqkLuEKEncTsi0DN6o0Kp/Nqr7+OOxDGuUOLNpPUy6okiR7zgn2doThjkHycJ6To0g6y3AY6ymQejChNaZx2DHatYuIfpi4Fxh27h4wHbhzDVfI8Ak9KJNh9GBVOzTbuofAz51fAD6MaPN1j49dT9BE3RflSxxu4rH57ECNKA5ctHVc1AfSRmEiDllAXO+BhXaHhT7CHJZpEin+g8fmk/LYiGLbN5H/Dl/al8AZTJuvTpgEJIc9hZSPj5LsEiZFaK7uAY0H52rqbjvLxvV7jHPael6dL3i3bv9ta9jwexmFDQc0HmyoqfvnYsOrrOHc9e2XOQLTVc9W6p+rnMuZnCzhosngNageAp3JFCYfZ7K2+rZak1dI+nL+Nsfw0cEvKTzA6fr4pjSMwCeGoC7W3/LTsixKwQ7YFLhIA8lOU9aCA4gtccN4mi+k+Prvjv1rkgRWlbyHVJljH0UaUp+3RgMMz94GWlM9Gh9rSl13HME/WixKNKnCnYDNZ6fbV1FeWpMtP+rFZy7vKARVU8I2PDbQLUqj8vGH6utv7YpBnvNDm7snbLaxGpYtcgqnSAshngJ/2mNvRjf/H1LUZBV8pMRj82dIDYoDT+45T54VZbOKd84poPPgSgscI51tUm2KL+GIpXOdUBIC8aiGjw4mfLFOEx7P8HGPT0wvSvI+SJdiPfRggsbldS6hRzASr0c6xz8ri5WKu/kyF85U4WRLnGT7nzCpBZFuP7pdIhavD7teHAaSS8PA0d/YGezHL+1D4jxCodgL9/D2mxI9BbHHGoNsa1DB5yIwgJFC5OXE19QeaeO5aVJ6KV0sni1wR5bO2COT8S4zJ8YcjUGJQy+ISeUYRlrRRtgShG9UXJ2cs0X8rNYgvQ+0/nssXhE+qqpbuwWNZFHgC1sskvbLnLcWQm9/zOZgITZ0pCBIFpmXe8WAYVtZ+zhJyIPEnXeZZwBJ8S6KTbzz05YaCXnqUzhp5srmv4KFX5zGobkgy/gsX0zJ1oj1V/hUfYZ1Dct4jio5Tg8ht0U0kqxLWxedVzo4Rz6XsPd4h0bTLXk7Ekl2soTJQ9HESokloPMx3cw4tnWh2+aUcOSFPLxP6AKX5bjVUC79vgbPp89QIANT4EBV8NwH7AvuO7bIQW1seYK8eEbGtiiWCGnS5DhjqJhJU6MJrQv+KhnAlHrskHIsTHpne30xaUr8kskFeMGtxnpFWobV7yVpS0zjyB3fPI9NVm6PPdZbvDvnLubJFpfbIrDZZHr9JcmgLOE8U+CI7wKWabEQVgi2yOUktAYZqc05AJgCl20Omj75i8xMyRxulFhxied5Wqcgkwo4X7bHck7SCaX/Ilks8UBC1xUen8+KYoFjHFk/jXYNxO+yhBIbyfkkv5THl3lgVSB0xyW5rMcUbA3XX0Lsa+8zt4aubTQur5VNj2AcXr/ok2i84zcCm+8e2N4rsL3faxv9pCyqCu1GsigcxWPz2QsbUewvV822kgZG/fhF+0wY5YOa+gwfYSYsatR3F9dlUdZ9QCPnrqRLHMIMmyyTImQKHEzEdXd4yRFt83muKCb8+KiQD3f46LJ5voNlCUsBF1Mw54lL/NSIZyV+Wx5nKuXiUIfPLtFkJOPtJ1Dxm0O6YGtUFPqAU3wGXohtkfjchVXV3NaTnz9R8onNmUvoDpA6vfFJn6irva18snP2Krm/ETjNGIXPBMvrjTO1bUynOMH0dxeXUZI1C3ieX6WoNuA24mKp00EHqCoceIoKavlxhwjgYi+0lXmDof/q4GpMaz6DSfdpyqisL7AGXaQuZ5yxJW4YlS9XCYVueCU0oz7PH+t1CXOkno6qn+niHta8IciWORwLkhr/VeTc0Onvrth+lmDdmVYypEyxK26063iSzJRQ6Ir3uMDKnxdovszpGBct0vyTLZuvjrKMn7AWxZl8nX65OqqqIknJu8vCmoWWUhIfeXO0Xmcp5nTKYNEsVPp6/PLUQ6uMELRGLsTVX9nAzVXRlNJHj6xWuwG1bNnDxBz6EtjNa1Bi8fbsZk8sx27+9lY63yEsMfjfnBhiU0vFDgo3mhWZe+xbzQtDJ8M4oUMzNSOQADf0J1pWl8Xi5hJWdZliI+kEj9KkIWxqC3tZpk4LaKMszG0FsgnXQARmsehyoPpoqTc10/SutxvqioLuaRwRWnwDp4Xp/YNm2opYA6d/QBhh4iWdC5voDs9sE32cZhn6PLhvLWabr6Kacpe5ZnEG0pRDFnPaOdRhjOncMWTIL1I8n6/Oq69Nlv3++g5kFXQcfTDzXMBylVY48U2bkvKGTv+p5SFTTSHHAwdPg1uwl765wNkTkEfgM0OHA9cUCtds+saaVVQ1VNrGkTXk6LdxpdkXFriET6BcXBRpXlddQOzN9woufqb1snMG6yJKjJXFGBKhigVfGBsKnAEWVwQ+MXd4GxcpExniKZzeVHexcIQ6MUwcDmkgH/HYYmocHvc2MpB5/OFmTrdx7/6/Bvd620YGLiatIoUbGBsrRkQcarp4uSSsOxfFX4IwRV+hel8rdoiDNIclDzI4c7svw99V/wFPKLiHX4oFzKpNPZzTegXIiKo1SMgOfgHP0rLCoeHgFlSwBXn9CvX9MUVarz9y6xwuf2QnWZsOqgf4AvL0Dlb1dfEA899fv//l3fvXr46yFFQ4biC7e/3qeZXl1d8TIrcgz4uaDP3318u6Xv/97duKtFi9WaVJWVTFXf0mKVZvwaJ4i3D95e27d2/hYvWWr96htcLyy//ssVTVIqNnnDo47ea5f0H9e55yvPHbv0OB0/oZvoR3r1R65Le3fMXfJLyFu/D76xRTlkgjeYqERJcCfK87x1CQ9Oz1K6xu8KXQQeW81aJvT0vbBvJHUCZLgAyRL+C5vQPx++tff3HGyRyqWaOuSzEEQ8DMvpY7Bm7KjutI7jh6nCDtDjRZ3SO5TU1I6AMWAwPCBi7avBJ1DZKleGdmZzmRGhvXEi/efz/PF/D599f/F6n591fn/3HDVP63V2QS//7ql1f/t3M3Ou2IWYxmYB9OaN9o0jDpu19+cWbTs7R9n8SNPm0tFWGsxAOp+L5VNJbbNAfly39bgef/7orJqHLe/+Kuc76kK9i+QhCE2FoSPxRPeVaAxZ4IXz+cNi1I21CTp380MCUo71KsFB2Roh1Sj5dEnNqqQ3X3KDT9BHuxYI/vmHBxHLbuQhRkTOiFD4tsLhEVL2Snz3WX5zsGNrSpgU/jLm97IlebAA7/ZQTn0jXYOu76EiO1sP2cl6frInpXr4sxOnoJ19mLW28d0I7R45OT2BiPkyQ2yiGdW2Szp40ljKC72CsLLUJ85aBO8Yy5GotY4W8CEfzkGyMxdsdKyXP55txMRLZ2mA3NJJOLvkzQPd2TdaLLpaazXX/91d1sare0ZoObxW3Da5+KSi/kPv29KMo6RJKQwVnKTKbwjg0x1ZERt+mAr6og6xjb2a3LASk3woGo1ghy1+XoaCOG9kTwmDHRFwg0E/3XXzysmNNncpGAOf8/X1Sx19+hmTb6ytTCe4+luG+CjSAbYSzdGEh7eO9CdljP/NLm7CjI01WzIqvb0YosIf2yC5N0BTLsvUa/KuKGfvc3xHLY2Yhm3bX7aPATtdRi70P4YlEK36+1deXyIiH22l7LtIcnx02+QBKC5HFIrYZ2xDV/YLmzimczPndTja4bZKgxWev8OYXNzReGi0sBF38R49lrT/jpos+e4MpMQ8UgTmrp6d0JrnpQV/pTuhAu7EPpx9LYww0EWCZowkkKA38D0MZicd7I43QNGvXv69NEbE4lMA4Y9Kd0Aa+Xzeo2p7ZOPoh+oPm8zYIm4KK5zdJq6aKwxjuAjOrhYPMxOCFzVcn7pYhxC2Er4QUoURdIArAhj1QYxo7TkfpNH9OFo3smXO3Y+Y5G0Tt6U9YL59FilebDG1MREPaM070ZEjbV+Grrt/wT2mleBC4uPY5Y6om9yx130RKudEeYFupSd9zOHmVZ8TS4A+BjCp+CFsSj9bosHuECv1yb3181q5BZ+lrUUfH1yEjuUm6wgf2LhbI7urgujpIge0K4s++P6uqhiS4iIG/uQFI3JSzRClN/RRbURqBjtfIRsUxsnOfVx/SuPgFlkJXV4whfn7v3Fr7VS1heMDeU/XrWv98wLPV69ehzinbU4I1YnSZ46T9aLLgmg7p/Xg0xMREiPcKm5nuetULYowsaGSJ6KyXf7gR8XpvLDsnp8zotyY74A3hRYXQJZyF5lwnCcO7+BKorgCwRGGNWWUzu7gG+fkggGxoYPic6ui8hpG03z5OXDaL2NeAo4TVDWvQwfdIhYXKah+g7Gt/FkNE8AkYmo3mI7xCfSQxPbIbosjM0qf0BR5BRtmgvgoCsx3ayxHcLfBxLll2nHu4JocBpFURC7rmfgP0CyHFq7fwRiRhC1p82BVl6dZE8RHIT4j0RwXf0CFJUN80onAFuIGkfvVavNI823s/FUzvW7p3fsGlANnx690L20WdF2ffvGKJtUQjaY5A8kAxM+IZL4MEG3qJhfOctDdlHCAMmhSxf3VFkjIlp8bUHjjHwkYHDRYeKCnCKZooiNsfAx83LcVPXm726p8hg6J9ptczSqg5H2MlyBhFfIsXM+Dx88J0ge5ugSpMwPzONIPrS8S1bjNtAt804oWKHordxtUZ4QOY9EKuzEaqN4e2U63QFIwTx0bhP80VEzL2r6TSvYVkF82KnvRiscGQG6jTcpG2i/cJ1ClvZDdL5aPGEVU29VLjCtw3wUjIqs6L+96eNVZcYLGQU7ctvo1Gb3aZER/8zXYyI/dO4tBlWpdg6Z0AcV+HEOjGIc7raPyZFoVB7HtyjDrbl1FU68vQRli94Vt1dL2ztEMcLfXva54YnXTukH8eggt3yGuwoGHB9gaBqSkhuhuvDIL27O24M4dAM/sE25eGRbON52qSzErdY6FFAFy4ESxzAFcNNwyDEZIiJ72pZtJ4fsAlvjxjRNsQq7Uf8hDGqwOd6cE+jcL/49wrGCdpSh4BFNxz6pvw1iKVbjX5GMsx26CyRCJi6M67qpGjWODJ6ETRrIhaVR8Q3JJAcH5NVMIxRN3jw0u3gV7dWPPJs2vureHzCmXDkkStiK26JHOVyXNTIAoseI4VT/cQWlJ7t4gRKHcKRmHDDOGc2FykJMPEIRu8rhhj4OFrtKv0XDBkBE5xVXRftjQQes5cPt8PxjT1FsDF17KIIkQ1/CfJ7g8/eLyQ8VkxhXJ/UFkZu7YTPYt8izmUUJPdif6Tw6YvEZrPQwPZR7K3q2hNjqBtNzAQ5Vima/uoRMQ4LVa4cBvVffEIOx0l00x2E7RnLzHsDbTybY0Rd6uzk6Xaze5T3rx+Zh2t6qBk6czxpL5sMqpMBeGm/qzUc3QHTvZsw3P0PipHqw9ViINu8LnNdsAkQAnzMG6QXyohpr6miENPZIWIj7yms2AgEYu+M2CAKnz7XJaATP45hN9EHTHui0owOH4+kuidFVpSf4HP3VHFc5NuXV9fVlNozX+TMtpTCC2TR/qZmUAfOqzO0pWw2V1MiRNSOzIH0xaoDF8bgQpqi7n1ga/9puHEP2XCUg5nofnmK7oeTibgnE4fTBGPvdu804eBs321nu32YQP+8XfeG+34sSv2oPEzkoWaYUbJm3gOLJ/PnVZ9ZIShQZlfZdM/4M/aLDuYEuz63wcz5j32wqtL2+qXn6ZCdFeUKBCZi6nFdgc0zQduVjCjSNeKju7s0S4NNYXLfNMhOGnt1P6/a84I+LXlINIMmC7G2y2HZdj+DqlauKfOn2sfd+1zcp3mksFCMr7/GbIHS5fyFftDRzTQQEIRsXGTvS7p1R8Rg3x/rdbfnuP1Ydsl7idEODTGTRkMmXWM9nzparUEe6T0O6fmijRndVwySEYQep519TPPEw8HAVQ/pyQl1Yz/QFCDy9C4msvdRkP1Xur4oKpzpxS8US2JHLYscyjOY+eWJ7DM9zb3+2e9aWinYE+1p9PS+8/D0Eudb1S2tQcYsQRTjutn1U/EZYmKdV4XxLsl7V767XpYQ2uP/iyt+JCKwTBMOd1DKxR8gyGTeqvzL3o5Na6FnX974s8j+NpzyRHsk+D5H1GtTgQW5vid6VibCUyz23E0bVXvC3ONZuTOuq7e3JXxMgUy4Qu94zajQrfn0OCvusT27Jyz6GeT3Dc6058yjm5pBTCo9Oxa0tJ0jxua1SIeT29axGiU7d4frRJNgwU6kNwmwY6G8BvdxHH/beRH8EE8R/+R9ii1ur172RM+Oshb3NDppMnKjSH8k7H7uRw4Or+AIm9WzDNyfr1DX8R0uI23cw48v6/26EjquTdNGgF/VOCnzJayKptwbE3x2+6anp5HJfRLc9Mil1wP8TB+cBLtYXWTNfapKpmp5klw0iZrtA46OvsKnao+e8JudQyNa4OQZpziocAqVOJiiaODtNG7/TPuU7bBfDxuMHq31RZkiy47y6mlvYuzwgDyi0kmt6P48P5XYP2zzo6jD3t4Y+5IVItqecM3sy3w87mlD1TqdF+dEP8oSHS1REVlWPzawqpEVi2VkGw4Yt9MCGf/Ykz7j2hNdYN4L/vqru8Ec6e2iKBHACEnH9xiHgu2tLNzgGOA+Rpfwz0gBuuqpCzGcYLlKK3zuegkTrGn3l/lD1qFRhGlILBCG2HquT58xVUF21NRLTNeEGAN7Ne+zX52KFwnbz9Y5dacmItpOc0bG/g3z1nXxAONsIwm6oySBVRUPKYn/QBMclIXJWujOirJZ7dFZ/nWxThN38eqqhaUOmVu4rZ7htLMXLmxuWPpkMN61u5FEPgh37ImAkAG5s2hXbccFBM8jbjkwQVwbu6v3HXrkovraauIgZ9SP0Lfg8cUb3IvAFPwdFsWUe2IjidICN+u7qYH2Sfl8LItm7amBurrjhoR6PbMS+YTia7fqBMlyDIWCZU9mVB3Uyigezt1UT0Qs90RH7YR62Dd2m8Op3hJvP5iWjCXiBU6CL2oCFX1O3t3YP7BJCkKTHAR2hktREJzjIOwSN6mJ7y81fE88hxUDV5fEOiKq9t5h5ATpQ5qwpixhnrxEuyTeI7wEm+NbQ+r1v3lLJXvcFUbtH6COrczQpr1GspCd50mGumpJjr/6aeK+MXxrcIrGrnFjw6MAE42QaXSakXa6YZoRdo1NOjLUkIOwujfGqDGk+FO8NoDsDMKxaapueWwCq1sem9od/jhXywifjM6Iw5O6o7ZCVMdoTSBLvUHWzmLkN0cu4RMoFxcFWuCqn7CEiLUCE0cuYfJQNPVRXZfpbVPD2LtGoYHqP1aR8iJ1RoAiws819ixO7rzB6l9HGSOJiMPbGPw2TAmRHjlB88/aKV7TjrDQEUJhE4G7FM0wFcYXmCi8eoALFemCe3ry+BgnWxNGdvq8Tkuyc/pS5PVyBLz/CUGcsdN8+SFFyq3+AAkfhsRrDmi6tI6fimwRaa5E5BEZgUJ+DPKHaFspDm80EaPxnp/ERtnnyIqM9vwWRFqQOg1NjIKYSVxxYA4SiH+1r4CXIK9Awj4sPgr6aOymauASVk2kxLknYI0vTMcnjog4Yq/RvnOwieJ3/aLBdSsY2516AdJYweZcBqwoNO1Q4r0KTnDZtC7DUXxdY2cj3t4DDXq7cAlXIM2pIHWPqPBPAD/R2m1uvxbYLZ8wGYkiHpX0u8LvFbLtP6VouvYm8eLsj3CSqu6td9XCArImyGHyMb0jxv4esk4/NPfJ29QMmr/vFVz8TOulJwtx1YO7wmRgiO5umZJZ94RBe3OGmmW0lnvc21XgCTkw7Ekdflg0spvvvOq7Su5hAd568VQaaGu2jrhhuESDXafYEohlqQ0Y490ZuYI5tqtj9bBFF697X2BVUbdmA18R2cwIsc1me1PILR5mj5LKDOOJGscyo7l2Me+jpP9oQDcR/rr6e57W5GW9sY8bh4ZGP12cYjSTjGTs09B+v9Su12OTjG1tbNqNdmQX/6juQ/GUZwVYBCcjOq96VFEMo8+IxfMK9jiVNqHV6ou0/k+Y3i+9zEKbFo6bfJHBD6AGcRyE3Vvu+CJmHB51W/a/4gQf+7Tsz7JAf6XSpIRdwOwj6YrhbcYtNx5p7+Z+eXlmjzpuiRoUvEowHIMM4HcFQsy3avSompibsLiHAbT/w+NJOb7+CA/K4YOc1f48NDyjJr8uQfKQ5vcRzwBJzNu4Rgk5yIOxTho/wCx9hKXNG5Hb8iZYLwB75MUYhuR1v6OtGeF8SvQbj+49cLA96qbML+EfOFvdvkx7+HWpSBM3u/0Tw+90CUFV5GdF2fJKHCO94zhI9r8W+30fpLYhCE73jLhkyaEvntzdfS1iRbnrnvPmk/O6v4hAK4oYt8K2JdrEYYEsSKjPCSj3apEM15YXoOwMhyDPU+sX8jtmpeuGPX+9meTw49XZ9f/Mxx7jXZZA0glLxP7YuT/afjrG6rlzaq6b+as1omEfFjfM457ovJnFQk7bb2s2Gtd6r6LFFia/+HrEWZohcgdmBW6Ta3fkC82vPXLWeB1B90QA5EOMxXtbk2l+FkbZExbZmVnolMo1uN9jyju+ae1KvB+gTJGtM/Aw2kXepjnYJ4330LjQ1CoMs071Txj7vNgD8uYOJPgSUIm2VYrrfaGteJvNVk86kH6Nhf1nuhgP+adx4x5ABQnl/Y8BnZrBP9imfB5QqKr0PkcSmBKmNL0S/87DtdQfluBcgh5XSZjaIXv/fruH49/c+8HWDunHeRX+asXebfzRepA8xNiQkz3Nt6b+dkdQkkGOkSJPsbLuyXIayl/9/wNdbr4A4njbMOANbTcO3PjO3wsWsM8RMQRnm0efqEdyggPm/2jSMiyIkDL+8rossnD3p9Lwn4BP3hunxVWS902Ej7IURH+kIOKuMSgaOFRNt/fx9mOiFUP01oJSWqkKY+rx+TjW6miiyIry6o8GlLC6vL+N3c9257D4J5om+oA3+qFHG2Y1QUPnFcJ0BTOYBEa/R1hmorC0eckR+04aCl9pP6f5Az4Nkxll2xNsdJ3CsnU97JVine0YKV5aaPy4of8hdVs7xtY7KAoh4lmw/REAnoM9YecIfmfJkXUp7P+DcV5V2WmO4YOWjSuYNCV07J+VF5F+NySal+xzcV8MTrhRdmWOeD7Vq+y4WLzESn2Fdp2IcXt33ldYPxXlwwie7xUoX4jo9Cm+ffS3DEtgpA5BefqMRpnfQ5JPKrR/cmQO3XR4ZLnF/mfRhB4vlOky1LO43eNex8xR36mLzwVG4EAU+2ius6JcoSmjIkwiofd+B0UinjFemN//l2c+FDg522m+QJMZugS62WCdW3BPFFA7xrB1vsUxilHnvPOw3yIu4QqSDTdCtSeTScYU25BxnVcbnEwSMtX7Xc5Yx2OWY5A8nOfkGI/ObbonbBMjMH9eB8Xs4e9T3JukI3au4Qot8HtzQjTK2oFf2UVdE9LCByMeOczwBNH0vihfDnO8v3Pcqb3DFO/vFBO6psm+HebH2DCgIbSP0caeUoQ4Pv/JbFW/SIPxDNSjJLuESbE3SXG3fFs6xWmW5dH2+WLjLIsYVPi9PDDUlH6OrNH7JH2Qxgjv9XkrzN6flizhosngNage9oTPrIJ8bE8Ni3wRlFIM14zTmRinoHWx/paflmURlCfvMyBZIco6Qq4NjOs0X0TCdNUkCayqWP2iCTW2Y3t4y29PpND7ue04r37L3s9xHYGj5rb3IkFQNSXscpztx3Qbl+R3PksyeVMJjewJW0GxsV9iDhz7KHWsrW3HQx+Qasur/fGDH9hoWjY6K8pmtY/nKdSQoj5rMPsxB5mxsE3UdbFOk9kSejk4aEmy9D7R7Z4wZvhB31lZrPzZkK0ddrOt8O8GXTcsMLq5/SdMDDnwPEK48LW9OBs3fGMPBFmi51Vnzh6/tA+eRkI2PL0Tgm8KVUBMcfTpc7EvQT/UiHArHtskHsGOn95bPmq8jSxJfDz7wZbjmspG6/69B1Jnv5hDFMBqDdL7fbGJI/pF5WuuHzJ8rWB2sZc4AH3jiR228MSw/bMFIPjI+HGSkOcLO9+gmENZ14Llc+lGK9LnIo8Vc0+VJ6YlYSJ7xnkq4ZCdseQLHFYHsuxlBCH7Cp+qzxAz9R56OeSDi+rvkL4aamUY4s5lQuduCMKbbhvMZgigRfhX95sx4QISdQGR7fRjky1i4pOTJUweimbvMp+MEiuwlTl9yKs2GL6LbIyB87y6Bs+nz5AaqQ8ahKQPqQ1beCbMWzR6pIe/aO5TwhphcB4uCBFF9JzEwSrjkNIlrgQ6vHqUNCVOaX4BXjBB9uvVRX507tIjYojwDNbWHy7xo94Tdjh5STLIJuX1mgGM5gKWaaF+X9jyiKgGGcEWFEVHos0s3tebLCYzQnjReZ7WKcg8H29ka2/9xTmSUSD9F0lbRdreE3mTxjRbTJ9Aj5sTtJsomnwRlDRPESocp0N/9ekQySShvmwSp2d/4bwHPvbZ0NOxOvk+Wie9r8M4rI4ZkY92I7knsnrR33N+F7IUDVjeT7/rtN8xlkVVIds8O8xgnBl0WOj2JUIBjeQzfIRZmAF4tSzK2vatcrvwJwQe8/Hz8/+/vSt7jttm8v9KKo/7EO+++9sqWT7iKjvRJ8lx7b6w6BlIYoUaTpEcH/vXL2/i6G40OACPcV4Sa9BAXz80cTaO3a6S7z2lkGcqWOnPKgsBGe8mGalCqshzkftqb5GsRhzB3ub1q6d1drNzpk9tervf48J2ZdrT5ZE70abUuozIE2QLeRN3iKV18p/FlytMG9Ccj74Q+7cHulhHf1gfy8MuPe3F+8NdUjUbDzO6aXtLN3FR1AfNqoLy7GXkvjEvet4npZkAcVJL3k6AfBRl3B3M8/OEeN3geQ8vwG36s53HYya3ohpl5VfF52T/KM7ay2xb+N/s4EfHtrnPeXzsRg2IbPy26udXFR9M1/JVVofYM/tlnmT5mem+G3XqRx/dLc6O+tVHpTkZdc4HuG8jMr8EIH3PC6C3fzkUZm4TDKnqeUvHsvyOc5yxaogkxb2GZyyAuDqzY7VVX8riu0kw1gzhyeZYSzW5qT4tT9n+ulZ1WudUG2J30ZbhJK8CHN0sazRw5h2LURXHzjrUDJNQvF3u6NdhJvi2b4Lr1Y7VJK8qvKYt7JzrSVl8NwnGmiE8eSPy56SoD5e0mYvkNEtT3Kq3x+60Et9JPgYZO0ZFswkvV7QGnaZBr6sesh+f63XXzny2t1fQo9fv3e4rf3PWC+z4GIkir5hN68XzDmlQAXr5J8nQVg7o0VvxNRHffhfp8eGUHiZ+gD0sfXFOvXhxR6vwZIf01c8S5XNcdBYPcGD4VZo99hdqL9WXnX6sa/qsdbrKZjdZMeEA5VgzRCet74P8LL70tnDoGxy1E96XQs+7wrzO09YMEsGzNP0rq7f+Fs0UOkPkrhS9OhTfpuymy3WDf0Yv1wPLPi/hLza0fvIWGur0X+NjVpPe1mvHAf8jiua0tIem/sgcW8KwflUU2S5pPNtx+PdJnMS+vUlclvHuqY6z0dvEmOm9Oex/qWcj9TtR3w5pNmbAuRPpw2/jjx9PaZkc02RX8f/Xr//522//9aveZf48tOetf6kPbtcXV67jYhfvTXNUauwxMUDJFZkQClXA/zCYNmdj6u3ZOL3ODkWZx5XBzV6fHHbJMU51o2iEzABR6zo0qZe8Fsd6t+tQkppzGNd+hZkPPDQH2Izx8oWEKTbUIvnqOgo0hUh2rFqg+jM82jCMBUIWbqrg6GKx07MQLI0tKYyhqpzn2J8ioLnCYLGYJtVcDH79Wlr7xG/U/R/FXF8u+3b4bRasqQKrgmhFQdAFWigMsjR9OBylp5pXASdwz9twJuTFnwVOkIXWA6dxqrUInIajGR7jUjOu1303tjQcupGbGn/cekyBzxRh7l84mgwHSq6Olcnr43ed+IkoorvslO+IuV5XV53rDT/OElyMg1yQLIGxNag8A7jog2sIT/pw0+Iwu4/z+piml3BxwTBzcvkSMCMOOC4Gs+7zuqlYBg3ZjLKLiWQuw7M1BrIBYZYwtvxAfHl8zTgUnwIv8sDKrOjq/9GldavXO/STGuuMXZLAoDhK+cXEMFkrlzi29CJCVHk++SryH/f17VnUtzKR4lSlwGkO6BANfeECVTUMNpy+a5JsS2NiGMUhd2PWNTDXhIbwdEHDcl0jl1H5anC1mRXLBUE1/5ql26rVwouWdQ7xh7hZOctnXLiU2SrNqQVbX8BUtNnCImYHRwUVuBLnOjRs0EFFAsuDQM3Z/34ikDNbucKqsLeZL9wKwDb/l849vC38tdMuht6KqrWkzqDR3tRk7NSoLSie1otmgR98zRcXa6AIAkHNBDMgkXHNGWHMude8EkzatnUAryN+/ilQ6IKCxeCHX5CfC3dlNU+p5PpaqSoiUPR1YUyWV8WWWrJ5TCnqOGBpMSj1t+M9gsgyf9RyTzYtDb9tHQBgQoeVu54A7fl9eHVgcO+iM0JCEW6503D9Df/h8UHb0LqvoYaH4cdZPjJ6ThVIlMBxpld4BlSRGWQQlnSWiYWQZRkgOwSIi8WVi48XgBWeWWhuVL1K0rQa0cNZlSZhynaI1yXubRcNLF6q8ZdHQ5dcMoKVWOnHS8uIqYgylF3Mx6vXyOXjtRiu2pdI7sq8yT1QNAOiqH9+BgXXQCB7dPxxFnBBkqvygARBYAYbLAzMQLU4fOVHhRbBWp9gZRv46qVVZBh/3DyOBlU2gZ0+/8o2sNNLq8gw/rh57AyqbAI7deKYbeCmllTdrG1+2DxeGjU2g5U2yVBkCu3qqYAoaWU0JOh/DrOfz/WiB7R0inC41eTL4UXPDitn8LSuMOq1VX8ahfOgi8r9S8mnEoZBoG6SOdDIzoWM4ZOV/HgFYLXt6mNAoBz/E4LUGSBLA9SSd3nu9U6nCLqqZS5bB5kRfLMueE0F3OrAtqEIuEbQzRXxzgbespHuzff6Mbg4vTqVT3WLu6ZQ+ySvOuJRGqgZ+0jCzUdAUj0XQC6GxbdZfnpuFmV9Aw/fXxx4Ki1Jv24eF6Mu2wFB81rm3Chon+g0YND9fBk4AJ4hXTUQoua/7/LsdERRIJEYzut+nuVD1DA0RQgEHcwwAcHDYjTKtYYQAsjt7rGQeFkg6PB9OW/EachXMPjAxD7HdSEhNPvoxdGvs45fGrEWA1HzGEGAQ3LU2EV5sbxppPtl62fjWjU4jFSDL+z9TUya5wXNnMNcPmoWH+H26Zk+FfGj+D2ppMl/RHBeqZWm4ZIlB+VRCS4mEZeiFofv4pm4QKwBfcU9RvwEEOPHlKXw1Ui47IevORG3ZkQNUpoSBDwKNxt2RkW2BJj15zBZBjYz5itxA87SWUreJQ/ldZzvo5tT/cBaIfafk/IJ0WG6Gy3XknoplMbGH8NFErafzgTFoAsLE6ArFoeIMtaBFZrq0kCxBpIclGeGQY8TAjyhzXnc01dcF9Y+yV1hpaOhNUFttjHSZJwpHl122PRHVor1j7NrKU0J2l+3jaFRkfWPs2/FtwrtN1nVQNEHp02sTwKCK+KA5Ztfu4S02sRKJoQzrx9By3B8PXCZLQxNxYrilmUT+zXvo675S9YLaSTrC/iuxGwAGvRY/2esF7VZMYLlnuq1wMgxFhzUgmB5RtmO9QQi9rLEUGHZTdo+NeV8iWz+2ab/VTP6gqOV8pQf6sd0RICzxoHGw5LI2tBGKbmAMbCszyZGv8MFnvmjyj/psdDwsuTYNmsEuY7zdsjy6nTYp4Lcnui2VSRKYMdLLnWCiS6RNiTRC0PuaGG2CAMgQzcOz1HE9UBoE1+pNcBszm/VJHQt/rkysLX+Pfg1AGvGHflJuFp6Y77jf3cUu+Shu314VZZ58uVUig1gjJQfkguj3Dr6aPW2AEVY9D+P9Z8RRy859NjxwANCqNBI6MqQsicMsxI1HULnRlBKWdZkAmxgpcGVr21o4PyUsflssC0esCkNlsZ8P3S4jx/tOczWMooYZYZkkUsvZLQgqeQwQlgw/RgALkvGHdqngDN/BohxHb4kyiryxYH2V5wn8aEcYut19vwlOTSEEf5kvHwfRiZS7sEoBU7LgnbZIGiR5GGueKEGCoo2SlGOFLLYa8bfZubmqwfq/N/dczG69FSdAc9/n6qSykufDgmOUYVIxoJacJHhETfQqqEni702/G03JnIgebHRb8shr4ecfUnSIARcPvcy5GrROOuyowdYrmatUVfirzg9DSClNQyDi3mR26jLEbMjDInhSXgKA+VWWwc86w0sDevFh5OcIcO2B4abG/1Fd/HzMRWvs2+HNIvxK8IDgbL+Mvy4cjTA6i2PBNX4i2HhPhF5pd9OKHnYreetGiJu/vUGE0G+YoP0iizSrxeRbH3Uh8NMlm4FsFr/lHIZEM04bXTDz0omiuNx1O4g8fqBZEjudpp5e8AyVeIwVdy5Iph5BZh1CPQPSmimq8PHMJd7m6Rlne82+PWGucKUphktpEF8OdcmrLryo9uy1yjKLK8HXslznP948333FB8exW1ciutTXrHY/SCG9x2BOrTvf+QHuUYE9Txd+0ugATmkVxiQtHrwIhjqgHVAo/njH0wsgAnF8su9Bh/v/n5/qGTZ/X13+lLs8qQ9K76JW1eI8Opb8RjN5u9gYZq5LBCsDnfrn+GtD3QzjuPPwdzSo/mrXQo8uOt3mTMQ5gbR1Yvz468XscY56rOZNc6rh4ckrX4R0UxvngwM1YbGX7ee+GBUhcNs6YQqzRM9QQdOOBQM3uZTSBc24jF12sRYp9LvawXojxVQ40cRvc2z5/lQojLXxk1q0ebxoSnE4Sg7Yy0Auc/+gcdK4DG6YsFha5l8rUz+IXuMpH/XjsIHGxqdOmrUymYawQ5cMWlCjV0om4UBmqwUaxSjirgKqG1iAWg5VM0Zt1zhtHjIun4Su7+zk36C1vgZx5ZBqYDMLJ0FbbBatGghz8ha7BkGjIiGLFzqVRfMg7k75ZXGjzfxjyYX8/tDUrfrKSUzPtzSGWsJLfXCbedZNvRh8ZQ8sRp89JndaY18+TlYJlVQKVK0sCnjpwHELyhdMsjrdRfD54dsF6fJ/4l9L2P0IT48noxZjeT9gUB29/jjLAg0xFaFMUuDoA42VRi0mTpxmPYSLogv3xOAcOe/9bH/ZYz5NzPW73b1bsXXRHz7XaTHh1N6qJMQ1y4ZG29WPQdMVb+X9bDrPouw+lbYdW2A6BvKnI9kmnIAO80AEeMGzFmY6zWaZZmM4xEn3kviUCmwnSvoqFCfL4Yov0cFIGOsEExK5eWOqqTZ43X23MwTmUFNqjJ3HJNZK0dS5N8vKFqhpnZiNzOM6n/fZPorD5oXGwLdhe2P8xx0ckeSpygFm2cd+OllWwxHf4hvhWM4kqrMHY5k1nKTyu8XFI5QUzuxmxlG9b/JSywDge7CGa+sTECSp3AEm2cd+OllW27cnaXpX1mZHB67w5XceZ9Wb/b5ns5fGZUbhRcUomjLO/FcAl/1D1eH4huxMiWR6F7tf57nlt05GPM140PMtSJsjSKuY/Vg0tLVoutV/AWLbUcuT6sXswNq/bdaJsJpezdY3DE0772VN01uqLpTVTVE3i/zZ3vxNsmL8nVcxl/iwtznq2vdiRLIMfXrL20pmsvrbvcknuN//br/klWej7+kRgNGFNK5iZPYv3mOk/SqLOPdUzsYB9iCdDB/kNQiyJiRyuA9FkHsxlK+qrSCVrWsrFrld7vsBBpTLYaYqRQWbt2W9kdRPmWQ/bRyiJ9GYmNozSpgymCtAoplreUqKUc0pixc5jhLkpG9xyQFBrCxCOwxQ6mFw3VcisfmDInBYSyCOIylNhMlu/JUXyI3TdSXgCbqC3keILhoBIQ/oo4o+hg379yx/VM/C5zkAgmsIBXlNYXQJoLyzILJWykGmSoUPGsTqDEoKHv3VGyDdxU/xofTQ9z4iohEKhUlh0zJlsUihJ27SmHrqPpkxOyvOgXYbXUiJluCH83IymG4ZmkwGEqg9odCmwJ1h4LjW18Cit8XWprvH3dGv8w6AcRMp7HxLKtuU0H3azWohvq8Vg5yVEksDMetLYPXWASxGUstHMajbgaHsQjiMJbaONRnvcRdmTerH0X7XhfADSQDOYOUFinGVXmD81gEcatL7dFRWkIzg6JUCMZCqZzBB+GAt80ONe3NfTTctMVUyGkpbDqI/Dkpimp+1y/wmfoYJKBuBpVt5vC9GtYe4vTqVD7Vc9D2dTtUCpocnFmQNSzSNbd4kc4ulUF8m+IiYnX4hvY+OyY7jE9XSDDqKDicMCZk+x0Fp/13eXY6Yky6QoJTR2Hh1N1nMJh0v0Ptd0XMYeyn+ublcKgcHceqZNRAVqW0SPEueaiGjfneIgVMBkkBUzKlIDjT3HhuRD4CUhnqTtbEtKH8IyuhSCqVoUzaYguTW/GtUvgmSw5lgTsMpIIYg4SM8Rcy7RqLsDEXa4LVEyIeU4spTiy/3YpqOnCoJ4ACDMFaOWxGhcSqnv5KPaCiTgKrqVPx5nLYk7jYrA6jp+Z3yPvE3Jke/dKwaS+SHLQdWWOSdGy5XCTi+rR53hNzYFNIeKsp5/Eh3zTD+JOVCLnIetPk5QtpWbfS3uhxXU9hCMSTxBkpyKs1XLN05A5e62pYpJNyyxuiSGUQX6nYOo/PwBXL7nd43p4x1kTH3J/ALKovgmdQfSlH9AFksAZDMaoIF6X3VX3ROPILODXUykGvqCS2BQ8sQ6C5/oFRgsshGLHDSuO9eD6mMdhTYTLbyuNIydwqICQwSaitAzbn/sOAMzYoqO8Ll+07cRBVR6aCo0kCTg0MKtua6Jg70FwVHcvAddGx2MLkU44zkcogJlKxLVBUtfenVNzHxd9QoFCKwUChUNjsNmbaM+02loF2G4ttvVHERdVxPovk8QkaomvlYP9TSXgMXyfVLKKAw5BJQrCVqDhrG5YICNAQKx1OcU/PcQV0e5WAWpXSSK09UMpRA/RBqRTuhRIBn1Wb8oli11JYWLZE1nD+fIyTR8ilYxEcvvtSK24bW1NfLJ0CRq1GxFhj/yDKUuQW5GKE2Po7RGszspkMx7S2SQOa3SRzZY4NrjFClhi8ATWaugJY4UAo4bUOhNhRHIYcPAF4+1BqHgWDNUCD7kCpZFa16x60x0+t6ASwyiqNDYV5VhRV4ynO1SQBkWdQWU0NxW40ZnNidfWvEpny9CXgIKYvtE104u/E0QqlFJzkyAQ2VsjeC7Htwttv6df+CUVMEmoTIbo6HtNE7O+zjj4R9q3QvjIOOoOCJ0NHbpdA3dTHjyEgdPZzAreiqEJwc6qYcQyi2xvFT1sYFNQWa0fEcIS+PWrZ5aXJOfuvUU3KXgAbzsPwNp/5m9BKGnvX9TjOMipzNTUa6VylANIeYMIApIRMADXjBMpwjdBchJFLsXMoAwFj+IizUkqxgSKXlXnJyLSvQYKdr1CpXDxscyvDlwZD6fy85SR69Dap+5JUgTqP3lLrB/3HmxL6GfOKOX5+XKlJHpNvmuGden+hqu5mlkg9FU4aRaXFFYNOqjf6UCfQMdNgBvFrBsnfCg8uSORKXtVaCWLUM83RcLbZtA9CiSulHaRu1EDOYkNXlMDqWpF39fsRnF19+CYTdNfLlH816g+npgm/GzT+Pa4P8JuK6PzDXU1q/B/ddectTc051Yjvh3bjov1+ILcpwJqgWczCsOa5j/NHAfYITjVPPl+ZefqZmxN29EohkQPFHqMspGFcUKNX8h9SFzILcAmnIE0CVgiJE+C2kdIIdX1o8sc2Uu8JoZ9alY5QBri31GpB3Eeaiqvpag/f0TE0oaqbtCECKXIFS1bfYxjVVbOPtQzSYIOtmUygXA4jhlwgnf9hF3TtrKlMXieb7HntapzMAoUAXsezWhAq0IbA8iBmsncSkDxYR5nZJOoyddSvU4tucZsevvMr42rDl/IajemrdkQr2gI+0Bh2ZTCIAfGhGr8y8XUClEXUW4vJ5AuN0dAuYB6QMIApoEuarQnIu5fuqnd7MZTSOol/dbV9paYedkV3uoqa6XBFVULvXppR9WE/adhyI4InTkx4XLvh3bocub0N1oQMYJSFMAQeBHFiL35cgxleJWlaBdKhZcIIGmk4E7AwdIbSfXq3sWlca4M2ZBfQsjIoDWCpFtzNAF05H9+WAWxBV8DV0q/jN/pgV+3VmsQ1+7YVzrV5d9P0mQZIc5hEIUygZ0toaqKZENxV7RMFkKqaRCFU1dMZNDXHH8+fXGVpSqqpEoRQUc510M6ZoFwG01Rr0y1EbYuwcjLJmUIaNdRUEUM9JAnEBBX1kznykRhqGMOrSCiH5HZoVbTkayBbgo4Jga2SuSi8mxIfCPEq2j9whsKUgqs0IXgwizWYdkSfh9GEzejzmIYxvJ4dTUuZiMqpQg5HeRVDoImTZ6ZpzSlfjLvphqwxpJ0AqhBGMdLbNFWlX/2o2xyVtuurkQVTWDnsPWoMH+ieqHIk57pB9JVpLCIrGXVGkcFMOWZts6Jfz3ZNkm5tac4WdBlvNj1RbpbqsRKdR9Fn7rdN2hvGqgpI539JRUn21NSCczlNVZSKTRpFiLAUTj0o91Q0nq/AD3LAFYgTDGcf5IAyXSmtkJmrPJmmszvTLuDz5O5+XYU5hmxauA10Er+KG5nAxnqeFlBG+Yk9aZPI/050eFX7/GvRTTU/e4oLsf+clE8SB1NxWxVv6ih19QxzTVU0e9x0Qyj9dmwfNwNcwZMiYE20x7NS93kyzSfZ72z7qLX8xoWlzDNk/rMERIkkQECUsxaO9cCEhO4qAjkGyaEQSR9iYETkVGwa4SRJ9GMWa7+wV/KLjmVM06dvxPuERuFXaT3B5V1/isbT8V45O2U0totrqRJ6EhysaXxfyUyaUydC/Ykk65RPJ9zSnE/JCWoJeCBlmFAH5DrtejKVwvSMBW6Gs1Fa//4OfWxCT8kayS/bQD0cJ7fOCMz3d+S5Af6kjtbz4WS0Xe+3JJj1YCCqZ+DEITrH4qYgJoworf9549xmIJMMMw52Wyr6NxAri7LcGjOz7wQEEUmMkQzMILgmNEPgx24XnkEYraoJoYm2kcTOvtFKM3eFMNnazKZaaQ8wU66Qp1xw6mBhwkwsI7cglwYxBnFOBae2KoVoA6ixDpMQicS5tyzZTRCr7r4uYDKSsMsWdUmjHsTE9o84p3awLro1c6pv0U6yqdIEbiDo2dzGFNRzuFs18QSYzo9NjrXCm4gcTrLrWpUnteaNG1dpxiaBa4SyZZuUbmcWo3DaVZLgUo3DiW2nD4SYgXL2aBg2gUJ0Fz8fUzGmYMMV1yiJocvkJG9hVR6e/lDOxUIqI5T2haTpJ2eNJ02a6vhrJeeoT3y7TCL/X6vwqhrLmt1aqf2jjVYJ9tGeujLrxSxOBrloU2jv2lvW4l2qz7M2b5OIbtog9nDjP2sy+iXPcf7jzffdU3x4FLdxNTwfHjECFhetlagQrL6q1IVf+MUkdQ1MfuipXewC33E60wjNH2ztVeptqY08pERu31jrhNjFsTwk1TTEfRXKn5mIoGyr4j80L2ui4bkk65ANoQw5ZDOegWp3ltEXniao3796FBE74iaR/61w4+Wmtib6KNPEaw3saEFQh4gT6ENLd8M1B8/AV99Dit7m2TNlD4o8hEHgp5+6oEK+5HS2Ke4zB0NIxFs3g/SAVGQ8JgXFQ5ycCmngY1ddYCNfsMLawdrwbBIKFCBdCDzMozb8xlMEvEAFHJ/i1iXMgz2a1drJ9g4W3Za5/Md80WrKoTv1bajo/SEpkzglDtRSFXAlpx2mhV/AumvP31letTrfGP2BYZOV3S5o3UDqki3BJ5F5z5JNTAEkv/llz/8DU+MKnpn8B3rbrG3C+lzZFHPQgTl4QNYDsZ8A3M2bjHdyovq7eJ0dijKPk/oxlGoYNujXJ7y6zyKsPrHS5J+X3dwTsmZBU070nSJ5Dmp/c8ijm5QCJ6OrNZmq4/qu2nzSq0xcsCkPOQGrF+e1GB6xwDtW7QIH8UDVeYYdE4/R5hroaOHZic0WUFt6eovrfeW1LtNAZ7YYHk/AY2VNG9QrZOcZdszuRptroKOFZ2ePW0Bt/VU19hfReI0N+AD4anuGryzySl37ebC9POfB7HJSOoYlJXJaKaf8d8uaRP4ITxqYWQch01qdeYzHH+icbWT7xrVGGGzT2qPqL1+0jdSGr7ws8qHs5Yv2jcfuh+rPasJazfc+ZnuRFs2vL1/cnqraz6L967UoksexiZdVmwfRvEY7NtrTvD88ZP2sT5OoJ+mL+0c3RBnv4zK+ysukfpqgKt5Vo87m9dJmfaZ+vvCL2L8//Hkqj6eyUlk8f0mVOeXLFzT/ly8MmV+29w0KHypUYiaVCuLPw6tTku4Hud/GaaHNo7EmrivrvxPV760vq65ZiscfQ0t/ZAdmQ535XoujOOyrLncvno/1K97Fn4e7+KuYItunQnwQj/Hux02dJL1ZbsIasTtCNfvL10n8mMfPRdfGWL/6s8Lw/vn7f/8/PCXSAiJFCAA= + + + 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 + + + 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 + + + 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 + + + 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 + + + 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 + + + H4sIAAAAAAAEAOy923IcObIg+L5m+w8yPc2snZFK6m6zPm1VO0ZSpEQ7EsUmKemcfqEFI0FmtCIjsuLCS4/Nl+3DftL+wgKISyIAxx0RkZmVD1VKBhwOwOHucDgcjv/v//l/f/2fz6v01SMqyiTPfnv97s0vr1+hLM4XSfbw2+u6uv8ff339P//v//P/+PV0sXp+9b2D+xOBwzWz8rfXy6pa/+3t2zJeolVUvlklcZGX+X31Js5Xb6NF/vb9L7/8+9t3794ijOI1xvXq1a9XdVYlK0T/wH+e5FmM1lUdpV/yBUrL9jsuuaZYX11EK1Suoxj99vp6FRXVdZUX6M2HqIpevzpKkwh34xql969fRVmWV1GFO/m3byW6roo8e7he4w9RevOyRhjuPkpL1Hb+bxtw03H88p6M4+2mYocqrssqX1kifPenljBv+epO5H3dEw6T7hSTuHoho6bk++316fM6L6oPaJ3mLyuUVa9f8Y3+7SQtSIUBlU8oqTFYkr3hUfzbKw7w33oeef/m/Ztf3vzyb69O6rSqC/RbhuqqiNJ/e3VZ36VJ/B/o5Sb/ibLfsjpN2Y7jruOywQf86bLI16ioXq7QfTuc88XrV2+H9d7yFftqTJ1mgOdZ9af3r19d4MajuxT1fMEQg47qI8pQEVVocRlVFSoyggNRygqtc23hP++TFOmbVKMh/+8wYH7Gcvn61Zfo+TPKHqollthfsCSeJc9o0X1psX7LEizGuFJV1Mi61dOMAPddP87zFEWZNZrzsplsXzwnBcJz8I9k7Ytow7oEj+/cYBVTZOr5+bPh/Gh4KSrLp7xYjN7QtyJVtxGikU9Ypd0UUVaukpLoihBTAeHsR4KXhlZ1wWCWbZ1hsb5+KSu0wkphOfqcnGLVmx4tFgUqS1SOPz20vev67p8oriYaXBzn2CLw5QEiJckjIjaEr54gxkOZWmO5iB6TB7p6wIvB61dXKKXl5TJZDzhzo5lue+CzIl9d5Smwdncwt9d5XcRkuLkG8CYqHlA17PGvbzdGgoHp0PfL0W5o6x+MBnlb86z2Z3m6QMVMbWOOIL9aUo2/lBb5Y4JH2yhwzZiNNeo85hXZHS3qFPf6Jip/+qpPPBN4BigJpATBP8PMAVlc6DZn/KbodOMt5n3y0OwYR27y6/19iSq/uficrBJPFMdRFS+vk38hz0UViwrRab68GnKhP8lX6xRh/WpjGgWY2X4bclTES2xpeG9rSK3aflMjNTM2634Z3tToLAitqdHZJKa9btUYIkpM0e0e/QCc7zgEJbGSQFAvO2nYNWsziVQr37BIDraSvC3N2v2XcZZu6m4M0KxmZUdxni1KPzU52AOLPf3rdts2Vb7+mp0WRV74orpAz9VVnX2rej8UtgPQTbLSL8NRiWUPC6Bj3dNs4dpqHcd4UXNtlyUbbMuH4NLz8lOywNLqO0H4zweyhGNbI6aO6QHXm9X9gv+LHjQbGPtBG6v+v9cR/YRxuXjWPyQFtojz4uUNi+ig/hVSrVP/Yyi3D6iMi2St2boYtq1pKinXafTyOY+jNMBAjRr7WuCtkt+ic15+QPcR5kk7pWAhZ6juDP+qiuKl41FWqy/KNyDCg+DJ22II5ruRIz3Bk0B4fCBS1OgfzogAau0cVrrqA62IZ9CJn0b0GJ/EcZJFxYuZT8JLO74fx/b7gu0TjdUZqmXpbvJM5mcHBf32jNv+yqGEXaQCFNpFqjrNoLLo+6CWbgjMV8ORsDW8tsUf8qcszaOFk5peJNGbDsFBLSv0SEujj3XSt9r8tj/76nAxx8HOJ+4iqtE8r11bnRoNrVRP8qxqQwdGHwtRI5rYgjANnT7jMZVT+OLPywv05B9SQs/vE2af6HzIu14QSfsKewaCm6uBjNSDDpS3dVkkeUHhvA7C8Eo6vWORtBrIpalxC+bTD+4mn2RoV2idvgQZn1E7k4zp5GT0Jo7jePQ29AFFgbZgx/niZfSlrDkJdFo6IGc/timKBHm6+wkaSYemO5rFnVh8ibI6StMX6/VZuj3abErgc83A2yP+pNN8Q2U6IJbkuhHdDoHBgQxgVNu7IaBfUNigW+62DYvnYNxoxFTlWfnLX0ZxeTeuYZ1DybBxTRRvXqpWiJFGeJkXnrE2BoHgY/VdHxk+UsvNOa9LlCzgb2iODPCqSmUB1xrp9OAyoqEqX1C1zF3cUW398s0A0UFrqRiUIdS8QZfPcVov0OKETjoqyNp4vhg/lqNvlyxwxYu6yfeBYvjbNq+xRbHGbTTkn2K07ShpB4gEUJ/Zs7dd+SXJklW9ogeUR6vG5GitXBQnqwhrocsC/2rvMf719atrcm6KuUmLOXoeCXOD7QqVmNY02jUQLc4wuNlBtNONEHNlWuSLOq6O62yBJQkLNjaOi+SurlATSuygX0+iKkrzhzc61AeNK29rQzRfRuuJHgzR9yitvbGdl1gg8S4JxZVDnJl0T7ahG7gj07HkLVt/s0mzqCbs22zqem3lhIZCiu5BVuVttcTylYiG2IGQdQFofljwtjEOuZh2+NoYQSbUzzksVGuGhnB7LmmMvMliHezcELNgrygsN1LAljxZoJtlvbrLGO+DK7LveOLvUu+Zo8qhXPoHGYcLewvqEPY4mNR4UdnVA3alTrDM8e5VlyXSblFvWzAb8C1XRzHIIah+/ebgbaOE7IZhNADzrqs6bWto+JsXB6NCoR4bEpHm/K/UF7jNj0Ver3FHwtgX7SJwni2Sx2ThdlI0zlJue9Y1+dpuvPEP097RYpVkJ/lqxQTbjHlTl3ItWuEl2X/LiSfn6Wv2KSc3y/1txQ5POIPhC6oirDSI7378i6uksSkZh7R3k1TKewthRnaUpvlT79FFjwl68jZ5j9brIn9EiyusjbOH63rlN9MXeRUYY4fuBi8YKTdo7z6GQ9qGfNzkR7H3zoFeiCedo2uj9xRf/6zHF7ooq++jmJggBV5Hqwu8idpojdGa/YhZbPRGzsuPyX11EhXeW7AOTwhr5Qr9XicF+lotMcWb5cSbU1qcG+NHpa0DhTAd1cQ2rpKY2EZHiwXXB+8xnZebiPww0d3emdSytBHwDqH3GPGsNAL39V7A6ejRatGcPq+TghrqH6IXHqcZiqO4Sh4pihBs/ykqryOSESPUrA6xWd5nwr0h4RBHDwVCrOHoER6wQXaDnse3RM/LKxTXBZsKx117tIhOXuIUNZ3y1XEsxktUJLm39PU46dpPEXvKynlJTp6DZZE8w9PfHWV7W3eLRUIkL0o7jCdL4tIwd1Tb31iInk+f8b7GWxbOy9PSm5y4Nyd4g/WQk1gEz01MlOFN0Xn2iEUTo+uiDDzNxiqPfwY7hCDbNYrx6DFKcN0kZbB6+o3BnjovWEkWcOSf86dm1GSlwdi809DmVXL/Qh0CZ3nR9fEY4d2X59FVFP/MyXaapG30Pmcmu0GC8byhJd6B9Jteb4uCbvrbOJQwk9RgbOJPwmCkBECLFlmizIkVyFjFskA6eFy/HNdVtXEueMgWgf6RlMs0KaswSFvhTxFmXqzXB/4b5+xd2Dqn6JLY/0SLRRJ8BfqaLsZtoN2YnDDhu8HbuF5jPFFqORBznDTPT3fK5nCLgsV1mi08MHXurNOMnNIF4a9WbQ0wo5GZolVsk7aJNx83CWpk0lvh4zUUlVV/GHmSr+6SrD2XC8iEuM9dcEN5tF6nib/F/AMlD8vxRHG4jwmO/keyGBH7p3Fp0680vvqkR+SnTMKdWISLukA0eaf3+rs9IRfiAJNHVLyQypbeEzb7l2XV46hE7eoTZNvd4/uCorIuUJPZTGpOBslD1bcZNhK7R0t+DFGbkZaGQdATLY0HKox7vQ0TQQWJvAjl+BggJdQIjfN6mTf+lKiw93TIo5aaJbFfIsHol670toW+yTe+8k34ixxKiElSgNqGILHaQBW6czsEFKJ2BuWygJ0hkGOA0SbqxzBGzD1kShsLJgmxshxR6/CiG2L5gDq3mCJ0igeRDUCAc+w4e4io7DsLqOg/BCYbAwjrOI7LhOJRDqGFUfSeg5B1nAdz7HN/CC7vcgOi6PEQQNZhDsqxv3QHep80CfLYCGB5/+EqivGoK8jGp6nlON6b6EE5uNsBnKhPmWKpOmVhHLXp96hIsFEH7SOV3VfUU0yQQS3ZLJlUdZwqHrXVwC1GaztE53EN0/0qeHAIKDIhWy7lwgGQLRvyZ8OKzvKgYneHENIOc2C2XWZ9OEB3+2KIM4RCgRdECGj2zbMb9jddHLI+t0Zt/+sQ1yxvSxPlO1JO025mQkRqfCvJXinQ/SX5jajgTq2uKdtdufXJb+syc0li1HjJHGq20VXlSV6vyR1t/9fjREwe72mZzQyNj6RS5sujG0yEeLZxILpt/E2u2YqJG2+2gmovv4ETliUlsO3iBLgYxvBI6McgOC+MPRItqpbz5Xm1+jbhGsA4IED5UEBorwtG3b7b44ZRh+KwFMvb0izFI124IfdfArWsc+BPd1fiOK8wn056rUfzhFGYlakTo1AXeg4XZgI9JU1u9IUKAGw9bZYnVeQmlf/LhIOrP+VN3mQE4XE7R+S0eL4Og8c89h0GEUBXUfYwwTuGge/GhY9f2OKbQ9t2Cr6XKQwEUtH0gN8T9PRFvaly1MahDx8dtiz6A0hgf2PshW3UNNj7zUlVB7TpMV8mWPICgF92gK4Hjs+EtPUPhrtCfTUkGuuFDIMHgP48zl18lBu8mPGnQJcf9+Ydi9Ant6IKgSG0J7ecvnFTJwOc/jlHDuplqnxmsm3FdLaR01LqLA2SYzSZ0FhKcNiYC11fQ6QJAvyCQU7XGHwHKVbKTZDHIACyX9UpMsoCHcavcr1Go59aHSdpSqIeu/TOnrcfu0upYdB12YfpBmKQ+NrKY7NB02t4lZsizOQxjbJJxKdsuJsNvRcodMutv8Jynk6fsaJiXxSdYI+8iX8Y7wRJujtWnje5rT2DKFyPh7xZRIfVRt6WZiEY5yHvkzzNi0/omSaknrz1nXjb23afFeDolcN0kBp5W4F2WqEOgs7LMxSRiegMhBn92tKVasOhBncANsDySwD9D8mmSAQcYQvncmtB198gu7hL8RqBv25gsR30w+j6gSX3H0JHDLnV8L7NsJL60s3gD4kcyiuMoD98bg6Z9D+ILgmkRA7aY5vjuyaNuGE44RCitF0hSoegoh0MKjrE2exenI3TKddwcRfPuKBywVAAgULsNpqrtP77jAZPMBvhFck80bZPo25aFUOfBMyq11PY8jrtHcqFTQg3SaLWJp26H1E+oXR9X6f/hUqa/DQIsovcBZePDW97D11it0tuq1t2tMHS0iJDZYllVZd6Qahzy8mzZETSGuqr9vJqXmcHnVS7qJ/O5uh/HfYn8rY6Gn2sk77V5rdDdmlN0FigV4A1r7eHacXgOfCwDZ3lxSryj/dpsV1HqTrFVJDs+lO+ahMw+fPR/X2SJgG2qTQnsPdeJpBdfV424SBH8eC5SOebAyaxJWH46HNUVufro8UC73YmuE4VctdAuv45f0gyxyvTpH6XWVqKwv2qcENSicHQqf5bBoyJWBdKxZh1EcTW0GlDfvq5V3WThwX6OgSRd5iDs+01v+dQdbuFud1YNGK/eRjB5JIC2t5fYCOXNOTmQAFqDyDkxB6C2dL69JkYNFF6VFdLYtI0qY+uUNz4BoERqGqA02BUQZgTs1q2E4TNgHp1mZeSGzJ9MTgQsVToNQDi1MWbfJ3Eij7Scnknh8VwLzkY225SBxPcQ1oEdm5YIvSLK7bt0hXCe4yMRFgh2QQPQMAuwhBCVyVg9l1+iorFZZ5kVfkpKSvZoT8AJ+m9HA4YgwLYdiRdBKLRWiMAA+qPg5ErQB7QOu8Ukw5UnkuShwLJLwUSaC+H9NrY9wS139fjjUaevWkRHPb08rbOkqKsJokKJ+brJA3pNv1hWsEsto6ylwnagUPSNTsyzB0kfetjksW2Z3onzIMoI75yS0Xz3VQNvR+9oX8ka2IrRakmBVWgh3uXeYa0j1sGegG5e9VvfGb32m/LdzKNDIFLYbfQ9jCbBZArEhZtvtx6rWaFVNk5DlLs4gBA2tEhlF/Kp45cHrH6LY7D8ixvS7Ngmnq5XOIxytbx4e1BpchC5dO/eco/I0LE8zLXKNr3ARTSzbJAyLTBPwVoECtaVCQx15jnU9DfI28Xb7jIkGAhHb6xL4p9LH8Trb8DB+6lWJjbTeVGvXHpSPTQwO5KW8V6f8suBJJhDdYKYHUEAcTOg1Be28IhPRxWoA4BN3WHdUihlvY+NDWcXjp/yDCtm/dtvSPyTp/jtF5gWR9cI1adfIU5Z2sllTZPRICeJjzbnzkbKFlGT06hXnnj2EIju6mroe3uYS8PMB20lZZ5vZ/fncf4vrsr0GMSabRemAPubTDrnLbtjuaJIPxKI8YrHK0/8A0QldbiOsi8QuZbUnnflzMILglzT+CcJO7ErLdXjzJqownCB0DIztKkkRJOYn2c5g99/Jq1SJPa5RsGx3bEuLedmSRunAyeOMjDmZAdRpClGFrfbgA37ASVC6wEAnmzUdMVDx4iCA5rgbytz1H2UEcP3jGboe5u2Oa2yhchDuCcLuE1zO69be1WthbfCfD4q60xnlfhkTaP1I2s+OZ5YOdwLXYbL1iOdMDJrFOwF8VpNeR9ucol07SrnW6WdpNi3UAN+zgoBJfrIYTXWr3phPVaTZNMJf+iwG86PIclW97WPL6WbmZaqiu3fGO0j2v/XqNrNMVR5lkaPZyv8GBJym7tXY0QF0Wr/X1GYgRvV5OXrpmMK1RS1QYrcwgS1JhKQEG9q6G9juwg1N5aFcB50LB6XRcguS2ltUaFjPQwaNe6LoXkSJuk85JI12VaPySZoES0lxLzOoaUTxDTKZBS4E0qMxXipBQuJXfDQ6WRYFBuh7dt0DtfMfwRle0AwwW+XA5v/0+ZOECZG0Gfb8CJAS/QU+nu0iW13zAotoPHOo/uJFlHpnQfE0qTuyXh3McdRpDRmYm93QBu2BoqF5gYBPJm2aYr7vxK6h8MJXlbO+49vl7mRQhxtGz2jKquqVsNtqObx0n7R/S5b5VH9eA0DzIyty1LvyRC2xShEFxdhxC2F3CY9Rn2tDhZAbxfRWkquO2c8jT9nld0SxbTxEPW1gBBUb7hEW2HDUt6dZSVTw7BPPKtTY8T3tdwhLhl4ZnNjBxM3MEoYP32zUzXPOa9QXEwBNV8OHIM6UhmUXNd8+s95r/NCrtFz/nlREVIxLAVkQZmKHpMEShubLl1GkVOXCWJE/31hJAi0UCnOOsJLw1x0A3ytkJtEufRD03CttbSHf+uerBN2jJ/+pp9wobEJbUWPfdLZPP1kabXucmJptyeq43z7EZHu1Cp3AVQbQ3tAAYFoL53t/w3Olau5w0XIkifQwuV12UCgjrITQKC6KDX5W3pTjX/8pdRnGcFQsFuyIdKfYoRtRqN4OGmQKNagyQ77bKUNszvuXSYpCg1nV1jLYOKVVKWmhyEPJQ8m6IaUtREanA/45LD7XING8V1gbEKBDjoJ2f9NKq9OI9u3DwJOHrLHqlQgwmxYOeYybyTEKvSknpZGyrEB+lW8HqgS4zTpMDrZrkZ3X0yQYKwrsnWMJmw5a+Elyn3TdPUURyjspymwUtyARpvWo3UfOiXzJUXQ0fKz8wrWbuszk66tk+m7KBYad1yk9D5oELlbdFM0N4PNAfSw4Eio2z3TZc7+T7CHBfPrXOk85pDkUbdtIObBOaaLrKAUCeZTOjybjJA/vqs7YyXQqM4DhpN3hal0tZoNDJbBIEvojY5oSoWxfAWkv3ZaPtmgk/3vyfoyRMFSYjtlBBBgifUFHf4Gr1rd4SwHwuC3YMUoK6VPFphtSboOtgCgb2jP1VdawD8lwBP7X9Q/PK2KIE+Fnm9HvmUeaS7WlMGN15snpnxIlSQxYFo0CD7kH1cIv5YbzpvZFiuzW9ZIE6bM2WwNmcB/LV52wkvlU5xHPS6QsnsvTbeSxmfKlqmkSV/dQE+lAbpEyd10c6ttaZonlpr/jkoCXlblEATPbtC2/J+1ZkOeFv8FMNXO72dFcNX2SwfVKLkJZlm62AdCYPtMnoht1KCImvSaI95kiThmJO6KFAWv0zy/FLX2FW0CRv9gOJkFaWvX10W+BfVYq/f/fX1q2uSQeG31391loVhIJbvPH2PqinVynV9V2GVmJ5ncXpD0BoR68/Wwxo0RtLWT9HYDWkMzw19RXuqEQ4anWakrdaZZoRtY5OODDdkIcr2jQ2UI15FEmIiROkZQmPTVN7y2ASWtzw2tVv846f2pDw0OpN2sj5uK1StjNYENrzrbIEWR6tRR8K+AvwDFQizXYD06ksU/8zr6qjCfHRXV2jKrbTQePmfqwleN21Nj1DXnI7u75M0wTIZbH+wHp0G9H4Q2dSQl40LhPXXCeatoe3kzFIYExtVP95Eki5PYoYLtBkvkUFU/kQL2ZSMOsKTx8fxX5IlDZ0+r5OCOhK+5Fm1nLjN/0LR+PRk5at55OgDuku8r60wqI5ium5+ytPFBPwhNjwRYzINH0fZz0k2wFybk6gYts3zkymb698Xmq7J87toAuOiXU2pAdinwhtb7mu8ISjaNK43RZSVUUx+bkyDyZueRGRkjV+hsk7HJ/tJtCaO9GkJLjY60Wiv67veRp92yJd1ES+jEk3pwL+MEtdL3/xrqmPPS9sc2Z9jhbOum9OSyb3GH1CKqr16eUuxE75C5OiNuVtsdGzxKSov0FPrxbnIyT2bePAqof9R5/CUBjzypGJ0ywNujj2hciFSAgSyjclTxg02LUAhg8MSSdecAwU739C3Eu/SPyVllUseTIQAb9tD3E1n5VDCcbIC1PZNkI/JPd3IaQcBAd5+K9HiR1IthcHooYVBGVSxHRytRXK+KfibpoQT+s8VCZ3ly516dtGkiJL1jBRLerYpgnvGlNv27AotEFqhBavETqVvgbJQWqbQAguD0dewfn4d912edLArFck+LAEfimeKXXq1NtLGAiSv7TgAidLjobzC12BF6PDacoOmBBXmIWBF3lZ/DOrp4W20mq+beJx3tjYnMaYr7KaGZpHtPgqyooa2tRdanTKGgWDU8aHmcpJ02FpwDUSDsB3EXN5WRy9fCR2soCGQDV6ECX6wOJJC6ahpbu1uamgM3e6jIJdqaFuFwhlaUxjtRgOCbTovheOvZA6KRd5W5zdjtAL03IeZdgoRKDdyqMJ52XWWZkuLAnijOoQneb2eyKl9hYmxToiDaAqvXd/aNAlurlFG9rFTjKxpapphfcGbLSZ/6mjtnJcb7qDuw7n9rSM5oOyXZCO3k7CAm44G1qTK8dzK6ogjkoBKV2UZvNeSzPTQ6wbK4S0kk804JpL37ZAgdv5l8/ibL5q/11E7eq9NR5ZUl0USjx7C2zc0esTuFKOZZCRjRxh3vo3GRhybZMPWxqbdLGGu04a3fsifsjSPFgGeszovO2TBzPjPWECyEnV4LbdARFv/QMnD0mLrorlLV2eLFGEzKxo/kKFR8Cc0T14o/pYaSkdlmcckKnnRGSvwqUdAK0lm+emsKn//rsVBI3hcAxxEGhukzaxqutVDQR1rCxVd6yD87coL+haEl11JUBzsSnlbQazBC+bNjumyK3ZXMfNNsMdW7mk1msDsYB8UN+Dg30nYgIN1r6zTAL6DDCr4LtBV+4bgIXAcR2mUxchzW1aOfstsKgfaSJpBGcgGRbtAYW0qOEFtKIHDnzgFDggyGU3A46YujMflSY02oqaPITroP3lbQWyQmyKKf2KKTxToTS/pht3dUZ5BruHjH1CaPKLixbH65LaPcXAdL/SS2DvbkD95ICgLcbvRAWIHBwDSIMAhlFeqJxZlCKV0cLrrJZLSKUgkHXSIPo2/XCsMIeVAJq+wtDjuVzA7Zlfod/JmpLuDYIDmIAcKOQiRUCyYEITaLoU5fLpCUZlnZ3nRcNP0bpCWfxF1ewc5IPDrgPrylNnUtjdgx88bc11F9/cX+RQpao4WqyTTX+398y9BHgQZ6LYweeW26Iqd646aoYlkLw1BAPtOEMxzs5lT4+wEL0J+8RQ8psPKppL/ACvbZVS09o3lCWFznudQkZ3iEBGWwTyQYcJC5kmxhEUSFZiHSFjEaB7LMEbHLqjiDXNLdhdDLXXLwrN7DCkYsNOQwwa9Yyw0BK0lUiB9v509sqrzXaEV4JhXBqPvcpBD3xbJ9RrLWnfHvdcFLidSURWl+cMbJd7D8ji6PocJ/3U9TATiuFaQ5FVnSYoHFuDNdszXT1+zdtiXzNGSexIs5wT8TlKuZHVI5I0qiI8hG9Wy9s0q+MRhtGp0piRQYbGkixKV3+5BOVRnxalCe9Cbtgpv5Mejxg2zGUODwWSSvOs+t6jz5zsBtIafXoStPGW7EjyMAWhfXbQNHXCMoPyCq72DwvsjqCYV90rOi6cXOeGs2UNsfXZqN9GD/7YMIzmIlrNo/TnQI2W6tVq5Kt9uprIEllu2WLaODmBC8Ob3qEiibJOu/SRf3SVZ5GkR65EfeFne1vXPOgAra+7eV4nqEDRMI1+irL6PYjKJxSWebW3UWZhmLX3TmttNtCOhsP1IFuGQfQp7bSoqEaWcbTSyIVryY4ja6BDnqCyThwxLUUL56HyhOoN+F+ZYuIsSJL59yzOn7uSCXIK1vQlX0it53s60nTxewioz/hnm2If6Or/W1dd7irQ577UmqjypGsMcqtVesfjdDnEIloBpVZljzbh+yJMTg7bljlWDWi6DdXWxslLsOtghDqsRs1Vdhj2oH+KsiW8svGl4sAdHV+gtmmCO1hv0XOFPq/X46fFJap7f66Twv6zOyEtWFXkaIi5kptMrXoQs1KutTnVWpDzPuYwH9AeZ1rEdodrZajBUHjHN86h0C4A1bqWaV0sDZX2ZY8EOSQjPQ8DF5LCKGKwiR2kSafZOATT1TC7u0VK/+FpKbZrX0OYSRXvgdnlbErr5x9HskBBpoiPzNC+uf6+jApVXD3ejD6rxLS3+iSWBDe4PHjvaXDedoKHzEmPCCzmKAyRR8rAhOUxUNYQwaz8n2U8S6DtnDjtba2t77CxDu9PMWHNakW4SVLSucOflp8dxWGtUmi1M/GeIWxZdUDRhOkfXta+XIejFAG3oORkoKPU9694OQTdyDUMIkisBC+k73TQB7OGFQkUPVft080ghwoguN+fJl7L556Av5G1NFKvAtfqtUJ3ijNTodZmeZgTe20C6RjHmlTCD0Byr5qXyTm2gQ8bP+UPeH2rO5oDkx16t0uN88TLFW6t5VmF56c6jLlD1lBc/pwgOwIrphYrwSV0UKItf/D3rFOfpc7yMsgdEHwt1Ra1YP6SNwAF3ZIS3ylpMeJ0OWIxf1dawXyDFmdGPjAOXDGkApR7LENRrId10y+FhN/K0dl68vOmQHFZUeVuaFfUvo6xt3cRoHi75yyjZJMyzhf/VdTn5nBME/mQ12bCc5cUKTzpzN22s9j4nqwSz2E3eWKvewTZEwMrlNjmAtujaMz9GqttOswWe2eAWlt325ktEb+Z67nJaLAfVLG+rIb+vhdVgmWfj5OCXMffRLdEKUbffXeqy5ab1yzcDNAdulLdFCTX6HiMQnxr4+RWtvA+zTR2T+4+j+Od5RqM0r+s75gjX2V8twXgQiaATPJoLPFSGnTkyUUtYD8x6ooMVdqjaCiG90bLGAN+0BtR4HEH81ux1kxu0wua0V3ARhO6gSORtzWOdfU/QE+7qcvqWXXds5u4jPA0PefESgJd5VAc+PvDxZHzcKvcAbMxhOnDxgYsn42I6FUnsFbacr1Z59obHdGBjeVth3Ed4RB+LvF5Pz5a45RmkUOMaGCkKe0xXwVGcXqE4J69W23tsSegARvemR3KQuLElbk6HrTIGzjrI93yxOQTzvz2sDVvreVQatgZDCFttCZjXDvtb4SOF+Zu+/kEA91kAr9NadY47Uquh8id8jrKHOnoYbSW7v0/SxG0f1NctNz8PoiRv62ixKFDp/abDB5SiAHc8HNlT/h50Mzp4GenY47aHYlYQvlBcPAQIP88sikrMf91LcB7RUANMB9aXt6XR/KZ3umw1/0tZoRWmwxM1EqZu/oqQbuy4qPFcDC1zf0hWKCvdDkIFSemRHYTlICz7JCxneVGvPMMGKI7yjYDqICsKrmXo9LFO+pab3zMd+NMZ9E5ok6+TeBdDDwT+BYMO5FCC+acA9UwnlTxiWvQPkrsK7BDPQVoV0hoiuofwUChJvclDYcL8+U8UK18JHCfwnyS+mt67TnJgRd570POy3cwevxzV1TIvAiK8wpbDOmFSLrjiHEmHsmwsSRTAqpXbITybD0AKBlz7l8PaxmxtRMek9yy0tO8bIF3PGUivJYA6QfCnz7lL8D2u9UDe5mawHLS/vC2GTKTJrQnP1L7vOpIOHUmxcGSWnOv0MLcCPHu4IwUDTnjksEFf8mMbgpQKVK7sbXBV0pAxhDoh8AeVIm9rZqeFxlPzfpxWnXIYWMR5rtZR8uDiwGhX5/JNh+PAus68M1ZUjHabMlLDJI/Dri2xIS/rWrjdqRB5xKf2YsihOkijszSOpMmP4/gUT1naHmsi1TVk0y74yn+ghDKB5H2aIItmEuKYvAfin6gw6JX+a5QtyN2fKE1ffHEdVVUUL8lm591ZYp2RbVP7vVftP2lrG+vKC/RUfkZE1D2PXnqVCWM8aE55WzDFvM9iqEiq1OFfRkllEkafBDU7xgxkPlmi+Gdee6U/b560eSOgOgiNQmhmiYTcyTcyrpfJek3g2+tWofCelzfR8+kzYqjhigoj6q40BnvnajteBAllb8k9fbzWULxQAcPeAjps4ww0rSO8RGFc0ev1CbiVgEr4kJpft1TzFBv5xfeRVPshn/1IGtBcc02vs/hDDWtl56SzrsgFNsLXl9ELYYFPSUlCPN2VlgThQWvJ2+JJ5qu06ExsabAZP1ZQziQ8dCtW3siZaR1BzowrBpWzcAJ2kCwFE7/EKRo+s+woDQTRJSqSfOEfpVZFKcVX+iG6rvC0E5EM4ZcI5XwNdJ/pPEuqJEq3WZOxXTTSYrfDGnLVNQDU6qshtG1whnT9n1ot81s2a33upJZpIuLkX/QtIkoWp0gPioMCvxEQHtSzvK0w15HdLtUCeGhS6vnyefTtz9X0VLk9pMqom0lQ+wiSdbsB3+gbOZSgSBWgnqZeSqWhS2nonJJqiOigSORtXXY5Qt/5qYAez/t5nLTmrsYiL0ssFqk/l/GoDny2rXxmYdX4BMAfAt81a+XDZ/SIUu87Rsu8qPrralPH0p1h8HCNazZz6z5RxMgBUdJbBBpxxFRQP/8VhhCYyVFRoGKKtiZ8qkN3ww/9XjdPFfld7KBven2KSnUGSBfSuV1dkF1ZUF5VCHdF4Rq1j9o45IrM7pOHumh2rC2eg8qXtzVPPOkeplpkYitcXiSJnt8wGA4c68yxu5ool6ZRcOEcUq9s/jmwjUL26Q2sQJdZdB7+OK0X6Dy7TnCTUe9vc48Au4zKktwcwwVVkECDDuHotLhJKuVreGFamSSI/wuqovYGn8rcD9fYB8SEik/Q3jRzFfjewRXKsEo+Kn8ki+aQxQtbg+UfeTY+HZqmfhTRurX/hb6b1Mdb7afBvPmN/DgnC1wA/VIkeUG1std5MhnYTaTKiuoyF/I1uV4xK/Lp8zovqk9Vtb4poqxcJSVZSQnA61fn5VkaPZR9a/YZ56Iq6t7/fSNvKdiKjumBJzZ9wfRjZ2RI/i9odYeKbsFMVusUXeYlZkxqr//2+hdhwgY1vuCOJGvcXfIuKRngoPI7cRIacqumgN536m/kEDJED4icbjRK0Xsa+vs06pZmnIbjNL8znQByYYk6YlBGNuYw4flKzcMTLSxwwUmcpKOyzOOEEmYgKlh67xOSUzteokVNJLf8yQ3tNFu8arwF8iobx0Izc8QhMYDGCoByGqYuluLfXv9fwgCNmumP5TfNDHrOtSKSsSU1iVDAKrzEs55klWgpJ1mMpSI17hCHwdDaJpPVt8WXfEBrskxmlTHxTTrR1iO8jGvCXepb5rYGOtr9+pbhMhPmww2leRM20bGJmifECnLG28Da8x7QDsB5SgZ/x0/Hr1+zJhrpFQltIjHMJ1EZRwtxRcUCvAjGtfKhTMaz8lkz6UJbZTZW/XuNarTg1prbMxWzyqtA7ApC2/CsojmAaz/kT1mak/xqwxZ+efNmfp7VD2UCrtVPn0knzraEaW/ZC+gmLDuooGFYRzYdNgGpVrbPW6dZdSOZlkfB+TLpAp+ZYG5OZYSNZTE7tccSZjply7YKsLNCYraKm9Ujmk31AnNq0hem2mzs3YbWXCb0dd3b9l8pT8PgECMPIW04WNIGwLYS5POzrHoIE/Cpep6MzNqmytYwZhu4Z8o0/KvXYzAm91K22Mb2M+ZwCDMw5nCeDPdb3QP3szBmd56u1ZU8IMSMm+N9czYU8JprRrqHCuJyknViAhaS0XUXtNqHpGzM4KM1nhRyYtWOJkGllJFUlSCm6uBtmErZBsBghozrRZpW0m0I01UZjyx9C+ZqPwBNuh9tAhhidRqQBaplQ5mg65Vp/yCXkAinn0QntWZEugl0nBGJTPrR1Z/bjrvFfJM8ouKFxsBqLKwBsMKGczDehqghXmP7OdryqerNdFYYSGcjrmLqzc1ZvTnQL0kaFhAqKDjMxT6TNmOxjG7NTkE6ium4VDpfJl1gc5NtBZcabmR5+JF5dIf3srIxzMCgO7ibJel17yO6KSq0O1oIGGJMFs6GK0H8c+xuVR2ZgK9UdN6FXW7L1INhDHhCo5akFRVa0JXntE0C/Kdqa2v0onZE0ylI7XyadIWtt1WcbbiiQ3Um4ucdXt1V45iJgXdwlad5hJPs4Quqlvni9gphbAm583FCfANK56O+KsTEw1o2/GvQHrSPooUBvZHXFTbq8LQ9YkZBtx16aachaJAuLKAVWcAWzCkxv0ArRzCBKCvnyGgr2VSZTYjbrABaZuTgIDbsEwyYMyCP1Zz1wpnlkj5MwDwSmu4S2wxVj26ah9ISloWGuKGQeZWWDM9OYH8mZCqQ1ibtDyrOdyDe5WfYvOQk4wARFDwU77NEWByKi5gBxjJjWpexHycpubTQNaDt5hA+OBU49OakEKXLgRrt5cNNug9df/kKKnr0NxvtySI0Ay1iErLPb0DpRjGBxtLNl9F6yCQdmkVfNdkbm3ufeHtBe75JjijjIWUtiF+hCjZMq24Q4NxNfsdt41yjoUzAvkZzaNIPNofqLDx8nOYP5Casnm8FSIhXOyAb/hQR7xRPSrs/AR9K52RneK99N/62ZxwVl/DAMg7sH6O3Y0IBPcCHMgbfDj6UjWAiVpTNj0nzXZ25PfJX6DFBT59Qur6v04zYvIMCnXNeU13hpxdqOjjrda3L/fbt6LaOq+0GNp0j33CeLXz6TcXZ2J+8QHpeoZXeEBAgIabugGyYWES8U4aAtPsTcKV0TnbCECC979aOnnFUXMIDyzjQwRAA0QN8KGPw7eBD2QgmYkXZ/Jg039WZzxDI01SvBQdQ4LKep1a3xocId0rzgV2fYi2G5mAnNB7p+VFWPpEogFzIaTlkCgZOxmcNiC23sYghyxBg4O3gNaDjE3EbMBdGJh6Gn5XXvucVdVXFebG4ZThGxR6SOjIeZMFtOVHWlIQrYXbfDt7UDGUiPtXMnSnPNrXm41xUtOkG25F0Ln8yUPnZmLoayL9cDSv+VbemOBkiACOcIJoRCQYPfn44EzFOn0kC5ig9qqsl4cUmUyI3QYocbAa14YRs8op2ydlMOrBTh45WQ5pAS1rNsUl/Zj+OPMuLekXdrloGF0Ehbu6hbFgXQG3Fp0ECcuSdmICz5MTdHTaiGfsN+WgIK2Wk9g0BS07ikM/ESnAvpuIlmMC7w0y39P/N657KyWYApWxkzUEsUoB9mL5t3Zop6/9UjAfMh0nTm1rboMQarjFQMs2Qx1BfDWYZ820p3wFdn1bhDebDmO+2wPxi+EVvJTEDDm+CMchl3Afy9ZawoGQMk9pw4vyYNE8rzMaKzZP0hpHEEDDEiu3rUuZsCCI2DyAOZL+pejEBI6moa9L8sObMHKXdDwzBAnLRbvo84L5PxnU7uWPo8kx9I0+ffEpwb4qXPnmVNlMZWEuVqYyt4JLPDW5QkXps+7jUaCgTMK3RHJr0Y/ZUZeBIGs1nxU+NGE/FvU1rAOuCOntL+XYwiLmYdjBvJp2gFeZd3GmYjppHOTjp8m4b98fj3R0WlPR8qhVenItdYjZduhMBcgSG28WEJtLeT8p2O5i85GNyX51EJCACd3kZlWjxI6mWGw6SsYumHsSWXRUbrtQ1I1OLEPeHu9tu2KsJeM9wGow4EcQwO2MOjIiehXT8AtZSMaWr1ahuEGBPmRTMr0ONhjIhTyvn0KQfXZ3t4uFvrIjZMfKg6mTcPGx1dwxR88HMxdTgfJp0ZlBxXrP1Iq+QyR5pAyc1WQmItcnK4N0d1pT0fCpjVZyL7d8jXaEnLD6XOUZQdvKj9b2rKkFsCMDbMKSyuZ3y0puMZAJuNZm/nfDgQwMxMwS0NY3VHrj/sWnITWC8kh6RhJX01pyaSEMwWc5M22uPHNbdWV7gjk8gr/A8bP/i0vW72TN3rKLjiwG0iulsfXFwA1AuPwlXbw8LgkOYkBPBOTJpv0cwb3hBl7HXLGKFgw4YbMBj9kl652U1y3oyle0so7EpT623IHLlClV1kZFXr5DJzQgYHDYHGEg7yxlsYsdsZtUYJrGWVfO0E3byptOGek9WIfilvZAK0MoyyWmzJ1HRrGXHdbZIkfJsRlEHtlKG4HaWirwp+fkhM4bRFguDnk1ihminwqQXm1ozmsjcSLTLhrTG6Ey4m+uHdhhz8OtOriLCKHRhFbIKo3PqLsZa6AYxB5vuYOTFMDeejkNBaG3mROd0iTvJmMoRTMCVyjnaIZa8XqM4uW9v/x9VVZHc1RUyZVF1bQXLwhUdWFjTgx1kabMRTcfiZnO8CywPj+Trmvx5K+FI6epujws0MBRorIwNh+5APmYjsZxfVDyGO4XF4s4bRp5FEMOWLiRKBnfT7WryzrbkKLtlLGuw6M8vcf5jn33FMuEbd/lr8Mwthd2yfBM9KPLDibAKuXGQkAFmuQmGi8NlPGtxfo+KJMqqflpO8tVdklHAWywxySMqXm6SlSKvrR0eBeEUKBxoatwh6O4lO/DRvLCOHZ1OL9jOqUnP2HpzC79qfIYbOgMUW8HxO7y/sxjWdojGDu70DEb19xqXYM74liVeUsHi2QrRGHQIkI/BwOdcDKCObgfHQ3Nq0jO23rbxvusK4KD2AzD0Pin47dHqe6TKu6HoPXWmCOZhcKU7ThjjrrH8HN432/m2EIKtcbbxg/sepXU/QoFbLVlTicxCSiieAKKi7o9cbHRSu7XSYzTg+STJiD8spIpHMbdwWW0NTO1/Fy/aVlnyM5vre2GT315Hq3WKPuRPWZpH8swGMHhg7uKQQ27Drp+j8xbclwm5C6a00WnAoOZsHHaToAKPOkaDF0ekHAaDQxzWQ9rwmAS95fMnlM/mXqPVQ5mARdVTZdIBtt4WMKjOESFAjsCWu+hekPZ+UibcXRcCE47f/DT0iMnqKdZj+aUHg5VZ2t4uMavhWKZb4nWzaNKTQcUtYmd7Rp6OhV2YN6RtKe/KLLy3D+qz36mfJWlFsrvrr6lZ4DDiSa66F4squuJyrW0Lda9+hHPIgp4FzHXyvNflqrwghlGyioqX0+d4GWUP6Cqq0Eld4CbiF3kUsa4mGDNMKlkFB2tbATdmbd/H0c7GfZqAM41nwUxbS9FsB4PSP+w4c1AlPEsO0c/Mi2BnpmZCkOAW3DeoPxvbHUfxz/MM9yX+eV3flXGRNLG22tvEuooQC0rq2DClttmdumJsOpoJeNt0Pm2cWVvH1bpNmKbexDy9iz4Fw7HMyNA7uLs7ilPg3XYpF8PgEPP2kDbsKkFveXAwP6uqxzEBh6rnaWdODY7u75MUf0G3uuRAAiTIkx2QFU8KmCd/xE/ahSk4SUZYk7bnzn5GX6+0Mz7lVaQPlLouzoqmZnroe25LUU/8nbAR8XgfscR8wawfPaDbsyJfaflOUQd2ibLgdg5QeUPTs51BbyZxTmqJb9ILtt62MN9Nbst6mxqjMh7TzOxsJ/ZleqYTyW7Sh02tGbcQVfKIJ+dz/nDL/CaTqdhHSOvAm4kexG47IW8FMuK4zm/htkI7nkn2Ftq5M7INh1W3gn21qhICHolhd9PtqBrBxLy5k6bjyRLFP/OaD7cXPsuz5BoiALPmgnWtcuiatg4xtTDGreNu2+FNwPG2820kBXzlGdOXx3WBSfFwGb3QtP7nWULw6h5SUdSCk5kPK9jlM1c1Zv6SSBC71qgzE3Cl0QwY9YOptzVc2L2ZI7CNKY/IEJjwptNjVYbNA9yqE435lbLt6GZgf918m3SJrzubNHzO4yhN/oUWXR9vP0fZQ028ATIGlFeBOF6AtuF1RVMAd/c93zqu1o9jAj7Wz5tJJ7o6M3KswdZOt6Wz3Mq5buG24tbTTLu3Hdm1ndIbpbhOhWugoouyW0VF9fXunyiuSBF6xvMd045FWZZXFMvfvpXoJC0IG5S/va6KWlSZBPU1qlqkp8/rvKjwCNKcKv3y9asGhOEkHgZgSggpJuN9kiI5xhZAi+46XqJFnaKbqPwJYWPLtcjY+7oQsuGlZy0yVKPF6SpKUryVieKljIQgoBZ9d+sTwri5PGveR03P9PNK+x/HeS1hFKZci6y1Mr6gapmDIxwA6NFpwr/BFrRh/7aNGrViilaBTM86SSmdpq5Mi+QkqtBDXiSgDLeFL/rRJHFVFyCOtsiUICpMAwhj8pAHrpICycQWANOjZjI2gjgHyTLNBq6ciCGMwXw08F+irL6PKLlUwsGCaVHrcFoh6xZdkPl640aDpA0QAQnXx9ToOkJYQCYFtExP9O7JL7m6G0LoEeIFn1wVfMSmD9izAYAxsallAQsDB6JFeZzmD5d5CeLqyrRIOtMeQrLZW+mQkF0Guq7I5vYKldTuAxECcFrkF+iplKn+rkwvk3maHmXlk0QU+1IjRDIUxkxAjGUVBzQxkbqeoGKVlCU2SptAQbBXHIyBZYmX5SxKj+pqSSzrJkm2vAEVvLYxGiwk4+C+0AzNTb5OYikeWmqCKJJhMOvFxyKv19Je0FItIuophXC0bmfDlfcbiUlo3FawZgUA9Wr2Y3KPF8JiocUOAJpjVyE0o59MYfSFZmgu8gocXV+oRSM+ug7TC3yc3WDNk1lWm9emDZHIKDZ8F1s7WuZVU3iYg0dntX0bPh0G949/4s3MPINfAVAYarL3J3RjULw2AI5H+ayEU2PmzZgSjz5PIKUUfRfADJEii6+iAWUyZreGLVozbcIEtzVSMJmfRefbgABNY326GAgzk7pHa0Pn8G6yvW+qNZfoBUjJxqC/U2rSiS8RVRLSvrTlesIs8SaV0vQONuEGAHoTHr6HBFr0srtkFlvGG7RapxHMkBCcqQPjRYWYhzHWMQqcHIjesEAZwhyrlEgeRr/17a7ogHvfze0pDZpvhQJNX6jvTXfNA+zN5t6Mjl1QVGIW+IGShyW4gg8ATNF9SLAJUUo4m4cxM7V18gJc7NDyHRvOC7PdMHRayyB9LBvMIkyIoTkqevagRtcEv2pld7WOkgeQeF2ZwfxSUigVyxDEaL//GVV4X6mbYhhSP3A+OAqkgBjLZotWujTLovS0xjUUkyDbTkhiTizbMMFt5h5iDqHhLgNBA9rOEoZaKLz5Qwj9FBZ5WeKKqQIlD2MwelD6TaQe/6pkVktTpDdYomelc3tTrqe31McCu1eYw1fJ0eXt8GSRqQCdYw6h+VPizcG9vFIfFdCPTDxLFY6fjRB38QEMYu5UlT/nH1LHmHKbU+Lb/nhXRjcAVjc4sYqcZoNDbS3ZAMwA0dSz4UA18GD49kxCNwW0fHzyShDtZEfaCgIqGgBIyBxqh6Te7fDwWUm7IazRwAZVNHQzptYQKcRuwwP3kbhtEAJgynRsJVvWYEkwJguy7QDUVU2ZA3GHR9C3/VG0SFEJpHx4cAWIdsJJuYJoEqwApWToAlCps1L0VILy3yjHwyW+CUQlLrGNiDUElTqbR8VFAox8DDwoRBPWDFOQQ0A1Lrt0p0C3R+t1mqDFTc72UySKEl4+KlU1iFhMeI2CVkqsAN3kUxCEchv+NKFbD20zvq5SSJr1OMcVub5hODZIQTKwgsEIoXoBCAeihQwxcJyhFPzwNWS5ele8mgypYfhxZAOO0CODSDSM5QpFm16Hbra2UvqIsNphCVUUdDLU+lLMY2sxvmG9vSCAmo9KbzE4kmsao4E9IlEZDiCcfDAQOEQiLvhPQR4Q4yRm56DlYTSilKHkdbQMIK2qYDFzMmobAUiqxB6IrnoZBcHtBqqXVT9CTiOyw6DP2ytUVgVmerRgYk5FKhrUkg9WXxmiqRC/qiCnQQvQsrEZsT9Z2djX2z5GF6AkCKgYGgQP0osLz1WRC8Q5MoW6XHsK2vAg8hFwkBA9mDhsBSl4RBMRgQuVlpNiCKgfx3BqvckyRAf59JVc5+KH6C7LMR0FPBEilMKBIACD3ohN8L/KGSHiAqgiJbIPQY6TNMU6rr9DoKAKB2ownGGNAPThEE5EpPamwOZqrIJKAqx+VHwVFZ029xoMyCUghvSQbAIc6AXdO9jceQeIpq4gH6CyHkQ+yc0JBQ3VTQCEZG54eBOyu1aiJJ4IJB+NAAsRibnooiCMiGoCYpzkK3oSsrlvA9NDgFOPgwf3ljwQKUAfKandty5X6DFBT59Qur6v04xox0GBYhejq6ndcGgQeFPVsBmFdpM140Du7i6UUjZFIPnoBFiIXsztLAWhRFQjyyZpsOP3zSUxmB4CnHocPLg3F4FIAfpISe0im3maKhllCKAQARYOdBY0F+ZUUjRAMTJjbK763TZX9uChsyDqnjOQsuH3Fw81RGBRQW4RiJKOJPieV9R+oC8MsNcfYXLIwNXjkdTyV7sK3BK6SabAhXrclcpb7lInQEB1DcU4lRVBbhPvhKroqMavWLbasYbb7WmJKIE02JppiWa51ZucSKo7tsqtn1lF+ZCN6sPxgcpbxMpYQZMmR94u9lePldQFoOTjEoEhurEXohVEApBNQREa5asnCQemGcYQWkqULvRYRxUO3RRkuWXvdktowsJoRsCASqgR6cnAIgFoMLirHpI52qvySs5oYIzmsRlLGJ5ocMHEECjqpTTYjAMqtcHAmcg6M5YQqoNBJ+MQmMAOtGmSxuo9uiCcfDAQOESbLqOCgi4gqpG9uE2bKn3KQei6r9KixjSYSndC6SduN5mz5OFgcAX5sJT1VOFgfI4Lg9gwuA1FbNholGyThRiSEcjGrR/fMBn3CAQcJuDWMrOrCFJ/i5RePIhGfjaQUinUu8t4TNOQQBFWIgKZdF4RQmJNiGnCRbokM7eXuMfLqESLH0m1ZJLGiKTRVZEPTlMTIhuTIEdBNR1iGTuFci5CuX9uN/l75DSEK+gHCtZT0c9CM6nbAEgpnaNQlPzGTqgxOYe1LMc7qDwmYYcNja73SAonjepnQDR6agMp1XhtPimdxmMwjUkCIOeU0hxVwsuHpKoGUUqSMktBNGULI9uxUNtaCdVXshuso9hY4nWdK8e4TXogJiUhByEf1xBQFnupvwLJ4RlTMNmca7ebTG5yIgwB9WMYwKtIojfRYJRQ7JyMys5b5y4OVutR4AF1e2AO3ms/zeMa2akwSLCn0eYgpEpBQBVgrTBMA6jUOiDSkZX2pvN6FpLCGpwVGTCS5enTxOzEZ1e8ZbOsQ/pIDq5SIdJasG4SskIq9ZMcuXwzOcg4H56KKrmUA1sMUiWdnvSbSkaFhhX+CSmsxbgU3gpPik3juxjGnOnvCHGAijABCD5s7Ny0JIJznxqQTFNRO1h1fcVVK2leVz2BNW2OfP1Kkcz2VpaCFhBvBzQK+bTHBioFdWJflYJw6ABkUJsxRXA5UWcothUeJTZX7lZTeEIxU3bEeE4lTOWRPKBLoErzOEunbACmJQEL7Z05gMUl11FN/0NRRZFW2jTbgjEKLQVMMSkorU6urZ8E4y5Ml9lB1SX9+m1S24sq+pV8zDmZ1IhSdWT4xpvTdAxQeFGFxTTTxAy6ACZKG7yZN9YUOUiIu1g4yIITnefl+q51pflqXNd58EpDdQxqKw1THmZE+tPMybeSQou5UOOxJpMSncUcdSmk7SdK3QP5pGm5xMPoNFwWLHW/qYI3sztnUtW319FqnaLNo6Ny6nCQ+iENK3hTiEM3cgba/rGRwb0LiD4SSPmA4AoQfdjnUBQUkiCc4IrIpmXFEi8CmQxFsYxbk2XSpZrx8zc/9daPtIpWKmQ1FdKmOGKwamFqYlqR0YWAY5JuJqJx7x5rTtFsqlsMXo7FiNTie882lFc0PtkpHHnoCXNAsoqKl9PneBllD+gqwlu1/ikpwPWsraTw8+rqwomtcl3GZD1ecMnZPKYVlpT0D2MaDqENBzmoFIJqQ4Rjk0vydpfyGFhbRz5SXVUwx4r0JTJVVhFdQyMfEcvaVyxRuir2g1UsV0GJOs3S1b9bprW3JZDygcEVwKxtzMtqqrRtMMIJ7O3+PbVbReCQCKQYCg8LkoV54k1FFgHXyEFCwlNr+kvCtipNXkl6GdJc4hTIR1Zgwwflbs+KfKUinQpcZYtJa8GGH/cMntLMk6OelnQ3uQXhGGDjsW3qBCYag3hkkjEvBN4KrwVCCl4OrlLK0lqwqmffSFQqezleSL8JDyYGJZ+K2UA4s4GpWMyJUpMF1IJPGt4CrygCAbamdeVDNkUBRq3J3m1UheOatgeRHXh+MkDY9/BNxtvzLKmSKFXcQFFVkI9cWQ+OBRdelFSGg6vQj3sZCnwx81Z87VJPTGld84HLUJiQ2PTSlGGLANW1k+qapJV9/1OfoRWGlg9ZXkmam5V/tlRBUAX2kZPa6VYkw5VItwJpV54xVpxf3zYISFBxlGSo6Mt+fUue2FxF7Ydf32KQGK2rOkq/5AuUll1B9/j7pmb75dX1OoqJOv8f169fPa/SrPzt9bKq1n97+7akqMs3qyQu8jK/r97E+epttMjfvv/ll39/++7d21WD42082Nf9yvW2bwnLFp5trpSkW1+gs6Qoqw9RFd1FJab4yWIlgF2voqL6evdPFFc0uvqZ0yW/9lTuGoQfDYX2oKQisZG6muR36+YjrVJX2RvSPeFZUR7ZhrJneLAEgo4bMSygR4GRXGNBiopOjNqhnC8wafK0XmWbv3lelNe+bJ4U5ZEwn81xkf8P0TRfzDGcZtFdiri+9B/N8ZyXl/VdShINDSjTfzXHdFIgvCf5R7IeomI+m+PazC5hJZ7kYqk55m8lyQ7HU3/z1YIforJ8ygueHfqvFn0qUq475IN5/U9Y4dwUUVa2uRghmslg/FrRt2HTAnmP9vqlrNDqMqqWQ9x8mYWk0CdHN86qgcBwZZZYr2uqTwGcfYltP5uXZPnZ48vsuDR5RGQ5Exm1L7CSn+uSZ9f2m4jl17ecNufXjrfC4sGt6vyqZLFmXXZvQPsvWJfw49fGq5W0/jhLlf/ycpanJIuEgIf9bifY5BcWXZKcU5TsQaHVkvyY4P40ikHsLVQ+/RLbPuOOuYQ85M7PrFhqRVdMMlxVoGj32YqWRGMleSbQsP9uPzPY1LxPHogkwTPDlptj/3p/XyJO7XbfzLF8TlYJh6T9ZI7jOKri5TXesg3xMJ8taIYZlWgPjlL917mXkpOchO1V3SPe8IoqBXIwJI+KeImXKIk92ZdaYE5RlNW8gdp93Jrlq1UJiCgEv9WLxeSweKmrb+vadZQmEceV7ScLnY3iPFtwWPqP5nhEI9nWKA62ClX5+mt2WhR5wY2KLbCYJ/RcXdXZt4rbOrLfLXRxVGK+xcwr4BuW2GE8zRYgvu67Zf/qOMbKDO4hU2bZR3FGmM82G/lPyWKBuNV789Vq7X4gWhuvPDF1V3ML97DQHm97bAnj7Qu3RhsP4/V9tLHqVoKBNlZX31Zt/AH1QRG8F4cpsMCXlOs0emkeMeMwDouscbZHXQDKtsRGGj+g+6hOK14c+89bxN+o7sy1qoripb+XF0TpxPFGeMZhfaZxHhFXZLUIEx8+4VBRIIRCGy8P7y+z9ZKdAa7tM2u/trjRs93a+SucL8kKiWbX5uvWCN7mkpOPrMmucRmIl7zqOBLVtfexTniP/qDEyhvZVRWd6FyZfT8FlJ74jpMsIg85Qyi7MpvtOM2RJLL6oMBOA2Siw6//arFlecbtl4J+Yz7bLKQX5IXH4SJKP9ngoKcSiWDMDgosuG69iCq0+CpufoYlW6NpmPUq2MLut5xPd2ib5AVun99sdF8txKPIV5xo0C92GMTlbfPVwquQcz6F3K622IvumzmWK7ROX/iO9B+t8YhdGhRYqMUTThueWPlzY06i6QcLAw86EXQ4DDzOFxzPNl9svaiQkhqW2PjE8KLSBIUNvWL9ZztcQNeYz3N7u3FXFl+irI7S9EXoJFOyNUqeHarnmSeDyeXIU1l9HD3fLCri1Dt4AURNNCiwCJigr3kPAiToF4vVKy949xv9stvhLo3zWogiYD7bbQAajwrWagvMjUmUlsJgIZCtkds2APYLqpa5515wgMpBcjX1RzLR2EalJ/kyIJtdSZzWC7Rgb/Kd80c8UiCHdoj+K17kTTDl9ti7LN4NRaSNAGA2O0zaQ4qL7NvauFF+uhVgNl6bLFnVK+poPVo1L7gN/TdiuQX26FmNHSi3OHCkNa5QWRUJjZSQEEoFZ7GNqNNU6lQXCrdHzWmzQXhpPg12F2VojXIc/bjpAI9nWGLBr91ABAZlCxzw0TtMUqR9qVV0coGuUYp3T/wROFe0vXwemLFDcPKEIfOkYSBkvvtsKwYSjEKhzTFPc6rKn/B0X632D7G4tGy+2mNqD9mFw3KofMojluslvYEhWYPEUtuIRvZioxjYqMo/Kcf8KVmgm2W9usuEnSJXZI7ze1Imd/wpdP/RQk7IZYtyyas45vOcZ9nhPUk750C/7NKjBFDl7gp8YrUNXSPhimx2eQUeGX0xnl5vBNcFCYy1QJ5ni+QxWQC+OxBgT1Sn575Aat0tVgneqaxWwvHZsMSev9AK6yPRCgWKrej79DX7lJP7BPyyyZfZuPKaOnLlCkNY7FBRFf0HenlqUhYN9qaDEjuMUn4QCu3w3iQVv+wxn21CddP8qfe10Id8hMhdCMKihfW6yB/R4iqqsIa9rrkzPKDYQhvklQY9DGHf+5u8ilKYPiCE0xjkjUiBrA/GbvKjmLO+hiWW1zdIh7rMfcJFDrbQoqc/a66D5IONrymr76O4qgtU4BWtusCGJa8tZDDmrXzE3DTE2Xyx2VtvHtQebqw33y3609aBjAa+zOZw+Pc6KdDXakkSAHcp6oanxBCEdQsbcwPGPyi3kN8a6y0s+DExNI4WCw4bL8taaKsA1D4TOBeB2n+3j2ASbmIz3y3M/yxtxJNJ8z3YBADlVr7eRpy+3kvwwxD21Dh9XicFjdX8EL2UMGV4GPtWaDImigG8Cy+FsrBuopJPHj8wbcRiC206qClcfhRKrXpNzjGPHgqERNtULLU7NO0r3pBMGcLpKVdsI5d95hteMJkCG/3VVjp5iVP0GWUPfCQwDOHawiUqklyYRxmMQyvUwKBoBE0MQdhQnhyGgdepuCIbnGeYD7pDNh7psMxmx7VIiCRHaVf7ZElS4fC7LxmUVWRk9Hz6jHdaYmTkpsAG32kp0JZ+sghPi55P8LbvISdno1yU2rDIyibDe7Pz7BFLLK7cnYYKRhkMZBXfH/+EPcpckbVDj9Y/eoySNLpLUgG9HMqtJXgQMITVKa8Cu1hqsRvIn5qxt6kL+cmFyq12Scn9C/V3nOVF179jhPemwk5JDmhx2BHFP3PiQiDpOYTDDr7Qcr8tzyktbLzN00/L26SukPb4Hp53GMK2heYIX9UCD2FJN7RoqwohkECxldSRdee4fjmuq0q4JSeUWmP+kZTLNCkrBXoexIIyja5JEWb3S7wPEz1jMITF8QPeDdGqScxfdBiUWAWF86iscXxNFwCazVdrb+iJEC04KLBYg8gDsFEK9G5Y4oaRXsX+gBdh8iynGL+rhHRr8TRbGLXHw1lwWOtePM1IHArEaBCAdWzTAAuC2EcJaB3tZNCeCtBqJ8Y+5MRtw1RvPMlx4vUaleBbmMA4tMBWY+lO18uj9TpN+L0CCGCO/wdKHpacoHffLKgD7PPsd3Y/kgWPpP1kQS9gPJ+sx9OvEWr1ogBzaEulWKRAcx7EBw0TQDRLD9ex7uNuBgcoxtq/JC3mkhyW2QcOkXQM4gXxYZmN5V+idjkDHBViqQPmLygq6wI1b2iC2AcQDi1AIbZCoQNe8kOJmwWwDS2jh5eit08otA5ZQwWJuYOdRBIQxzYIDdRN9BDOo8gbV1PE30xRgG1NGE+/cPvduO8i+uwDeeRVx4nkIf/3i1fpegyePXBlVh52WWwjV7QtQZNdbUj98GU2/sLWjhF3NoMSc4ytvSLgY79bn46WJ3m9JtcV+CSyULmF7SDB6oatmwV69E+Nfxm/DiEsIw6aMzZsLUARB2zh1mi91mWeeGbC7TzvDmpPXnVb1R6JlxOxbL7aGJ9h818d5xXerEuxAsU2bk7/zIndXMui+6DyQ5icGlcTGCs7FRNLLTAnNAhJQLn5bNPLBySmwt18dQwLLG/y5poPjF8Ha9//r9BxiFBo6W6+Ii/s8jjZgtlDXEd2fO1OAOA2uln2+1aHnHr03v73BD2JDxgIhVtjcLXa0/O2SIPE5baIrOY4xlbbHJTcjCuyOTGxzeEnVTsohxObDQpsIlwO+cjEcrfrVGHkZIDL/XLV1FIT8GpsSCPOfaGZ2afXuiL8k9cCCD08fUos47BW17TM/eGGjRnIVd29xSRukQ3ArcIAYI/boMBi75yk5KGVPvMKt3UWSm1s8jYEE0YNFNs44ZqEIcSaHeal4Z1xcjiX1i6lVyIgCKcWhqmPZK1wUC4tdRMA7u0UYG6zRJWBanp6AAvH7nNVRGJ+Zebz9qhk5kzVUxczmFyUsLL6tvoecd28+ISeab4U3jM+KLJe1rcka/68Zmcgf/gQm4tbXIth+01PmU/SzRt5Xp6hiNis3coi3Kjgi/efW9mrokEYlkXozrRqLNvPuGz/gYsgg7IDAzsxcDjO9WTZOXjV3wwIfXDIUkF2OCeDORzQqXEdjtF27xjtcHy0L8dHc8XVtNLSvrcSxDhrMqu4m2Wy+ttvkAGq2FoNN6MXY3jZ7xbYaMYeDlP7zeaCRLq+r9P/QiW9t87flOAKrfFe5HK0fVl4kdFw93lJfn+9/29CENlQZv67SzDZEMVUvN41K+x0me8WG4V1+zQy16XNZ5tNR5ecid9tbL7/MRW7pQ4+TvOHLp2dPV8qa4/Ek01zosIbFFicj+AhXOalmF6X+T77LA1WulbTZURkPBdNJapR18+mfckquik0x/sjKtvRDFGy32efxwv0VLpLm7L2uNImGirDkvnklxAFShTPfp993i/zNP2eEzPqCsXk6RgHudWiGElecbtHWfkkWgHDkq3bonhG/rdYPIy1qa008YnRYYnVVaVALyiFeKMq3CtMXZ2zvFhFQCpmvtQe83XEP389LLFwZAVPQRw68dXR/X2SJoBHdVBggY+kKeJddO236d1H52UTv9M96sZRjCu0iJqRBBG5RQt9jrCFCu+ruKI5d0OkK5/zhyQDr+mJpXaYu/RWUuQCwNasVe3seJ7Kd3Nsv1RJa46zUp0lRVmJrM98tpt5EdXm67RrFFbH6yh7ESza5qMNHjCWzi0+r8L8jr89Jlks6Gih0KKPQqqxE9vUYg3bvePXte6rNab3IKb3Npj+kazJdj9Kxbu5XJGFVbDMMwTlwh4UWMhPl8SXk5/N5ym0/Fx2PZUB7xu9jSS5mPWymtt6mE6PN8s2whc4+dwU2eKE83rwZRbnIE/5Z1RVeCNZ5qL4iaUWmJcFQircQLnFHNV4c5XEIGa+zOHW4PcIflS7LZnpeDb4sarvofRM+mj4zKifVhriclBOOgTbqqNCB/yE587zhwyTukkzzZ9cDYts4tqneZZ3jMds55I21l71FDYWlYusqeuP5YgPtzEIYFrc3RXoMYlEuR2W7NoSNRNzd2eNfnzdYXE8x4WrjsPNn6PsoSaJ5zks7Pdpw2iO8wW3qW2+2EYeNm5Z8EGvvsgCZxvM0NY9ET2RMISFLtg8cyVvRApkMUfRA0eU5ouVO2Or84b9MaOAx4oo3Tn3RKe6/HR4h8VBh8urbqvx3/X4pE5pGgJwKegLLY4vs+T3Gl0jYFfOFVm43tLo4XyF+0MSaIhDB4otgi4rzv1MP8wvg39om6y5G3pdFTTioszrwnffAWF0kXMjNLtgt3W9F8VpWGKPEbhGzBXZHASfFfnqMq0f+NcnhyVWB/F5HQtyxXzeGinooqj8OL/D4hjzBlfdBQ4PsTOh72NzBm/zyWL1qlNuiWm+zLMWbLslf9jJqdrZPov/j7kjm+uKfh/16Xn5q8fjGAwrqzzOukBahAJgp94zdY/4fs8rXva4oj22zQnh/bnPke920xLx57wmQLHVmlDsYl80k1UxSopguuB/rFFZYeucSBZgDwyL5zys3HbLakeP+tkj2DDXCggmj6sFcPVt9fPJ34t2fS1aEsjuFMaOK7XiJmbf5cumDmbv4szpfIMR6E3J2OHnc63zqFglJQlJaO48ea75HDaX9V+LYltlMOy1gz5923Cf1n/dGg46fa7IpaX0qK6WuNEkpoEJIbhJhdmBs+zQjcNlYa/7hwir76hyviA0uU94Owkqt8feqnhdIwCYeVtfycTe5D8R5zRgv1tiO4pjVJYynINSC4ucxDVhq1OmL6DyrZH2s7yoV/6BND0aBzlW1B1HaG/ydRLzKPqPcwm/eL3b9l73+SWcLeRyK2607VziJsqXlC0CyAbF4yocksrjSAdtkUfRf5xNOggJoNclBwXWNwbAywKWzsVGe/J+xfarOSbyQg2Hpv1k4wArKygzC/vdHptsJqFye+w06zaItyk56Cw7nRX5KytXPTWpivpY5PUa1FN9yS5fy7jo1x5es3Sf51BSRDRB82lQcFBYhjxzyLc5ktlGVUAAs43icVWHksrb6u/a/otl28Tde3ZeQ+fCT2Ca6bSXFUm9ccSENgZlBBgUWOIT00gxn22OIoH3gBzeAAq782qvvLc+BCHno1Bqc/zcHGNJUAPFlvNCrh3WAl6uyL6/MFqx1MKF2LxdBSMWCq3xNlecpf5JGZA9x53URYGy+EW8RQBD2LTQ1LuK+HiCYYl9n4cHljKZEaEsdvZRBWkc5rMtX9d3FUnZfJ7FKe4YxN48hGML5IKzuoUewr4Fmna6f25QNRYY0rNF5dhgSNsWW5WgGBsP4diCYiw8hGMLuK4oezCEo37Cej4hNmaUniEEkswAPETbIDENwEO0DZLZANwqeINUEa6Wdl8t+QPmOhdO66QNQLcpsu2dmPWe/W5zTea+zhZoAT0zypfZYH2KisVlnmRV+QMVCM8tH9gnAbFY55Yo/pnX1VFVFcldXSHpPk8N6dFi+Z+rVNNUA2K/estCPqFyi1CkwHk6e8t7LbHI19YBlmRfgJUu3vhidXCCWQQyMdSQNsErxUIM9dp8tcMkmqCbr5aYgDG7jfBLVP5ECzU1ZTB2fT55fHwv9rj5aofp9HmdFDTo5UueVUsRqQDgiv+/UARQmS934+APSYHi6gO6S/jbPTIgGxdUX63NO/spTwF3lAzKpyWIg+RQTi0dR9lPcasFAjjjF4UVBHDDf34iR03KnLB2WQdlmPtyJ+zndxHvHuUL7dcFapOAmathCAtJq7HNWCT/omJ6U0RZGcXkp7CwKeD8WxOZVA3p3+IVKms+r7gO1kY7rkmGBQU9YQifFqARyaGsIiF6K08xIAWYzeWVIl5GJZJ6YUEAm31VAt+9GBTYe/zaHIGgv68rs8dKNlwk4XFdNWu00ktnXMnmwCdM3vddOJZht1FXaBUlmXCzQgJi3sanqLxAT+1m/SInEa8xkO9NAbY1x0HdhvtbifdOn5Ky8n8IHUDpklPYDM04h0e9B5KXGea7pYMCPJywXCp2Le/Tx+Se7p8CMheA0oW5zNCMw1xd2zwW9ruFBi3R4kdSLUEmEwrt8AKpYpjPfwDGDcOrHvw52RX+1hxjuAV4N0wOZc/90BkcX2axOwC8tfZe2vOy6wG9jhgJNhMIYD92vDFdQ3slqNzG8omTdUJMDcGm5IoccAI3lvgyC7sYZcTqF01f5rstNqCDgwILDyEqS+GWfP/Rhps2ZKfGn8hNAMAea9VeYwQIP3JMmaWoO2IYEmlQEjq0KZrenrwM+Fz33+uIUnmIavPVwvDIkuqySGL4ZFgsdcAMnvuKpTaUlPXXta/yfrr2ETxwdjhW7vZAzfIKDloC4toGSAYJiIXJoD0m9T0eVRyLOh+HfsifsjSPFkD+Mq7IZqHqqkrNHgDAJlI2RlmJOhxCsL5YbNF3rD9/oORhyb9CyHy3CIGss0WKyCrDBT8y36316wm58Qpp2KZgu5bnC5ogy3t5Jmhcl2e47pjLs/9SeiFkP7pwS3h0k/dP5EKR9GzxHluKrIc2jN9IxOjiNjLCMg6nho31bobAaaX2my2WY1xCXnsBkPVFdn4naDfPfp9+L7dzEkROkZpErD5S02FxfHULrrrdmvymiOKfeGDQoSVfZoGVhCFCdsqgwPJkEcFHoHyZ1Tlh8ogK+LVgofAPID3+zgoWk4cUTemy6NsEzsG77w4OEFg2rf3H9o6G2WyYqi6yK/Q7zYDpabwwqJzMFmX9kdgo0CW30AwU1pAK5/e6QlGZZ2d50cwW72vnCm3w0mlHdO/OuziEQme88kgOJaD9vMHJ58VSG06N7u8vxKTR7HcLr89ilWRgiN+wxIbSjPjCVwclILsZ2jPbop/TP0+w8g2x8A+xOS3+OhTbrbkvo6I1XcSbrmyJrX8OwjgssTF2NjSGjqSh8tk27QHPa8bwTXejwiyMCrSgBxXwuIcQc6y2B33X8c31GsV9ZOJm7v3SCatQu+QWtsM3jloMKX7wSL6uwYBsDazlbZyzJMXCJwTF8mVWKvTpa9aSQfa+wKDY2iu+O89vqCbL06BQYHYxLqzQjWRogH0w4//JkybtByeOwYPBuG+HUmjtGje0+pc+LB1gZcd43JdxsPLcMz3vvHyPigQbsb0knOSruySL/FcNPX73ebRCOtIK8rPmFgvywSIit+IfLm2+WJwwRll9H8Xkdl6Bt7XgbWAZjGU4GBAHZhWGg7IH/vJ4980cy49kwSNpP1ncnwIOwj7ZB+tEJaJEgI6LhUIHvOSHEjcLYGGFl2XykOHdb0I54px/gBEqtz/DIylKhetLXJn9xpsESfJY+TKbwDPoKaDN1/1xjWANGv+EvRdckeVu7mtdfb2nKNrEjuKmTgTZ9tVvlCUv3Dq3iw6HtpJ0kwWVW4Q3oOcKfxIfHWO+212c+L1OCui+RPfdQZxP8qwq8hTy8spg9n9DEFbmAgjb5FJ2lCaRoDbpp0k3lI45mbdLcdOboaNo7+bOaTAVLkE3qh7n+yBRwRDYrnEzrpsX17/XUYHKq4c77oCFL7TcBh0t/lmXlXi8LhRabGro5kOGWCy1WcsuC3SNUhQDtyoGRVOsNTKMVByghXFQYHPjI/tJjvdA+0Uo3MPgr5sE7/QJP/opwx6Ng+ZT1N1+czV0hnTyFKzsEL4rm+MY2savM9c5QvOCgdexAfnpckoA15vbVSyNACg4dwH9YMH0ZXqaRXcpv1Cw3y2wobgukNAn5rOFw058VOiT9YtC+UPeu7S4FWFYNOdK+Klapcf54oXvIvvdxg7KKszYnd/tAlVPefGTN4dgGCslggXuhUpLlyRf1MIwjHUrp8/xMsoeEE1Ppm5MBro1qq3tlO/dsm5sTo/Wy6puq5JTvfzg9uKDeCvd4Uo6EfbPOS4UcpgOimwth7O8WEVVJUTLiKU2xrHfw0FSAa3v0qRc8qsH83lOxbpNAXfSUeckGeBptsAzys0LV7Q1KoyySfspgJHWgrnaatLq42izpnkex+arLSZRO7Lfx9u9zLVJXaIVou4mbFt6blRZVC6bVXX9cdiHNsodWDSfplxQgSR71gn2doThjqP453lGjyLZLMN+rCdB6sCExpjGYcdg1y4C+mHCXmDYuXvAbODODVphw8f3oAzC6MCqZmi2dQ9Bnju/jPgwos3XPT52PcET9ZAXL2G4icfmsgPVojhw0dZxURdIG4SJOGQecb0HFtodFvqIMlQkcaD4Dx6bS8pjLYpt30T+B3ppXgIfYNp8tcIkIDnsKUA+PorTKxTnvrm6ezQOnKuou+0sG9bvMc5p63l5vuDdut23rWHDb0UQNuzROLChou4fiw2v05pz1zdf5ghMlz1bqX6uci5d2j166alLOzQuulRedxwmlrxJ7/QWfah3liDmk7PeXL4RFJV1gZrgOE+nCIvKxRuirr+tu4/mMS9s2j0RpT1UWMMiq8PUJBdOU8mnPd7LtPP/IcFNlt4OZR6bO0MqUBx4cs958iwv6lW4Uw4BnQNXGuAY6WSDaVN8B0Msnet8ghKIR9V/tDhYzNdJzOPpP+7xecllQV8HaBMs+7olWVxOXkk1gu0+xTsr8pWMu/kyG86U4RyWWMn2P1FcCSLdfLS7QiheHrS9NhgBVwYjS29Da7AfvzTPCPMIhWIn3P3LT1L0DMQeawy6rcEFn3PP8CUGkZMLT1F7pI3npknwSqpYPNuxPfheuX0e011mToI5GIMSID8mhTGMtKKNsCXw36iANyUU1yRmO+9fraPkwdP677A4ne/Lqm7tFjSQRUGuawyRNF/mjFn2jf2ezcFCbehAIVBDZE7uFQ2GbWXt4zimz5G23mWeAYDiXRSbcKcnDTVi8C12vmz+CxjkvVkSmBelKZ/jZ1BiYSNWVRQviQy8O0vEM1Wg2AX3ezXu9z64/6TG/Sc57plU3QV6Kj+jqkJFOOcdjNNB8ZkiGkn/ga2LDj0VnKXsAyI/3kHadGbAjsTWnCxR/DOvQyUJEtC5mLN6HNu6+G9zkiz6ZhjZO7WhnDBuOZRNv2+i59NnJJBhUGBB1ei5C2EWXJrDIqvla6tThoUzvLZFsQRIHAXjDKFiJk0WJbQu+PAggCn12CEJk5/0zvYeXVwX5G2Hy+iFtBrqXV0Iq9vbuoaYxpE7vnkeG1Rujj3U66Q750LnyRaW2wKw2WR6/SVOEZSCe1Bgie8SFUm+EFaIYZHN6XAVpbQ25xQZFNhsc/D0wW/UDkrmcC2FitU8z5IqiVJQwPmyPZZzmmAl+RfN60cG4ruu8PhcVhQDHOPI+mmwwHi38HEpNpoFB76mxJc5YJUgtMcFXF8aFGwN118hcv7Q5bL0XdtYXE4rmxrBOLx+2aUVeMdvBDbfHbC9l2B7v9c2+kmRlyXejaRBOIrH5rIX1qLYX66abSX1jIRyi4CaMPIJN/UZPaJUWNSY7zauy7youiBPzl3JlliEXtZpCiIcFFiYiOv2QJcj2ubzXJFd5DlGIUNo/9Fm83yPigIVAq5BwZwnLuGTxZ0V5LVtkruRi83tP9tE2NEcoJ+ikt8csgVbo6LwB5L00DPfXIPEJdWcrOa2nvz8ga7jb85cfHeAzOmNS0I5Ve1t5ZOds1fpnRbPaSYoXCYYrjfO1DZxruIEs99tXEZxWi/QeXad4NoRtxEXS60OOqKyJMG4uKCCjztEABt7oanMGwzdVwtXY1LxOR3aT1NGqn1BVdRGL3PG2bDEDqP0LR+h0A4vQDPm8/zxb1cow+rpqPyRLB5QxRuCwzKLY0Fa4x95xg2d/W6L7UcRrVvTCkI6KLbFTd47B2ZKKLTFe5wT5c8LNF9mdYyLF2n+EYvNV0tZJo/6iuJMv06/XB2VZR4n9CVaYc3CSymNGb09Wq/ThHA6Y7AoFip1PX556qBlRgheIxfi6i9t4PY6rwvwGRij1a5HDS17hJh9Xzy7eRMVRLwdu9kRy7Kbv74F59uHJXr/mxVDbGrJ2EHiRjMic4d9q3mh76QfJ7RopmYEGuCG/8TL6jJf3F6hsioSYiSdkFHqNIRJbWEvO6jTAJooC31bnmzCNRCAWQy67Kk+GupNzTSd6+2WubaheixEhBZfBWlgOv+gnrYiVs/p7xEGmHigc34T3eKZbaKPkzTFn3v3rcFs81VkU24z10OcnjTlkIWcdg61H2Nadwwb8ouEzOer8/KiTtPfXt9HaYksR+/NPJeoWCUlSQbUJOm7ZRMiKnlIV1PIe8HBs+AG7KVuznP2BOQB+EzTYc81hcE1m74xZhVZDZm2sWQNGP02rjT7wgJX6CkqFpd5klVlGxB7+61Eix9JtWydwaqIEm1lMYZEqGLAF9qGPGdgiCsAn+g7vI2LlI4M4RROZ6rbWDhCnRAmDofUk494bCE1Do97GxlIP35/M6fduLf/3kQPatsGAhcTedHCDYyJFSMi9jVdnFwSxp0L4i/BmIKvUJ2vlTjEoyRDBQ/SO3PbL/3fZfeBTGj0gL7kC5SWm3rX8RKtIjqich3FdAe/QGdJUZLQ8OguKlED8voV7vtjgrVed+TWOlx+T0/SJkVWB/AlypJ7VFY3+U+U/fb6/S/v3r9+dZQmUUniBtL716+eV2lW/i2mchtlWV7Rof/2ellV67+9fVvSFss3qyQu8jK/r97E+epttMjfYlx/evvu3Vu0WL3lq7dojbD88u8dlrJcpOyMMwenXSTx8zovqg9onebAbYVf/wMJ3NbN8hW6fyXTJb++5Sv+CvAX6cZvrxNCXSqR9IEGGmEakbvdGYFCtMOvXxGVQy6G9mrnrRI9/vO+vfjPtMIT9W/n2QI9//b6f9Faf3t1/p+3fcV/e0VXwb+9+uXV/7ZuvjmwbVrOHqMiXkbYFvoSPTfXMH57/e6XX6yR9rmHGrx3SWWN4rykj3XGPjiaAKl/JGsfJBuG6661MtNkiQtbLUWmI/ifhwSvCjE4RuCh/tQ5LF4aqqZGaY3zE9YKN0WUle1G1Z+qEEYffCQPR6Nam5dvwtKUz2oTmLwUfZ/qZoyub9LMuJOYMGzySFdBH9nE8nRdpuYY2LNUg7Wm1bB7stCMounP8hQvPeOgxqQnv9qRB9eZrZXExi1ZyOJU6yCx+hY1ccjeROVPeyuBr+9lLOApwVNB4k05Uv23VfT83x1mgGgpajKGQUcnFBvn98kDMYqDoP16f1+iykfZ0TAiHwTHURUvr5N/ea1ql5jViUbxYcZwC8BJvlrT+6V2y6Ht5PUW4BFGR2/FeliTKYqy2sKWNF5tWhlFREL/KIvNXxzUKd272mA1YZFrFOfZovRhZtbg3HTLQDlTHvjfgxH8NdAyY9D6BXqururstq3erwzvHFYGzD/rr9lpUeSFj4y1XaLXTxos5EJKlRBm8hgRt9aZsMXnqKzozXzXvvQI6K/TzLs3GEX4vrxzpEwdx1hhg/1xGBnLNjL70Vqwz8tPyQLrLx9+xH8+kJUJL54x9a2BSsLQOqGY+jtzXqM1Xlr+Xkf007cs2ReHmcHS4uDkYSLRjVGbzHt7W6W5sz8Obubw0239Ii8t3Ed1Wo1g2/y9RnVn5fWpTveEE5mx2W/MBpW9dmXtkQJhMZaB3fwyGoeXiyI+c3Jvnyl920biwWwB8VjukiwqXpz2gFqV897BavuCV0vYcrRCbCyJH/KnLM2jxZ4IXzecJpdu01CdJb/XKKEo7xOkNMLt5exbibpGGce405mCiMbLR9HhO6YsHobn20s/EIc64SPyDJ19OCE7fa7a1wRDYDsvL9CT32EVPX9IGBPRdBPWX7m/ZZB4bcWGl/gddgvqLulXKBfbYE+U0ubKmMfxU5GvgrtOCFJbn4yJ6Nzkwbt6k4/R0Su0Tl/semuBdowen5yExngcx6FRmpw0utiMze3lALp9mCRF7qsws7TJgri5+uQm3wSJtjuex7EGWn5Y228DMnjSI/xhLNPTPVkn2tcbVIb/X/5ib1Y2/gD9bmWI2yhqIy/VQu7S38u88DoXMwqnceqYSTyNC+LGMW0VswDvQxp/DVZulANxrRHkrs0K3NxR3BPBG4xp1KP/Z5q6ZHDj6HwR/PSqb6a576lr4b1LRFHbxPDO6ghjacdA2yN7O7oDffY8aP6SZMmqXtHV7WhFl5Bu2UVxsopSEi+Lf5U08PXdXzHLEU8tnnXb7uPBT9RSg727NByKUiSjn6kfXB+ZZq5lmnDt4zpbYAnB8tg/5tBEm+yJ4tmMz95UY+t6GWqDdzLcOWX4GogfLu7RifCLGM9ee8JPl12+Voew8aaiFyc19HTuBFfdqyvdEacPF3bJO8bS2H3Ok+YcmTkAdoqSMLBYrDfyS3q5Qqr+XX2+mM2ZJ9M8Bv0pWaCbZb26y5itkwui73g+71KvCaBXE8qlX2RnqNPboB4OtfM4qEreL0VMWvCNiS9wF+iTA33mej+MLadj9Zs8JgtL94y/2jHzHY2id9SmrBPOo8UqwYb2asUc8vjGK1PGaV9u9ptqkkzva/YpJ+HyfotLhyOUehpmjwy7aAlJJANMC5NGMmxnj9I0f+rdAegxQU9eC+LRel3kj2hxFZEM0Nf1ymeWLvIqKL4OGX0tiRusZ/9CoWyPLm7yo9jLnhCyhLqjuv5ZBxeRKKvvo7iqC1TgFaa6wBbURqBDtfIRs0xonOflx+S+OokKLyurw+G/PrcvvH6tlqi4HOREdOtZ92Jsv9Sr1aPLKdpRTTZiVRKTpf9oseCa9Or+edkHFAWIhPG8V5uljRB26LxGhoneSMnXewGf0+ayRXL6vE4KuiP+EL3IMNqE+9CX3ihCf+7+FJXXEbmTE2JWh5gcLq1x9X2iAPHAyDnR0UOB0IoP0HE4cOoRkbeqA4Uf9Q8x+umTFsngFUUffcfiu+zfUAyAcfCGoo/vkJxJBEk2cIYntTvg8DLKFk3qmSjtsJ0sSTYTF8eSaejZ5qlwHwqcll4k5B4Y99gvRBl5zC97xCKGkXWnTV6WXpXHPwO5CcmeiOI7eowSXDdJGZwebiCwj06rV5IFG+/n/KkZK1lo2gcbvWz45P6F7qPP8qLr3zHC2yIftMdR/JPmfCfZBDwPNsgWjeA7b2iItwj95tLLpKDLV3sUGWJiGnzNgWMIfHTgaNGiSjQXcV1MUczmBPi4fjmuqyr3upXV4vqRlMs0KSt/hK0spwjzJVbMA5+H01VhbG9TVEns52dmEQRfOr6mi3EbaLcZJ0zsUPA2rtcYT5Q6D8TobIRpo3+t+SZZoQBBfCzu02wREHPnajrNKlSU3rzYaq8BVjQyA7UabtI28X7hJkGN7HrpfLx4orLqD79O8hW5jUGWklGZFfe/O20s26cIfEbxAyUPy/HEd7hNCY7+R7IYEfuncWnTr0qhdU6POKzCCXViEOZ0tXu+nkER8jLYtpy6giNPHlHxQmbV3vUyrO3jeGGvnrtcj2Vr+/TjOCpRu7x6Owp6XF9QVNYFotfq1WGQzt0dN4awb4b8GDbl4JFs4nmaZ64At5jvUUAbLoQKEsAVwk0zQEjIEBLf9TJvPD/RJrw9YERbH6u0H/ET2qgCl7vVHY38/eLfShQmaEseAhbccOiactcghm611iwJcZepsUQCYGrPuMqTvF6TyGi/DJMiFpP8hjYhgfT4mK6Cfoy6wUOWbgu/urHigd/v21/F4xLORCKPbBEbcUvgKJfjvMIWWPAYKW2CNgdB6dguTKDUIRxpEG4Y5szmMqEBJg7B6F1FHwOfRKv5ZsccBGeVN3lzI4HH7OTDbXF8HZ4imJg6ZlGE2Ia/irKH8MkzA8YUhvVJbWHk1k74LPYt4hyiIL0X+z1BT18Am81AA5tHsTeqa0+MoXY0IRMIGeW3+rNDxDjKZbmEBqj/5BJyeEgENEcioPagcM9Eat4beuPZZCOuNdZOsHa3v0dJJbuRObju+5q+M8eT9qruXkSB9rZOq8P1Go3uoGpfsu1zI3jFkHXhfCGQbd77vsmHCSI8fPAbpJfSiHKnqWIQs9kzQiPvKCzZKHlib418LwqfPldF5PiwhLlmYw7g9kSlaR1iDhmbT/I0Lz6hZ5qCIDTy7UvabGtK7ZmvdmZbSuIlM2h/U9OrA+flGd5y15urOwEijkfmQPbi2YELQ3AhS1H7Pgxr/2G4cQ/ZcJSDq+DnFgzdDyc3YU9uDqct2t7t3mnL4TBitw8jzMMoWr5vE+zvyaLUjcrBRO5r+hkl6/YVwdAyf152mSe8Aol2lU33jD/Vz4XYBwfqExC7ZHXVpod2QGqU1NgD71lerKIA70ITXNfR5jGqUP0MnsAp0NXro/v7JE28zWN6R9fLdhp7xT8vmzOELpW7TwSIInOzsst+LEQeKzRbZ1zi3EKuEKSnn/OHJAsUVUvwdbfADVDaHM+05Lx1yCLKI/DZ13THD+7dETGY98d4We6Ybz9W5bOkKKtgZ4qESYMhA9dgx5e0VusoC/ScCXj8aGJldxW9ZASjJ1l7H5MsdvA/cNV9enLCJDzwTOtI5eldSGTvgyD7R7K+zEuSKMctkg2wrpZ5huAEcG5pNrtEWWFY2339M9/UNFKwJ9pT6wh+5+AIpr65sl1avexaiijEbb2bp/wzIsQ6L3PtVZz3tnx3sywQMsf/J1v8WERQkcQcbq+Mld8jL+t5q9JXO/s9jYV++HDJH0X2t+EQKNgD1Q8Zpl6TSc3LMz7RqzwBXrIx527WqNoT5h7Pyp1xXb27K9BjEkHC5es6mFGhG/PpcZo/EHt2T1j0c5Q91CRRoTWPbmp6MSl4tCxoaTNHjMljmxYHu42PNUhy8xbXiSI/hZlIb/KHh0J5Ez2ECVLdznv0h3CL8AfzU2xxO/WyJ3p2lLW4o9FJndILR+oTY/tjQXqueI1G2KyepdHD+Qp3nVyB09LGPjr5qtqvG7Xj2jRNgPh1RXJaX6Eyr4u9McFnt286emqZ3CU/UIccvD3gZvqQHOL56jKtHxJZLlrDQ+W8juVs73F0dIGeyj16AXF2Dg1ogdNXsMKgIhlowmAKooG307j9I+1TtsN+PWwwOrTG92jyND3Kyqe9CcEjA3IIWqe1gvvz3FRi9y7Q97zye7pk7DtYmGh7wjWzL/PhuKeJWmt1XpgT/SBLdLA8T3RZ/VijssJWLJGRbThg3E4LZPxjT/aMa090gX4v+Je/2BvMgZ5+ChIMjJG0fE9wSNjeyML1DgfuwnUp/4wUqyufOh/DCRWrpCTnrlcoJpp2f5nfZx0aRZj6vAN+iI3n+vSZUDVKj+pqSegaU2Ngr+Z99ptV4SJhu9k6Z67cBETbas7A2L8S3rrJf6Iw20iK7iiOUVmGQ0rjP/AEeyVpMha6s7yoV3t0ln+Tr5PYXrzaan6ZReYWbqNXTM3shctduBgzxdVJKh+UO/ZEQOiA7Fm0rbbjAkLmkbTsmT+uid1V+w4dUlVdNJrYyxlF0uV6ISAXb0gvPF8waLFIptwRG82j5rlZ300NtE/K52OR12tHDdTWHTck1OmVmsAnFBftquMlyyEUCpE9yKg6qJVRPJy7qZ6oWO6JjtoJ9bBv7DaHU70h3n4wLR1LwAucFF/Q/CrqlL27sX8YJinwTXLg2RkuRYF3jgO/S9y0Jrm/VPM9cRxWCFxtjuuAqJp7h4Hzp/dZxOqiQFn8EuySeIfwKtoc32oys//VWSqHx11+1P4eVaGVGd60V1gW0vMsTnFXDcnxZzdN3DVGbg1O0dgNaax/M2CiEQ4anWakrW6YZoRtY5OODDdkIaz2jQ3UGFb8CVkbovQMobFpKm95bALLWx6b2i3+MFfLKJ+Mzoj9i8SjtkJVx2hNYEu9xtbOYuQnSa7QU1QsLnO8wJU/UIEwa3nmlVyi+GdeV0dVVSR3dYVC7xqFBsr/XAXKi9QaAZIIP9vYszBp9Hqrfx1kjDQijmxjyNMxBcJ65ATP/9BOcZp2jIWNEPKbCNKlYIapMD7PPOLlT7SQkc67pyePj2GyNRFkp8/rpKA7py95Vi1HwPtfKAozdpYvPyRYuVUfEOVDn3jNHk2b4fFTni4CzZWIPCAjMMiPo+xnsK0UhzeYiLF4z09Co+xyZAVGe34XBVqQWg1NjYKQ+VxJYA4WiH81j6iT9w6jePgu+yjog7GbrIErVNZiOl1HbbQmF6bDE0dEHLDXeN/Z20Thu35Zk7olCu1OvYySUMHmXAasIDRtUZK9CklwWTcuw1F8XWMnJt7eAw12u3CFVlGSMUHqDlHhnyLywm27ub3IiVs+HmQkCnhU0u0Kv5XYtv+U4Onam8SLs7/RSavat95W8wvImiCHycfknhr7e8g63dDsJ29T02v+vpVo8SOplo4sxFX37sogA0Nwd8uUzLonDNqZM8ws47Xc4d6uBI/PgWFHav/DopHdfOdl11V6DyvirRdHpYG3ZuuAG4YrPNg1edM9mKXWYwx3Z+QaZcSuDtXDBl247n1BZcncmvV8UGQzI9Q2m+3JIbt4mD1KKtOPJ2gcy4zm2uW8b5b+vY7aiXDX1d+ypKIP74193Ng3NPrp4hSjmWQkY5+GdvulZr0em2TD1sam3WhHduGP6j7kT1maRwvvZETnZYcqiGH0GbN4VqIOp9QmNFp9sdb/gZKHpZNZaNLCcZ0tUvQhqqIwDsL2qXdyETMMj9ot+xckwcc+LfuzLNAXTJoUvwuYXSRd3j/duOXGI+vd3C8vz+xRxw1RvYJXKYbjKI3IuwI+5ls5elRNyE1Y2MMA1v/h8KQcX3+EB+XIQc5qf94hnlGT3xRR/DPJHgKeAdKYt3GNEnqQh0KdNH5AafKICpM3IrflTbBOAPbIi9EPyel+R1MzwPmU6Dce3XtgYXtUdZFdod9Jtrp9mXb/61KBJm52+yeE3+kKRWWeneVFwythjPSW4xDd/xrs912QmoYgWN0z4pIl+754cn9/kYeKcle97M0n57V/EYFVFCFuhW1LtInFApnTUJ+TqNirRdJfW15GRWs4eHmeGr+Q2zErW9fv+evNJPsfr86u/2c+9hjvsgSWTlRg9ifO/dH20yFWz51Tc+3MX68xDbuwuH4e90TnzSwWMG2/rofRuMZ7FSU2P/kl1yPOkhST2zMrcJNcuyWfb37tkbPGqwi6JwIADzEU721NpvlZGGVPWGRnZqFVKjfRwx5T3vJNa1vifY+KBNs6PQ/jXeT/396VNceNI+m/MtGP+9Be9+y+uTdCli1bET40ktyO3RcGXQWVOGaRNSTLtubXLwheOBInAV72S7dVSACZiQ9JHInML0kWb8nifT3b6NTIDbNK1CmMXTL2xNn5Id7Vj4AKvK2SPO8b24vzstkopQPhK1Trn5N9uMbfhvV7iEtENO9+DWjVTf0PtiuXBAplmRwyPAMTAkpdlvjnDkdL3WVJHUvQ4SkJU3vM3r/b7tX+b/Z8sLXH8HFdjs9asbmNP/4e7L762JCTPc3Hc/XxgTRJhAwRIk/yZd3I53Qsvrr/93qJ3sfk4G0AYESvG3s0Pnc/BRuxzxFbGB1tHv9EJckZ7TD/r3NSjHMipBZ/WVXk6fjjT+nCfwKc/KEdFtuZvLUpfJEmsfckBR53jaO8gcea6eY93jYGWiKisxUEdSUr9GnH50Os0dVEnubF3b/OcYHK28MX33w2O4f9P/Ew0Re83i89GjerCTq6LnFLdyhFu5He7x4+M14grf/kiLyTjsZ/ad8l2df6NgxalC3H2eg+QUVz9LApwzrbNZK/sNB1ckP3S+qmto+t9ygvBI93weZXAPUYbATOHs6dgSvrQtj/j27zrkxfZzX9qM/GHdqdC2TJn9EpIp03xNsp2bv8kPeHcEF2ZZbtvK2O6ct8/+Qr9BXedWLgdsd5H1D1PS++Bjj5PsbFE5k6XYhvF/sNtTLSU4c0+foHljI7IBJPaix/cGMWbFokWW5a/1ksoUOGMlWEerZte7/XkDHqW3PxLq8bsFCKuTfXVV4c8ZBRHiaemnfOgwJMTx8Z5refeeZVXgdne53t8WCO/QTarcHaY8GNGKBGxnHf+aaNIIs6652H+RbxER0R2XDjpjYymEQm3wsZ23E1aZMJQibL32XdajiwvIx3X68zco1HxzbdCGx8OObPe0Axu/v7FO8maY+de3TEH/jN3BAF+XbUWXYxa0JY+NENB3YzvMQ6PeTF068x3u4Yt2bv1xBvd4iJXpPd1i7zfWwYsAhNMlrfQ4ob9o8/aK3q5mkQboF6sUtv0S7fTFDchW9Lp7jNMrzavt4Ph2UenQo/Fb8ANeU5R3pWn0m6NOrDvdclV5i51epSiG0EZM5Zfv0kG4bSdthKYAkY880riku8cW1DK21juLWW4LmLJSCpXLBk32vj67v12xqBoW9wQq2oWwy9So4oK7dz/PYLRtPC6CovzsctHuNSInmNpj776SoZsXFrt/v8lOxmiyNkcS5EYjR38TU3Aszx9wtXRX50hyFbe9yDmtydDbruOH/M85d/op0m9JaD50j9WsjPSUf9UCgetRK9Ltvl7MunJs+ip8b6jB9j2pvCFJClOP7pXb4VXwNKoroXh20S38DKLw0Nc6kuEZL1rxuBZdilsnZ1/4dDo9ZOyRaXj8dTnBy2siaGtO94dg9/c90aq72ZZ5/2wGGfqxujxRaeLGx/tntPlzn+crcjWdPas0ExdKuqB8MszdpVpMv7ASNwTxWeolHhDsoeO9XkADSPsn3tzROn6dOYdi6qKt491muI51eJcBNlp+yhrT88tvV3y7aMDckH9L18h+qJu8GTHFg4r2c6YEJGo8VvzVwqMBeRBqN2q88+vqbN1H/bPzoYbwS8fiSh0wzfavMYU+LyEe2+5ufNBZUIcg27yHApJGFITd86jflo87q8j3+8/oEoSV2awY103orjPq4ThoQJfonuPjW3FAtEEM7hmEVswnu419Em41e0DL8z0CKhzO5c1NGib+KnWiHbSmjHS2c/e8QWPGQYWvwFGi/1RuBw+bRLERvv1GkE6mZuUJHk8tSthtdgVZyS1kblPLyr4qIySF02mbubBxeq6yypkjh1zIvH1l78myTyWDv5N4kIRPreyHwD3UUNhk/QR3SJdxP5OduPikcm8cL0w9B/uTBEHunL/fj9cPZ37vTAZX3WcxqKyT+8Men80sDi65iS+dFsJDcyV2+6J6TPx3yK+lb+mH7Xab5jLPKyxGvz9NcI+hlBiw/dVrwwsCTv0DeUjlsA3j3mRWWaBtrMxQuT+8wrfX1qb85835uF9BsxiiyFNQQEE3NSEkYqKgpU+GpvloAxJoxdFXVCyTpw1JjtUxM57G1c6l6jerrlukNNtKJtWJ4g1+SreJ5JnZP/LGO5wBfZxAd8I/pvnNaM3JuMPpbZLj3v0XV2l+Bm435H53a3dBOXZe1Mhwuq0cfIXWNe5LxPKjG2nFNL3rxc3qMqbp0P/WRnrhscF9MebtOf7jy60twivMoqLsrPyf6ARt1lNi38X575kbFp7nMRn9pVg4Q387bqzJbMGLhL+TKvTezIeVkkeTEykjIRp86nZ69xY6uPPyrE+2vMB7hrIxK/BCB91xdAr/9yMJ3ZbTCoquOOjmn+Lfc4Q9UQ8V87CUccgNgOZtvVWseSZt+Og6FmiJEkbi14c4M/LY/5/rIW1W1ysg0ZT9GmQ6dRBXq006zQwMh3JIMolpO1rxkmVnNz3NGdwziMbdeE6ai2XTmNKtOX28HO2JGk2bfjYKgZYiRvUHFMytq5pAkKQ0ewcRlWvj3jSUv16zTGYMeWVlFswssztF4mN+i11UPO47GjbjuZR4/2Amb08ke3/crfjEpuLV8jqchxZ26zeNoljZSBjn8nHprKAUf0Fn1L0Pe3KD09nNPM8QPs4ejLxOvFy3A0AjsPSFd9FCuf47LVeACH4ZdpfugeDW91LFv5jEIRGJ3TYZ3d5KWDA+VQM8Qkrd+D/Cxj6e3g0Dc46kG4rhAfW8bwOU9TM4gFz9P0r7y++ps1COMElhsLepGV311u0+m6wT+j2x2BeSP3+7MNzTh5Mw11iLMhT5BT2rJmHfC/qCTe0h6a+pBbtiTD+kVZ5ruEjGzbw+sfp7yoNwEPCd5p3O0e0f5cH82XXznkv872f6t3I3/+xhC1LN2h9OF3tuD9Oa2SU5rsMB9//vZcEHVokOGBaZErYZv8D6FJ4tVSX6zG6WWelVURY1WJ8zXJdskpTiFxOGLD6V0rvG+WL3mFTvVdVVaB0pp02HKIYVnzCHff98LZHZ1KXjyjMGECFdxRmpMnFhEow/hxfc4r5cXHrPHP/1vt6F8/dLqMy128F6cPhv1ezc3APsAQXRgEa/Zj7wVslFwmfbb8zQa0f5zRGe2bCA59RIHoSgW1V/n3LM2HyGNkUIcf2cH8z99/D4QxkHOGJwlFELT18k8ANFguk46vFgK1iA4ZIrdpNBFjQZiCSSwaxbwMY6HsmFRVwdFl1B0f/WVubFFmTCrKuIH9KQyaLQxms2lUzdng153vNxmdo/b/Usx15fTY9r9NgjWWYZYRrigIukANhUEWJ4/RomzIzL0IOIF+OMJgQqP4s8AJ0tBy4DQc/8wCp95dzKNdIut6+TFD7whINzX8uHabAvs5yoZ/ZmvSO7ldnLDKa5fglv0EldFdfi52ir1eW5fd6/U/TmJcBOdSiJfA2OpFngBcamdaSZ9qh8vZYXYfF7XruBdzsWGYWQ35HDBTOF3PBrP287oqWwYt2YSyzVgym+XZEg1ZjzCNGZt/IT4/viZcirvAS+lENym6un+0oSbr8w7ee2yZtotiGGSHKd+MDaOlsrFjcx8iRHjkk2+oeLqvX/RLx5YmYgaVKbDaA1pYQ1+4kIoaBhtW3zWKt7kx0a/iJO/1lrUw55iG8LShZTkvkc2qfDG4Ws2J5Yygmv7M0u7UauZDyzp3w0NMTs6KCQ8u6W6Z5tiCtR9gMtKs4RCzhSODCrkQYwc0rNGRsgSWB4Ga9fj7sUDW3dIVFoW91XzhFgC26b909uZt5q8d91j9FuHWkjqqT/N63OCmhm2B9RXmiiaBHxx6QM5WTxHG8ZhVwQRINAi9IOnYJNbCQjCpu9YBRl0yzj8FCm1QMBv85EE7psJdhfcpmK9vWFQUgawvC2M0vyy22JLVY4oRxwJLs0Gpi9jhEUSa/SMXD5e01P+2dgCAQWYWPvQK0I6fw4sDg/0UnRASDHPzecN1UUf6pK+6pXVXgzUP/Y+TfGT4OE8QK4HtTCfwBKhSRrWSdKmOfDMTsjQLZAsDsVlc2YzxDLCSRzubGlUvk7R+lwpHenPClM6J18burRcNRn2xyp8fDW3A2wgWYqEfLy5KL8NKX7aZj1cnkc3HazZcNdmR7qqCxEMpyYIo6lJiScHVE9AjOvw4Cbggzll+QIIgMIMVFgZmoFgm/dKJzmbBWhf0aR346rhleBh+XD2OelFWgZ0uJtQ6sNNxy/Aw/Lh67PSirAI7dTCrdeCm5pS9rCU/rB4vRIzVYKUJfBaJTNuOVECUNDwKHHQ/h7nPNx1FD2hpBTHprSafDy98xGo6qrD2hJGvzY6nUDgNulTxyFX8sYRhEMirZAo0Gsdnl+HTKCD7AsCqu9WXAUE18D8hSK0BMjdANbHgpz7vtLKgizrm0k2QCcE36YGXK+AWB7YVWcAlgm4qizcaePNautc/6gSVcXpxrh7rFnekkPskL9riqSTgYn6qCFdvAZXi2QByNixe5cX5SA5lfQNPfr/Y98m0RP26elwMsqwHBCSD79QoaNIGCzBof94GDoDUyIsGQkT++6bIzycpCigSYfDanyf5EJEORRYCQUemmIDgMepo4GsJJgTg237EQuJlBqNjPpbTWhxCvoDFh4ztMUMXEkKTr14sx3XS9QthazYQkQQpAZzkVGsX0ifTSPvL2n3jGjFMOmIVPvPor2LTPC1oplzmmqNm9hVuF57pUxkf0NsEc1M8RXBcqYWG4aI5B/lhCTYTiIsRy6Tf2SNxgVgD5oq9jfgJIGZuU+bCF+Fw3g8f8YhbMqJ6LkUOArrCTYadQZA1AWb5MUzmgc2E8UrsgDN3lJI3yUN1GRf76OZcJ30s0f5zUj1KZHAfRs2zpI4LprHhx3CWxHicRoKil8UIE+BQzA4RZq0DC+Q6pIFsDcQ5yM8Eix4rBHhCm/W6p6u4LKx9oqfCQldDS4LaZGskZ5wxIzrvsulDXqHlr7NrLkUOml/XjaFBkOWvs2/Rd4z2mxw3UHbGaRXnkwDjDDtg+erPLiGpVnGSCeHM60dQsxxfDlwmM0OuWGGGZd7AfiQ/6pK/ZB2TQrC+gHklJgNQL8fyP2Mdq+TECObbddQCI0c4cGALgsUZNR5YTyAyPpboK8x7SduFppwukM2va/rfOKXPuFqpzkVWJ9NBAXyNA62HKZa5pQ1TsoE1MC3PKla//QOe6a3Kr/BYUvMy59o2J4xcxkWzZHl5zvYpUl5PtNcqFCVw40WXWsGE54hbkvCFIW+0ZLoIAyBBNpM+BxaXA6FVfKWWALMpv1VO6Jr9cyVga/l38EsA1oQ38k64mvtivu3/7oR2yUP7+vCiqorky7lCK8CYkn+ILxnl2tGnFm8NUIRZ/3iq/4xM5KJNjx4PZkAIZRoVshpw2RGGOYlyh9BYC6oS1mgzATawUONqLm1o4PyUtnk02GY32CoJ5sZ8t3S4jw/6GGZLWUUMPEO80KUbWS1QIlmsEGYMPwaASxNxRz2mwGD+DBAzHfA5UYbJZwfaX3GRxFnV29bL/PglyQhhJE8ZT7+HoYmYdzBMgdWxoJ43CFpK8jBPvKQKCoo2laAmXNBsLxl/q9mbLx6o0393x2J07q26ATz/ccYleJQ+ZYkcowwRjQW2YJPmUa6gRUOPZntp+FuvTTSB5Gat35pNXgc5/ZGkQAgM+dTHkItF46THjh5guZizRl6Iv+L03INULWEYXEyLXCKuCZstYUgMO+EpDJQbaS3wzDcwN6xnX06aLBnWvTBc3eovuouPpxS9yr9naR7Lnwj3BMz5S//jwtEAizc/Eljlz4aF+wQVWL4dYuKwa/2tCJFp/HWCiSBfsZ57hhfq100EWx/kMemM5m4BsFr+lnIeEE24bbTDz0I2ioM7autIvHwgCZzbeTOvD1iiSCadMsO5IJh5BZh2CfQLJepOF4ePfi93laRVHe82+POGqcwUJ5maSYF4O88mtLKaW7d5n1FUeVEvvJJjXDy9/rF7jLMDuo0rdHkucBe7J8XyviVgl/bdj+ZGjrDA+tM1vwRakENyhQFJI4eZBZMOwDKgQf74hYkZMMFofr5s8PHu63WGedl9vTt/KXdF0viKr+LVlYR5Nle8jGb1b7BkktkcECwOd8vf4S0PdBOu48dgbu7V/MUuBRLu+j3mDIS5nnX24fzw6ybOOAd5VnPGefHwkKT4FxRNlPOk75BtaPh17YEPBlFMOps7oApJ0RN04SSHgtC3mAppYyseUaZVrHWwfN8woN9joMYHFF0V+XE6lLCdc+smtmj1+OAEMumRHoylAOQ+/wWPhcBjGIoZl61V8g2r/F1+iKh/1wMlX2xwdOyqkSubaAXb9yrjJtTaRaWzMECjhTJaxbAsLgJqqzgAmg9VU9otWzjNbrIuH9Hua37mPWiFn+XYEigZkImlk6ANFkvNWkgfWY0+w4BRIqERLvmqM8bB3J0LLPHhJn4isZivs6Ru11NIZvlyi++YC2jJF647zrIgj1Gf1EgsBh9dZHe1RL7GOVgkVVAoJWthQ8a7AcQvKG0iyPN1Z8Pnu3wXp8m/0b7jMXoXZ4ezsKuhRr8noId7+HESBApss8yIpUFQB6sqDNpEmUw67TicEV++NwDh/L/5tf821vyrWeu3t3q36FuCvr9F6enhnGZ1EOJ6SIbGyalnjyn8e1Uvu+7zSFZfC7u2DRB9fZm1S6bIB3DTDBAZvIAZhblOokmOyUxGxKrvOXHIFOj8Cloq6ZjPhii/rgKQMhYIJqbyfK4qaX64zI9kn2ho1KgqU9sxumvGJYX+fUPWSqpqq+4mhlH975ucz/LAjSIh4Iew+XEaRyd7JHmyUrB6loGfjrfZcPQBfS8tzRFVZWpzRHdNN8n8viFzJFW1VXcTw6j+t/IRS0/AD+GET1YckOTJHMHqWQZ+Ot7mW3fnafpXXiXZoXWuNN33cfUm3+/x/TOrcqFwQyZKrXmrPufAV/3DRVZ+V5xMUST8qHY/T/PKbgzGfO34JOpaELYGFpdxeuB0dDXreZX5gcW6LZen04vJAbX8Vy2OcFrfCxZ7DE37buU1iQ1VTypcAxXdMX++R1dJUVav4ir+EpfiPV9d6w5VLf3rH6e8qDBzad5eMTcU1LiKJHe7R3SM//xt/yXHCIi/pGBDgkWCesY6e0jq5zGSbvtyeZ89iabDuoX9OUX3cfkV6I8thrpjKTS9sVHQhN7YYqg3lkLbGzqj/etjnKQXVRXvHiVjKaGD+wdJNYwMob6EvociqLuh1FxUtYBasfQQJcLvdvkZnhhMMQhQhkLTW+sr8B5VjzmkP64c6o8j0XWoDdcg8qCtArKlrWXLqQlrhryYdi7vUtmRfsYkpQxgQxE4Y/pSTQ+XcYUOxDlH6GEognoYSnUqSnbVuYCseF8CqqgrNBsBRS8cgWI8opYoeh+TBILG41PnW04KJDGsIJVq1BhCHQtM/gqxb6YY7JShMNO2AjUChUrfHZWxwtuK7+Ps/BCTsVJYIpZKxQdNacyLhgl97yyFbqLyuzxxvvIU4LTliQy7VfSn7kjbQ/9+VeigL4Ha7wt1AtQTCrZvXQnIfleoWzi2WbOlX2aeAFw8cjS6Pis8bTB0v+HdCjTnuXKwR5ZE0+FwZyj0NRRB3Qylmh4GH0Khh6EI6mEo1fVQO9Ghu6ogx0plkwgN6A0kA3sGKTVcDNcdQs9DEdRbXaq3jtTZpGgUqULQFlLlBv1IepC3bWxqmpAIUnPTFKtMTkOhkwEVx6Qs8ca5OzkV5RFIQNkEKu3mFi9rszi9OFeP9ea+SRso5UJNDm99VTU03JHn0ZLJTpVB/ZLiMjKa8IT2Pj8lO1k/baGio5bCpCdZJ8r2WwqT9t8U+fkk66QtVPTUUmh6ah+KCJ20v0Ptt0WGy9hP9ZPW3ltfuo5lyVQLWZZSw8Wb5AEvG4u9hguYDOICpjTkQtGzujezYZR8BKgy6XAabUwJ5Ye8giwpVSbtpCnWdHKLvmOBb/Ikq0r5gIFUUMcgocH6S7LtGopkay6jDVZHKBkxtljVk9G43SK8HcjqDSACTTBXDquRIdGKl5O1J4auXESeBBaTpzLby8lyDct2dTJ61f5OkvjZdKenTuEs6ktJDupOWcOJO2O+bDgyHVOSN1U2gKRQMVqk3KwfZbI4Wf/KSgq+lPXc+DVnUnNuxSU/sj1PMWDIjBNrpEjSAZmqpSW3GLW2hoY7Kmi/wApVBvVLFWv38Tl4Ytn+Du/bc4Mz0SGoKrCL6orgHVRXasJ6DzJYgr5YKogpSu9xfUQG8gu4NeTKwVFhSXQHHrLQi+L5h4wSPA6REVucNN6j4ymNwZkKk+lOHgdKw6sCBQciierqwLjn7sMg71igUH1fTLt9gzKEJ7LKOIok4NZAoNKdiQ5BGcVT0aEMPBcdijWdfCrknVBlUCdUsU6SIaigKMlQBkoyFOvmB4pLDOXPKDk8QotmrhycESyJWYevEryuL2HDIJIouqWoTE4bNDYJoFGcPVhZIj6cFzARWQLVORFHqp0TVDgeYFZQpfC8oAjMu2qiW6m6ayg0XTZEWgN7PMXJARrSoQg2qF2pFrdE16pvCE8Bo5YjMjj1foeqChUa5MoIZSfiEK1OyWLcH1HbIg2odpHMtnPZcldGaMSG2RJXGqUDOHOQUMKnDxJiS3YM+DBjwOxmiA0ZAV8LsTTSOyGWTCt2PYP2cj8SngAWmaXRobDIyxI3nsp7FUlA5AlUWlVDtltqs01sNf5XJdmEdCXg/qMr1G094h8KZwemFNx20AS6riS3IYqLELMbkO40XiGISKI61o8uTqc0Qfv7vKVPkP5ysqssB51AYcZDS67ngL1mlzsGSOj0N/e3qMQmmDhQGzgmtLeVcv8HgUJ16dkSGQwEf2GpuXdVk5vciEY1qfGRVO+hYnYdbH4tzETstz0hMznYNDzfjAY6Wy6ACA8yZgBSBU8AtYFPSP9iUjwWoUtlniE9gcHyUd4VUypbKJp2Jb6nEvUrkMg8HlgqmxHWDavBWAodUk8FJI72EevETlWA3O5Zav5NA5WtCXCtxwyoXeaZFsDXAKQJtZP/M1ZkY3UMDxWivmmZMgBa74IAbYiPMqhmFA8t7FUCuvpHVxKlKKjlIvHvAogoMp9/pqbyaQNpxuylwii1RKwnv1IpLK1ilIHXBc0IK14NyFQjU0gwdDB9mIKEruRVrIUghvVDj3p/dFE/Ekq5UJzzOxFD4j/P1gN955vqXJF38bs1vl58+Fkf9PBR5H8x4vee7opxF2j8jzi/BSQVpTtUezFVO8TorvWRFSU3qab4fnCvZJrvh+QFDFgTVItYGFY993FxQOCMMKnmacwXpp5ub2+FHb5SSORAtkcoC6kYG9Twlfyb1JnUAjycKpUqASuExAnwQoxpRPXky/ljG7Fvu6SfWpZOIQzw1qyRQvGGzBVX7mL339HBNElFF2lDGFLJszlafI9mlBdNv9YSSIMttiZSAfOgT7HkAun8L7ugp4KksvIJoPPIc88Z6S6kEJDX8SwWhAppQ2B5EDXpJwlIHmyiTKwS9iIj6m4yUHv9oV6+m1dWHBOCDymbg0Ll80hFK9wVD9CY7JlnEAXKl2rmlRVfJ0BYiXhLURn9CDXq2wXUAxIGUAX0sLZRgfK9rL3o7W2dSmiexL+43M0jqSd7Vu0uIqc6uaAsofdRmlD0/saxv5RVGE85sWLEuVf5zZBLXtyDNSEFCGUhFCE3gnJiL+O4BDW8TNIUG9K+ZYUSONJwKjDC0Aihu1iHQ9NyqQXakFOAi6TBNCALj2GvBihMwJBoCdCFuoJcLD6EApFHFh6BrakIjdC0YhLqwF41XXQIpTpEohAq4CNckJrS6BX2onbBHZSiikQhROVDUJCaw4/jN1d5mirFZAlCiEjHp2j2TFD8CTfRmhAZUdMiLBxNMpJJoQYb3qOvJwnc4SAi77tFO02pljFmFRXCSeJxNCJqYmwoW4IcycBWlfFDvKtSvhAyq6j/wAkCqwRcpApB1z2jxbQl+jysJnRKn0Y1BsvrydE0l4pUcXCUy1GziiHQZBIb6K7x9LKI8WOvuj7Sj1JPAFUIpQghiUhV6lc/4hJner28HFkwgZnnAIPEsMu/o8gRHZ9IIi9No2GZiYI0sAxGNxJrixX9jmzbpHJYG5rRjM4zmmQm0s2qZixF55H1iectCVVkcKoC0vk/UmECdJFacPwtV0FVtomjCGGWwokHxQuLBv8KuSMHXEHhwTDakQOKTsa0oow25kk1rd4N9dJQjx7XRaijj4Am1wFP4ldwIXrbUM/TAcrAv+JOWiTyfxMdXtQuZl50g/dnj3GJ9p+T6pHqQRRcV8WbOExdPiogqSqN+OeuCGbeDu3L1QBX8CQIWFM6443CLXpSzSd63I31w9byaxfmUk8frVFjECmSAAaRjjQ51AODSNqLCMSFVC6FlPQhFkaKOJikEZPAln7Uop0X+kp+0TGParqQm/I5wVH4FZoPSnrXedF4cu+lI4pGQ7tyKVlCT4yDNYXvqzL6qetGqPNI0m75eMI17fmYOK4agwdShjF1QHzadiarws6OOOA2GGwprf/xDu02wYfRjehsRNAMl5NrdwRiziR6byBPg8TNfDiAcDv7NUGBPShINTPkxCEmx+yqUGwYpbT+941Tq0EZGNrAsVtT0b+CjCJf060ZRmN2QJAi8LQkajYILodmFPjR68VMIQatskG8FW1LgnH7Rqu6c1sIK1ubWFULnQFiUB6ll4ucOpiZEEMP0S3QpUGUofBTkVNrhZJIA4ixDJUogr+bvrI0bkJx6u7rAaZB4Hxaozah74OoWP8RN6kdbIquTZ1s/mAnnTJNyBUEpTomqlClMF6rih1gOj02TbQVXkXK5aRxXa3wSqnN1o2LVCMJ8RtJuzVWqbqdSZRi0i4TJlnVOBz62H0hZGgoJ7eGYQMoRHfx8ZSiIQSbXHCOUrF0cQ7yFlbkPl0L4xcLiSyh1B8kuXvOCmloSHV5hpkx4iu+XSKR/69VeFGFY832rFT/0ZZWCfbRdj2Z9aIWK4VsWhX9d+UqSavag1B5Fm9TfZqzeR1H6qYFYg8v/nMS0S85xsXT6x+7xzg7oNsYL8/7xFPA4aK2ksoEs5mwWvMLZ7liz8Do5FzNYReYe2ukEsgfxtKz1OsSW5L8Snl9o60T4hZHk/yLNGSaycufmhRGWVfFv2meV0V9iivtkk1CGXLJJqTuam6WpVm5HMTv8mJFihtxkcj/VbiQ26upKU3b5fiswdhaKKhD2AlpKq67/pmDZ+CzGbOiqyI/qvShIg+hEDg5WGtUlLm+RqviPrdQBEW8djVQKcYiId0YZA/l5CqTBqZDaw2bMseZrB1ZG55VogIFSBcCD9OIDWcBi4AcZYD7lGldhXpkadUaPekypanbEo//DHOeuTjdsdnDoussqZI4VTjUqirIhXRzpoVzpN01/neavGfjldE5DItd6fUirRtIXGVLsCeyWeI6xxBAdFY4ffwfmFou4MjgP1D2u6YJbUI7F3WoDXNwg8wbYj8GuN03CZmUovq7eJlnZVXESZ0MBS/Devm6gFf3eSSrrzhp8t+XXt0OUbOgLac0kxW9B9VnpfI4TEyBldLZmoaiy+VdtPqovF2mYGNSfQGnF+NaDI9YINNZc8ChSGE2TrFD4DG1uno6NfPGgc1mEJtKzmY6+kw+N1FBI1sMjycgnR1pQ5Wnbpxih+huanX1dGrmjaPHzSA2n3fP+Iso5OsDPgC+2p7gKyvJY9h8HnS5CT2onQ5KZ6BJilwtlFX8u3lVQn+EnRZm2kWIW6sTr/HMFzqjlay/uOYIg11aexT9xbOmkVrxeJRR0Ze9eNZkAW1/wH/iDSve773P9ygtya8vnt2ece0jav56hcrkMDTxAreZIZKveGi0o7nOHvJu18dx1JF0xV3SDVTF+7iKL4oqqVMT4OIdXnWS/LbkfKZOX/gF7a+zj+fqdK6wyOj4JWX2lC+eqft/8Uzg+UXz3qD0IQJmM8EioI/Zy3OS7nu+r+K05PbRsiYusfbfIPx7M5Z4albo8NS39CHPDBtq1fcKnVC2x1PuHh1PdZ738mN2F39DLrx9KtE7dIh3Tzd1kHRy3CRrRD8QrNpfvEriQxEfy7aNoT7+E2N4f/zxP/8PMJWSPsWPCAA= + + + 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 + + + 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 + + + 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 + + + H4sIAAAAAAAEAOy923LcuLIo+D4R8w8OP50zsY/ddq+OWLuje05IsmQrti9akmzvvV4UFAuSuMwiq3nRpSfmy+ZhPun8wgHAGwgk7iBZpa4Xu0QkEkAikUgkMhP/6//7/3/7n4/r9MU9Ksokz35/+ebVTy9foCzOV0l2+/vLurr5H39/+T//7//z//jteLV+fPGtg/uZwOGaWfn7y7uq2vz6+nUZ36F1VL5aJ3GRl/lN9SrO16+jVf767U8//fvrN29eI4ziJcb14sVv53VWJWtE/8B/HuVZjDZVHaWf8hVKy/Y7LrmgWF98jtao3EQx+v3lxToqqosqL9Crd1EVvXxxkCYR7sYFSm9evoiyLK+iCnfy168luqiKPLu92OAPUXr5tEEY7iZKS9R2/tcB3HQcP70l43g9VOxQxXVZ5WtLhG9+bgnzmq/uRN6XPeEw6Y4xiasnMmpKvt9fXuabJH75gm/p16O0IFAj0h5R+mKwJHtF65XNf//2ggP6t54p3r765dVPr376txdHdVrVBfo9Q3VVROm/vTirr9Mk/g/0dJn/QNnvWZ2mbE9xX3HZ6AP+dFbkG1RUT+fopu3/6erli9fjeq/5in01pk4zuNOs+vntyxefcePRdYp6RmAIQUf1HmWoiCq0OouqChUZwYEoKYXWubYunsoKrcnvrk3Mf3gdvXzxKXr8iLLb6u73l/jnyxcnySNadV/afnzNErzscKWqqJGuqdMsTusVOs0uEtxktOkaPMzzFEUZMEwNvvIsKsuHvFjhggrFePi+KDuEk9PiMqnS6Sl+mK+eJm/kE6oizNGEbOUsjb1DZVwkm0bgzNDePHP1MVnjZbG6zOmCLn05+RxlK1QclN+T1S2qfLE1WP6ZZ9PToWnqexFt8AZbYSEm9N2k/sVd/jCaN7+RH2LmRkUA+VIkeUGlslq+GwiPy+g28Fz89nrYfdV7cvR4hDeb27x4ctmZo8dXDIb95ixvS7Mt/+2nn4wm2ZK93iXlJo2evhCWt2FUY/65QFVFx2LNO1gk3CS3dUGhX7V49hzkzEFvp+Ggb1Fah9gpLJultNJT14lnP+ZxlCZ/olXXpgP3tjga5hUQ7tlY3lYzHXZTC6hYUXZbR7eWLALgIVOHMH3eF3m9mV9A9+0v1fRc6/tzdJ/cUgaSzOTLF+copQDlXbJp7CniyroawE+KfH2ep9CC7qGuLvK6iMn4ci3oZVRQ9dpNpvTd8hQlLZ69BJG3pdkI30y0XNqZaamu3ImnaB/X/qNGFyg/wjhUrQc4uJ2k0e3pGg/2JEmRhty/mI1Wc8StUt/zWOBDN11L5Z3/OdFVBVfKTCq6m8k4RyWVcaVcgHKQchkqA+xl40iMSqE7oeuunXGogyhoHM69hNXLOl/tqqP1MkeXrvWFjjCnJVldZ2l9m2SCENFVvczrGBI+4bQqf6EA6lZaEeIkFM5QsU5KsjjPUUyN+tYC4QLFNbHXveJx7QWBvK1Al0m2h3+Ti6y3v/wyRduDNXTylqWL94jyNirIsoK3dZ6Hr7gqwxJWQwprWAPutYhZVC4Gw7Z6+YpFtF+9zqt3ohV0UiB0gVl1Q9vzU54vo8fjR7TeeN96YUStIk7wcFOgrnoQV8m99+XTadlItYb5/XAFlY+mQomXDBMLJv7EYSjH3LSLHK9/e4FEqpX0370QkrcV6iyxpCrS+kRMfmEezOhA7sy/ZB/w+jijKr0ftoM0zR/e16is8LnkW155I/SwiQj3RHjdvcOM+bWKO0zkz8tkra17nK0ca/ramtyObUTSgMe0UYGo0o1KIQ1OKfZx7YOsfMDiTNqppvyqEaPjbjFFokjnyr1leIPMS5I3KPbyXCGjMJV2U5Z/rtfXqPhyQyRY6TeAKYy6zfLxW2LQ2oeWoE2fMLmoQUeh9HFQV+xiHHdWAgbKBhmst5xgEXtJCxZRMJnx4jAqUdsBQt1O0e186LSrsyGT9Ro12AKmmn2IbXWc4mWCCGJ+2O8S8rY6Gr2vk77V5rfttWeJ29XdQIa4gjzGs5xO3oqBV3rYhk7yYh1Vvht2h+0iSqvJu36wWifZUb5eMx7D00UylMFsTAc3N0ma4OXiS+0wFqd3KEUB4ig6w9VBHOc14MI9he0qDB99jMrqdHOwWuEjmiqcwdRhRCPxCkQk5ZcMPE9aO5uU1cf8NslcD6i4PuUiLKqlKJwVgpakkruJTvRfMWCDGiCWCrs/AGKrtx4maYqnuZ97VTd5WKCvYxB5hzk4217zmp6q2y3M1aDRiP3mYQQtWwoIqdh+N1Y9aqlBGIaQE1t/M6Xq8fEjUWii9KCu7ohKE1Mg1SlHVQOcBqMKwpyY1bKdIKwG1OuzvKzgsfXF4EDEUqHXAIhTF5tYT3kfabm8k+NiuJccjG036Zkf7iEtAjs3LhH6xRXbdukc4TMGZpE/qIkW7NoIBOwiDCF0VQJm3+WHqFid5UlWlR8SjITcuIP9FuAkvZfDAWNQANuOpLvqNNprBGBA/HEwcgHIA9qKwIu7nNY/wofYU6yUwX3noUDyS4EE2sshvcw6PUEd4pDW6zx71SLYn+nlbZ0kRVkFMt3q1ddZGtId+sO0gllsE2XTh28fkQNaIcbWaK/QKoS/3SdZLB5dNS0yAbCTDatdmm/maujt5A39M9kQXSlKNb78gW6V7/IMNZcfk7d1Ej3O1JLfeVt+kmnWELgVdhttDzNsgFyRsGnz5dZ7NbtIlZ3jIMUujgCkHR1D+RndO3JZ783vkgLFRD171eLYb8/ytjQb5kRhUdQ3pGwNH0EcTcpgzoMP+UdEiHhazhE0dXlXIGTa4M8BGsSCFhVJzDXm6EdTX/8LL7XL/FvkbeLdhtCpuRx2zvEpGM8Bxt1x7SdU3eUSE9IY5mqo3Ii3ZGwQ00MDpyttFevzLbsRSIY12iuA3REEEDsPQnkdC8f0cIkaaRFwU7ffhxRiSb0PTZXpZMZMUuHk0ulthml9dEdWwiRSiREsc8gjXpu0EGFu63us7HoomCNM++Utb0tmZAjrAjiVtnp9XaD7JNKIiTA3wtugBzmdcx33c2HxK3d9L/+t/oY0gBtXi2u/5hVrviWV76I38cYwDQbXea0cbDaY9fwXX1C/jq+b1SRGq/5KJrTHgOzySepa4LSsD9P8tnf4sl7SpHb5isGxHa64bWcu0eP0Xmxk8MSiHM7pt8MIshRD66sBcGAnqFxgJRDIm42arnjwEEGw3wvkbYWK5wuVitey2UB5lF0Mmy2ze6cm6na2Ft8R65bpqIznVXikl9Ht9Emkl4n/M8yTbWrq0De2bXmyw4wscMquiW4EmX0KtqI47Ya88VO5ZQYJH+2xQiGkQiG4XY8h/DL6FPmqjqtzfBpHDy7nuKiKcI9ejfBsh+LXdmlbNkh1Kw3hZlFSz6OKudVzI8oHlG5u6vS/UHmJOSUNguxz7oJLHi7XTD8cK8dy61UPyUTJQQBifBwIZR3cyWJpaZHhw/mxwmIsq3PFrWfJiKQ1xABQs2p+saAd/X2lz/68oJWGpDn/KLQCt0kzNdMEviGk7LekTDD0abZK7pNVHaXpk68esswF2MVdjjXhGfXEE9y/OdubNSyw41q03qQBIvrC5mPp8IS7h9wfaMJErtJjfheXRHeoYKf9Rnu6qNfBjvqBMHboqBLFDdq7j+GQ9h5OB/G25Zq++FFPv+iirL6JYqKCFHgfrbReumGafV8lqgUeppHT8n1yUx1FhfdlT4cnhLZCYqSSAn2p7jDFm+0kwFNgFOeg/GjimoMItZroxiQQEetGB6sV1wfvMZ2W7/KHLM0j/3vyFo/vzH3N0maBdwi9x/ipc4v/ciPgdMxA1KI5ftwkzRtK76InHqcZChooTlGEYPsPUXkRYa0JhZrVMTbLABXcG5JB5OC2QIhVHF07M0I2i9XktDwnmauLAA7RPaKjpzhFTad8ZRyL8QwVSe69+nqcdO+niD3Xyil1KT/OSJUA+TBCJrjF8jQhKy9KO4yNM2Bvv0Zxsia2qbMC/2ofSf77yxcXJNU63j8duh8s38lpeVx6k5N5PtCXcbCKQ+4ls3u8NDG6xufQ++RW5fGPf9RRa+vwEtnNcY1iPLiPElw3SRmsnu5hYE+dN6wkCzjyj/lDM+o2JYm382BeJTdP1CBwkhddHw8RPn35IT6M4h/0fVDyDrh3Gh9yGiQYTxta4hNIf+j11ijooR/PUrKu12EmqcEYPYbDSAmAVi2yBM2grOK1QDp4WD8d1lU1GBc81haB/p6Ud2lSVmGQtos/RZh5sVwf2W+cLz+xdk7RJbG3fWmEJPgO9CVdTdtAezA5otewE7VxscF4otRyIOY4e78Gcnnt4KHA4mr9HBwxdeas46xCRRmEv1qxNcKMJmaKVrDN2iY+fFwmqFmT3gIf76GorA4qLDqv6wod5evrJGuv9QIyIe4zlnk01RtxoU0Tf435O0pu76ZbiuNzTHD035PVhNg/TEubfqfxlSc9Ij9hEu7GIlxwRZg0iVvkJi4OMLlHxROpbGk96dRArIKJF606TbpE7e4T5Njd4/uEorIuEOmSQp0M8ipl3+bBmnWH9BWxPVryY4zajLR1tkoRvdHSWKDCmNeb9s5QQfI1hTJ8jJASaoTGySaaskYsT33ZbIn9Fgk6yXSlVy30ZT7YygfHGDmU4AyjALV1EWSlgcrD52oMKPjzjMplDkpjIEf/pGZW5QnKBLCrlg+k3lUyUJkTkhTelvjdyaQxeGl8rDqzmMJNjAeRDUCAc+w4e4mo7DsLqOg/BCYbAwjrOI6zhOJRDqGFUfSeg5B1nAdz7HN/CR7OrVDpc+fbX3oCvWnTl/bHFmX/4SqK8agryManqeU43ibcQSFQR3CiPGWKpeKUhXGUpt+iIsFKHXSOVHZfUU8xQQa1ZLNkUtVxqnjUVgO3GK3tEJ3HxerpSh4cA4pMyJZLuXAEZJ3IjLsbVnSWBxW7O4aQdpgDs+0ya8MButsXQ5whFAq8IEJ4+TR3qqJTMoxWqe1/7f2a5W1pvHwnetO8m5kQnhpfS3JWivFwA/ikdh0TMQY3anVN2Z7KrW9+lwgtbL2ryqO83uQZm9jN2TIlYJos40k3M9Q/kq4yXx4dMBHi2fqB6I7xl7nmKCYevNkKqrP8ACdsS0pg280JMDFMYZHQj0EwXhhbJFpULefL4yz7NuEawDggQPlQQGi/dDX9Y/LOEUYdiv1WLG9LsxVPFHBD4l8Ctawz4M8XK3GYV5hPZw3rSZNohteZ2mUUKqBnHzAT6JU6EtEXygGwtbRZ3lSRSKqL5E/k1/Yo9Ke8zPGOheKKx+3skdPi+TJ2HpssfRY9kJ5H2a3SZysMDwSOjQvvv7DFkUPbdgse7n5/m+/TbyKsaH1L0MOnSZ5vCH356HBk0V9AAucbYytsI6bhZHX9TVUHxCSp48rE5HQ8gF+Gkq4H1rr7J7RKoldt/b3irhBfDYkOkywqBpf19i+ThaTzM1zTJC7KLWySWHyUnyQpytSng58DBT9+Rg++cvu0vCyirExCREhNk+LS5eZWFCEwhPbmlpM3buJkhNM/58hevJimHpnqWDGfbuS0lTqvBsk1mmzRWK7gsD4Xur4qUiNZX6kxdsEgt2sMvv0qVq4bSq4A648n+3mdIqMHyQMlq9ygyW+t2peYAmXp74NSw6Ab3n/ABwjm+WZLi82Appfw02caZRqNngjz9A/9zNZwNxt6K1Dollt7heU8HT9iQYV1v2jGM/Lg/zDdDZL0dKy8b3Lbe0ZeuB7vm7CI9ruNvC3NRmAYV2B9RZ/mxQf0+C1K6/lbbxXTjznZc6aOqXDVgm3PWYPPtv9Ra8C1XznytgKdtkYe9r7IgiVuiEMGAMl9pRZOOam9qJk5w2SSYhZk3XQ974aSFbq8q9fXGfPqtyuyNnHo9tzgPMerF/lVSMcUDY8YRgZxta7YTUIRJSSvpo8YUtS1vTfhwpqmDYWSGFa0oVMBrUFMW0YDMO96ELOQbqpD6h0c6r0aIm9rIJp3/p6O6MEQUf3e+xG1EqsdjXuJw66hkS6NLFxGlmrXrYEc9lnJAbw1OUz7dSpvK5CGH8p37LQ8wVpPPeQ1XVAfk7/z13OoQdjwACyPG+5/SNaeCDjBPu8S6Kzrb8gdno089pcNLLa9fJhcPrDk/kvIiDG3Gobojyup4/RHf0jWobzCBPLDJ9mASf+DyJJAQmQvPbY5JGRWJ32GE/ZRDdsV1bCPQ9jBOIS9a/4zsg+rHOPGm7voFgeVC4oCCBTitAFnp/E/d8B49zqESvgEOYHAhG8kaJAU4401LED+vCYSqx12iGCsSVxe9QeC0HmkJIcEw+xTpgNT8YnDaNXoTEmgwmJJFyUqL8mpHqqz4FSh3ctNW4Hn/eZDGA/d7ZFg4RLkzbDU7VLpGUkNP7kIkkndrgTPQB+H6oIMdMExgfALLvb2Au+vIJpU3AtLpgWWHC+LfJatz0ntMrr1P5ZhJPul5by0QtlxdXt1sMyukn0UzP7qw5uK7Kn+PKtAvudleVtzPPM7y3u7C70lHPSdG9tXY9TYLB+J0bgpW74Jo/GH285HLA7KMrnN8Crq7krmeBxukZdQTkv67KH/3UsYc+Rw0vzPdRpAa9XIvHCPSFJb55e6+nJDkVK91/6WxvthDFVecs2bGaZVZYY14/oTOFlMl//dYbCuJlbTPOmqtjUp1E2rugxbm3jdWEscD8IjrJRFtNf95G1pzjETBXYaeVeEDOvc7hhSaG9us7nZ7SO+p7HwR7D92pO3FUhxatEEu9Agb7fhT8xD7JNpYadlm5HA24uE2ZeyqshTgm0rEyPZazQuD7sYbuLOCgvPcy7jAe2upnVsR6i+1DAYKo+YhkxZPdBDa1xJJa+WBsr6pk/5qJGEsPAF3Ez2u4jBLjJPnvKFrpImc8P11ZTajCih1SWKds/t8rYkdPP3V9uhRaTxgiU5ey7+qKMClee315MPqrHhrv6FV8IaTZhArnmtfYaG/OKYJzsSUtEQQq39mGQ/mAx1iySysdW2tkfPMtQ7zZQ1px1peHjQMQF4h2C/zyjWbUuj93XSt9r8to0jKPsHHr8W3sl9AFSTbSRdW1NlQScnddS8GDj5WAyynodp6PgRj6kMr69C29Szya9uLPr6N0k9NO8ex178ydsKpB3AyVltw7rcE/KGueC19O9wz6LBDFTzou8YFHrWl4UQlBYJWMjrWcvXh+U9VJkobU/xzQvyH1C6uanTDJWl/wleQBlMrLwgHixtV+gTNO1UtfvmS5MF3PTOdwV+j8p2gOHcGkYdVKn3AoGvuKqCQq+pIVPhddW8GBDv1eVRvnbM0U9qv2JQbAePtZ3Rxa6HMms0jaHH6a+lCKWdErVJub3DCDI6M7FXA+DA1lC5wMQgkDfLOuYp7vl1n5tYY4uJsts6uvW/sw20Bu0Dc4sQy9Gy2RMquuZuNVjihGUecG8ScDQSwjuJxMFmU+T3aNXiOwLcbG3DKPMqPNLAqTOCJpjY57RxG5l0j+1kqXSPpVviADXeYEeF4O46hrA9KzH7M+w14KQF8Lf+SlXBSQsgBq0ii9KDuroje1oTsHaOYsy3LqenLgPQKxXivcqgEEItBX1VhuM1k2x+UnsomeVmdDeJMhwnbJPt1duMLX8hvEy5b56mDuIYH1PnaRD/eZ+sUDHls2RayxgoOFWC5GqoOUhSowrCFmBWy+vEdZIX9fosL11MBLRu+apHsReh8rYu800ShzJLh3C3nf8wc3p2sFoV1AI6sX/ILmRlU8qXfkmBwkQsFSQHAGKrPVIUlG01XWQBoU4O5YpuMkD+8qztjJdAozj2Ek3eFqXS1kg0MlshPJUu6ut/oVglHf82TYDS52YhlH7d/5bgE5inASMqK9ITb5+vFk+oKe7wNXLXzhD1PDaERjxKd4RxMSxrORinPUHXwRYI7B39qepaA+C/BXhK/73gl7dFCfS+yOvNxHnsTPMmb42HPyjVKV97yuQwmwORoEHOIc9xi/hr5YQe1rBcml+xQJw0Z8pgac4C+EvzthNeIp3i2Mt1hZB59tL4Wa7xwPePapEB3yNZiQv+/kgqT5zERTu31pKiSaHT/LcXEvK2KIG0Sc4CXTiQtryjIYI6BHu/6J2kKSZWawj1NlbgJbhRoDMg7wVmizpYR8JgO4ueyG1yUGSfUHWXr6a8SZJwzFFdFCiLn45wzRkabRo7j6pBAVZ7j//deS1cRo/thhrC8PYt0udODChWLurrCovE9DSL00uCNpSrvaqx48d5GrskjeG5iYk701wjHDU6z0hbqTPPCNvGZh0ZbshiKds3NhKOeBdJiIoQpScITU1TectTE1je8tTUbvGr7j8DSrjJmbRb69O2QsXKZE1gxbvOVmhlmxDWupmHqFid5UlWld9RgTDb+fv0Ht2h+EdeDzHecx6lhcZnSaHaqR6hfNgPbm6SNAnwWmR/PthMTgPqVE0ONRj9UYGw/DrCvDXWnZxZCmMiGKafSNLlWdRwgTYTvkJa/kAr2ZRMOsKj+/u3szR0/LhJCmpI+JRnQ5rvmdr8LxRNT092fTXJXd+h6yTA+9Y9qoOY7psf8nQ1A3+IDc/EmEzDh1H2Y5YDMNfmLCKGbfP0aM7maLjKkCdjjiZPr6MZlIt2N6UKYB/COvW6r/GBoEj+pJKGpqiIYvJzUA1mb3qWJSNr/ByVTDLgCSX8hhjS5yW42OhMo72or3sdfd4hn9VFfBeVaE4D/lmUuIYOdhaQ5pg+/by0zZHzORY4m7q5LZndahzogevtvZ5kT8LniFy9MY/1Gl1bfIhIFqLWivM5r/oXzXyJRkJbNtXlXYL7F+HPNFrpQ5Stvtw7nKyk96jjKyDwPpWu0SsecLhThcoFNwwQyNbhT+mU2LQA+SOOSyRdc/ZC7AxPX8voFn1ISvL6AthBCPCqvSEeOiuHEu6qFaC2T3m+T27oKVE7CAjw6muJVt+T6k4YjB5aGJRBFdvB0VokEFTB3zROVOg/VyR0li936hmWGpI82n2xpGdDEdwzpty2Z+dohdAarVgJedyo90BHWSgtU2iBhcHoa1g/XYv7Lo9E7kpFso9LxAcvx8UuvdoYSWMBkpd2HIBE6PFQfmk5QUHo8IRNg6YEBebeG0beVn/H6mk+bqSarw3aS+nT7rbmO+xQQ7PJdh+FtaKGttUXWpkyhYJg1PGx5HJa6bC24OrlBmHbL3N5Wx29fFfoaAcNgax9EmCiW8uJBEpHTXNtd6ihUXS7j8K6VEPbChRO0ZpDaTcaEKzTeQkcfyGzFyzytjqjHCMVoByAZtIphBfexH4Qp2XXWfoObBTA1NUhPMrrzUwW83NMjA3JXz2LSbBvbZ7sORcoI+fYOUbWNDXPsD7hwxZNwTVxO+R5uY47qG1yaWPuRAYo+y3ZyOwkbOCmo4ElqXI8V7I64ogkoNJdWQbvtSUzPfQKb9knSDU5jGMieYeeBNHzzxZ9NYfXfLOkojnSp/YP7hua3B14jtHMMpKp3Zc720ajI05NsnFrU9NuER/aeX1nu7d1AuS4PS07ZMHU+I94gWTDu0WWRyAirZvH2sw5RBOoV2erFGE1K5reS6IR8Ec0CV8o/pYqSgdlmcfE5XnVKSvwrUdALUmm+em0Kn/7rsVFI3hdA1xEGiukiodKBtxn4kMlQqGia2chHirpLy499UqCYq9XytsKog028+Qtkew3YxrnmQ/OHlt5ptVIArOLfXC5ARf/TosNuFj3SmkN4NuvQQXfBYrjbwgeAsdhlEbZ8NaX+13QxKbbuQxoE0kGpSMb5O0CubWp4ASxoQQOf+MU2CHIZDQBr5s6Nx4HWdh51PQ+RHv5J28riA5yWUTxD0zxmbzIaQRw2NMd5Rnk6pv+DqXJPSqeHKvPrvsYO9fxi17ie2fr8id3BGUhrgYZIHZwBCB1AhxDeeWRYlGGEEp7o7t+RVI6BfGkc3lIL4y9XLsYQq4D2XqFV4vjeQWzY3aO/qiR0xsRrYFghGa/DhTrIES2smCLINRxKczl0zmKyjw7yYuGm+Y3g7T8i6jZO8gFgV8H1JFZZlPLP883nbdHFd3ctJEXU1+srNZJpo8b/ttPQV4bGcm2MEnrtih+z/VEzdBEcpaGIIBzJwjmedjMqXJ2hDchP38KHtN+Z1Ot/wA721lUtPqN5Q1hc5/nUJGd4hAelsEskGHcQpbJ34SXJCowDxG3iMkslmGUjl0QxQNzS04XYyl1xcKzZwwpGHDSkMMGjTEWGoL2EimQvt/OFlnV/a7QCnDNK4PRdznIpa+Qv819ExRQ7XdBeVsax2rTB4ptLbboscKf1pvpM5wQB+g/6qQI8GY5saER+JbhQ+E9LS+jx+NHxFDDFRVGdITZ4DYvnoJtxEd5VhV5GkLXCPfewWlJPb2QNcHkUp+XGjSWDTYTw7BXgAwb5KtpHcGYbFzRy74MtxJQCFN8e0ksb0ug2MSvWU0k2qkOfbD6F+Yb1twRXJtuLuBmaOi0xJjwskdxALdSDwloLrnml1m8jmgt7BzvAuK6IHzdJl/yjkOXINxLLXlbPMmec+YJfqwSSyPIQ1diZdb2aFYHsEYaVgy6zsItsP3KUjDxU5yiZmv2XA0E0Rkqktw7YQR1faH4PH0NLyo87VJ/lbkOA6IfTYhkh6dZUiVRus2SjO2ikRS7GteQi64RoFZejaFtTV7S/X9usSxmK7OU505imTI9/vQxv3WQyLjWLXEIYrDspbG8LYZM23TrEi4Z9XYIJo7M4FJmYK4E+GH1KsAEuaSCDXp9wDYE3RxA5crehrmBFsgYQpwQ+L1IkbfVpHHGPXrIC1VG6TfTGGo05qGJntc9zgiwpY5lzMd+W+F+C1S2hcnzEd2j1P9xzryowoXvWLZ+gsFnS76z6bOzeq8zR31C575yi74W0/tZYCZHRYGKOdqa0UFCw2kF8RLLYl+HjzyryBkiKlXPG/3NiYvclBiZ8qJUWsIpK/RpbvbhCgeR3zwe/kpAtZf/8rZYOnmnIgp1AKIz6G/W2iTxlppqlCtR4F9wXcqhhFWqAPVas2cFTcvSb7yuC3aMZ79aFas1hHsr4aFQK3VI1uCtQNbX/0Kx0pP+l8nclubXWIkHUxTAy6g1bx8+NS9sBUTYp5j0xTmRDGXZGJSjY7FyNYYfBKkCTJCkKlhbkxKb50TfexZa2vcBSNdzBtLPxtS9besg/fu65fBzL/zlbbXHTm9/vTAXYqFd9VSPo/TscQU8jCIUiqZVAcIvsie+Q6s6RZdR+cOB7Um18hWLZM/08rY0Bs1fpjFoHmB2Udl3TJvVbYx5dvy4IRypDvR8EyaGsLkFkLby9y0yDoOq7+ZLdlwU/krOZ6zxndcu1p+PEQ2+LCrHusfZyrXVOo4xn7i2y5JtOgY7LT8kK7ywfScI/3lLVsUZwnJcyCNqVldvDg406PP8oZXW/bCTLCIeC0d5RlwDiLXwE0VH2xqvObgDDgK1eVAUrVwVuTjFCn/u9BbHBfGAwOhe9Uj2O5q8rYb0vlpcg2UZb/Du6EC0K99xnGJ9Ng2oRrJ9k1zWtzx6NQZlr+shCODCHgTzUi2/Fj6rMH/V198vwOe8AC/S+nb+VkN5ZH6Mstsab812M2D+qBRhjCT2iTklDlh59orHtF9UUy8qPKL3RV5v5mdu3PL8jY5e1pvP9uxwi2C8+i7v0Bp9i4qEoHKxjpD65asRmv26k7dFCRWAc2eJOvRbDW/DnNem5H6K20V3I1/K5r89t0/Oh7b+QkoHp6l0vDINZTkjx3ONR1iYFfwhL5U53gJZXD7mt/lZEpMVsD3JCz5U6/QwXzEq0HTOcI3zWJch+DOqHvLix+Sze1YkWDA90SV81Nq1/BNMUZzHj/EdPhUg8rSUM2pFFh1pI3BmHTLCK2UtJsWODljMtaOtYZ8mSJwZ/cg4cMmQRlDqsYxB/TIH9d2y3kvfJQWKSWTWqw7JfkeVt6W9X5vGgNhMjOYh3V8myW5q/nrd3123k485QeBPVhMz7ElerPGk0xambe9jsk4wi13mjbbqfclDFlh5t2j6jeW8zG3HSGXbcbbCMxtcw7I73nyKaKY4z1NOi2UvmuVtPQM79YRH7cMo/nGa4RbiH55hA0dRFaX57SsJxj2LBp1g8MY+RJLZYNHXC7j8S1gPdPzXwQo6urZCyKykssbOxOSkGlDjcZyFSFX6Kcrqm4jaFIpLtMYKhdvNUStLIHR7QSJva5n96VuCHnBX1SF4k7TsqrOaH6DbjKEBeJlHtefjPR/PxsetcA/AxhymPRfvuXg2LqaKEnmKoFWCnJl4jGjPw/K2+lPFm0Cnk7fL3DSZ7/hFXpZYB0/9uYxHteezbeUz8320SU73CVV3uYt3aVu/fDVCtGcMxYSyhGrSQun31Um2N5Id5x1iTGT+PXDiwX/UqEarY8xP6UFVRfGdY97ZNrSjfAUi3POkvC2GYN7x+rgneBLIVdeIqYgRiJsRAdRWNKqVwkBeJScJEM2g2dPxfskH/Zh4e3hp3xPlcPuUrBFpc4aW5SH9iSRoA1zoVw34YFCUQwm2RAWorTWUQWXR91Et3RCYr4YjYWt42UVH/QwiqffyWSHpiiQvvF+TI+w0f8w0aVXrTRIkjDmff3CX+SxDO0eb9CnI+IzamWVMR0eTN3EYx5O3oc9PFEgPIU6k07uQhvR0ucDC7bJIvJ8PwGiccjI2m18c57X/UyW4EytyeRelaT8LAZKb9DsznOAksI7Ap6431ypMB8SSXDeiqzEwOJARjErHGQN6qTbjbrnrNiyevXKjWaaq48Uvkzh8toZX3anKsHF9AMT8IzzLCz5Hhq2vYolnMgyBrM/2ZalJlT5Ry010zUWZWot5kXjv0E2EVzXeVelawLXskFpc6683UXLr4ubWy6sOx15WydvSSIupAnO1OuZEDQfSOZd0t/byTjd3E2sWkYc7Qr8MOVT71ei8GieyiOJTZaPaNUkD0fQJ/5/XGTNUvpCQx7vAMSxep0XpGfGNw1XEUPutV+2ftbWNZeVn9FB+RGSpezro9yITxriXnPK2YIp5J/pf6CgXRp6EtX1NGOLyCUUlZtrmUWyvSNgRpv16UawXtaYx0atdCz8adk5IN3VM7HSOby1zv8PLNyvddhhhpfTI9otlv1ie02I5XW/yosKt3iRO+blG9feLY9sWx0meYs5ZqG3MEaTNMBHLYTCFSaD0I9n4deQy+oH8MDTZBL5k/udMvEhOEpSuyF8zJGvquOIoz26S27oYewtOlyIprddZn6pg4tbOUYml3Gl2o/LcCNNU+24ERk5ejgiXI2n8qAVwoTsS/Fdj8OFKVw4lXOoqQP2e53jKYvccFcSls8vJ9IpBtd/qVGI2RKKKhj/UeVjfTJOPyChHxkS7Jk3m9lgtpEtTmk/yCik/TLoQTy0fc2hqdX2aes+gjZFtVdhidSeSLHazJ5n7qjwSYfkObdL8yTFqgkexl2jyttpdyVekLbOoA2m9p2Uz2WHMrf8cVGjnPF8964Y4Gxi4vARKm6n3cAnTkDbDa5AUr1W1uSyirFwn9NWqEFMB4RzHE1HRBYM5HFIbC1KgUHOTu0uDC+RA00PbM3hKNeDgwlzMklWS3KNPTEZID38sF68uVbqjG1lgEr+pXvXAw5lIBiOciKSAfm6uj34Wv1H9vdIgb+uvafEjv1pSTS5wTKOogyS7zu8TTNhFA7dPy3ar6havpyNNIHOonwlK5CHMO3Oc7/CfZFucw/zYcU9j9GTDhJ+DDfLLzU2JPD3OqSOWH4rDqIrvLpI/PS3rZ3iRN892bI+bGnnMkb7SbKM/BgsLw2e1gyK+C+FqQ2rV9ic/qS42KEdwIJWXPsaHTWkVt2DG9ZGOJTWuy6EkqmQA43q9ZlVJTV6H0/IkjW7LflZ9Y6pkLQXTQvHqwFI6fSJ7GbNcx9P6Ca2vUdEJnTS/fvmCPlX0+8ufBBYYwRL1CKuviOS6R32lN+pKzbG0hQVcL5sJUU2S4rDsPUGj6xF5SwtO0EVCROcZDcQym6ZPuCPJBneXpNInAxxVfmMyBQdlmccJJWG3v9EnABpBjbdmujyvupcPuf4fZ6sXzbJV1hoW+WAZgSrgfYGOCBMTL+nfX/5fwvBNG+wFItNgNwSukTfjMeFGvmTvENnDXhxQnYvELpVxtBLFJqboavylXTQkkAprUCVmjySrxANiksV43lKboXBIDM+ZpJN9c3wJ3h9QRo6INnNo0g/2nUyxP32zHDF1tPvtNcOsBjyM9atV1zdDBgarSLmXhbZmXbip3eNb5TjmYlrlvO0Ex2Jdvt2FgKeZSynXqqtBnMvXsGFcTWsA845fqla2ZEOsPE31K3oEBZIiJ/u0xfBHCHdqqYJdn2F1gnOwGwsS9/wgKx9QcUX5RMUUDJyMzxoQW25jEQP8BjHwdvAa0PGZuA2YC5OWCfxivHaBz9PEVavJuHmFdS+sg8UVWh0RkwzNTyPjEn1ViCPHtWy40qA9aBughTolyYZi+NSHqK0SH1CvOvTSTkPQIF1YQCuygC2YU2L5VascwQxrVzlHJu23VRZbxK2tU8uMHBzEhi2IDQPyWM1Z76dXr0TDjhMLSfowA/NIaLpLbDMWPbppHq+WsCw0xg0wklJKhmcnsD8zMhVIa5P2RxUXY7D+sazhMkbGASIoxFrD017mvAVgBhjLjGldxn6YpOTat2tA280xfHAqcOjNSSGuLgdqtK9wD2+06frLV1DRo4V1IYvQjMKgsH0KlG4UM0gs3XwZ7YfMY4SLyKvDNL8l9xh6A48ACfFlB2TDkCLinTL2SLs/AwtK52QnjD6k90f5ml6Z94yj4hIeWMaBLZwtEwroAT6UMfh28KFsBDOxomx+TJrv6ixngmwe1jlL6GucV+3/ckskCA4aJEeQVkZJuA3INgkjX54r1UOYw0qpnCcjY2VTZWsYs322y5Rp+Fdyp2BM7mVdsY3tZ8zxEBZgzPE8GTHm8CD2Mmfd9sFXrazkAcETTQtjdZTh8ZpLxnCGE1kn5jiBSOi6C1LtXVI2udkPNnhSSKrFdjSq+xdVJYipOngbplK2AZ2RzRjXizTtSrchTFdlOrL0LZiL/QA06X6coz/qpECNi6+201AtG8oE3a9M+wfQFYDTT6KTWDMi3QwyzohEJv3o6i+tx11hvknuUfFE4/41GtYIWKHDOShvY9QQr7H9nGz7VPVmPi0MpLMRVzH1luaswzpbpei0QuuDqiqS67pCTdTU1VCiYzgTHAo+lFZ3YFCjrsj3HmbM23r4sBnhfGvBhgWMLDt9re1ZIO1QDM/SsnpGC8GL87n2dvCArRvLEnwNz6I5Ly998BYHZM/I87GwC/OGVC3kXVmE93bQ0NO235sb+iOvhgmECgpuc7H/SJuxOKZvjaCUjmI+LpXOl0kXujpbw6WGMpGHn5hHd3grl41hAQbdQSFK3n+5iajRtdBazCFgiDFZOBuuBPEvYT1XdWQGvlLReRes6C1Tj4Yx4gmNWJJWVEhBV57TNgnwn6qtrZGL2hHNJyC182nSFbbeVnG24Y4O1ZmJn3d4d1eNYyEG3sFdvm3/YoPi5CZp0pD0ZjRTBlbXVrAyXNGBqTU92EH2NhvRfIxuNse7wPLwSL7QF+SuJBwpYz8HXGBkoQKNVaChQ3egEB6jZbn8UvEY7gwLx4M3THoHY9jSjUTJ4G6yXU3exbYcZbeM1xq89Jdfcf5jX3zHMuEb9/XX4Fl6FXbb8mV0q8gOI8IG9thgMctVMFwcMPlLg/NbVCRRVvXTcpSvr5OMAlr5s5jiURBOgcKBpsYdWtpBxraj88kF2zk16dk2udWoxmd4oDNAsRUcv8PnO4thbcfS2MGTnsGo/lFHNEPs1yzxWhUsnq1YGqMOAetjNPAlNwOoo9vB8dCcmvSMrbdtvO+6AziI/QAM/ZwE/PZI9Wckyruh6C11pgiWYXClOU4Y466x/BLWN9v5tlgEW2Ns4wdHU3tfybjVkjWVyCxWCcUTYKmo+yNfNrpVu7Wrx2jAy60kI/6wWFU8iqUXl9XRwFT/d7GibZUmv7C6/ix08quLiLzl8C5/yNI8Wml5awwemLs45JDZsOvn5LwF92VG7oIpbXQbMKq5GIddJqjAoyY5Utnc6jImgMEhDushbXhMgt4yEzzls6X3aPVQZmBR9VSZdICttwUMqjNECJATsOUumhekvZ+VCXfXhHCO7hP0YGoHG0Mr9t4G0GEH5lrYJVZUjmC+bRueo51jyQ8o3dzUaUZyvo6ZyoiDpNW1TMvUdOZfeetyhoaXzJaxtXZgc/O5dp4tGL+puBj7f0YPJY0x1SZzFSAhpu6AbJhYRLxTyVyl3Z+BK6VzYtL24slcSe+7/J8946i4hAeWcaBDMlcQPcCHMgbfDj6UjWAmVpTNj0nzXZ3l0/CbPewGgwdPRL/QA27HjxUqsig9qKs7Qt7GxZJ7VE5KG6PaEKlUFW3IZ9aBncpebzWkGda71Rzb2EYWEwAneVGvaQ5qLYOLoBA391A2rAugtuLTINZgeSdm4Cw5cXeHjS7zTRIb8tEYVspIFMyakzjkC7ES3Iu5eAkm8O4w0xX9932R1xs1JzGAUjay5iAWKcA+TN+2bs+U9X8uxgPmw6TpodY2CLGGawyETDPkKcRXg1nGfFvKd0DX5xV4o/kw5rstUL8YftFrScyAw6tgDHIZ94F8vSUsKBnDrDqcOD8mzdMKi7Hil2Jl/iQdBAyxIoWzYUMQsflLdIH0N1UvZmAkFXVNmh/XXJijtOeBMVhALtpNmwfc99m4bidPDF3O+q9ldIs+JLg3xVOfCF/KeMpaqlcP2Aoub0PADSqeMdg+LjUaygxMazSHJv1Y/NkDcCSN5LPip2YZz8W9TWsA64Iye0v5djSIpZh2NG8mnaAVlt3c6X2Xmkc5OOn2bnuBzuPdHRaU9HyuHV6ci11iNp37nAA5AcPtosuctPezst0Ousq9T26qo6hYXZ3hLt9FJVp9T6q7gYNk7KKpB7FlV8WGK3XNyMQixP3hAisMezUD7xlOgxEnghgWZ8yREtGzkI5fwFoqpnTVGtUNAuwpWwXLy1CjoczI08o5NOlHV2e7ePgru8TsGHlUdTZuHre6O4qo+WCWYmpwPk06M6q4rNr6Oa+QyRlpgJOqrATEWmVl8O4Oa0p6PpeyKs7F9p+RztEDXj5nOUZQdutHa3tXVYLYEIC3YUhlcztlpTcZyQzcajJ/O2HBhwZipghoaxqLPfD8Y9OQ24IRm7VJKn6XbKj7uZpIYzAwFXgLYZX2e4x1d7YXuOMzrFd4HrZ/c+n63ZyZO1bR8cUIWsV0trY4uAEoy7WEq7eHBcEhzMiJ4ByZtN8jWNa9gHRjY+yxwkEHdDbgMZv7rIQzuyl7MpfuLKOxKU9ttsBz5RxVdZGdoz9qZBIZAYPD6gADaac5g03smM6sGsMs2rJqnnZCTx46bSj3ZBWCB+2FFIBWmklOmz2Kiop5s1p5N6OoA2spY3A7TUXelPz+UP6kdLjNwqBns6gh2qkw6cVQa0EVmRuJdtuQ1picCXdz/9AOYwl+3cldRBiFzq1CVmFyTt1FXwvdIJZg0x30vDjL0/RbXuFRtAHW5MNBVj6onuuV1wHTEXHgVmmIFE1B3Dp0fusY1mAoM/CswdwZsW1fazkl/Q7FP/Kaz1ksfJYr7YYIQCUerGul0pu2DmkPwhi3jttthzcD69vOt5GSwVde0JoS1wUmxe1Z9EStjKdZQvDq7nUUtWDbyriCnXlF1Zj5xUaQk5lRZ2YxlxjMgFE/mHpbw4XdFZ7ANqY8IkNgwptOd+eGzQPcqlsaywtl29EtwP66+TbpEl93sdVApvUez/3H/PaK+U14RroAFHUgnmdAbPhc1QpkU+Q6v3WcbTCeGZjZYO5MesFV3Qr21drZIOCJGHY3DWuqEczMmztpTjPiQh33WXKdK7dtxasGCzHazjIYzSdyUV+XcZE0D4abZVkDq0hTxrDQ1qlj4KYWSr2m7MwMjKYn/k6wHR7vfVShT6gkTvlXJ0W+1vKdog6cEJ4Ft0sDL29ofrYz6M0cJlQ98U16wdbbFua7zG1Zb6gxKeMxzSzOdmJf5mc6kewmfRhqLXemuLlJUvwFXel8agRI8DTRAVmdJQTMs+e+knZhjpOAjLBGZ9OFnQYP4pTLBE0GpTiVQuDwuTS1v56UoLdMpr78UUE9jllOp6p5stHjSL3lXD6qvCDPZyXrqHg6fozvouwWneOldlQXuIn4Se77oasJOoGQSlaeH9pWQNZt+z6NKDTu0wxsaDwLJn1RoNkOBqV/2HHmqEp4lhyjX5gXwc7MzYQgwS24b1R/MbY7jOIfpxnuS/zDzsCiqwixoKSODVNqm90py7PpaGbgbdP53AnLjWwwOj9PTb2ZeXoXnT8Nx7IgQ++gK+g/alSj1fE6StKDqoriO3rvfpIojlfyKhATg9A2LKxozvZp86U5WD+UGZhXP31GhsxkwRMXM4SrZiCxOh+xrIKGYR3ZdNwEwKSjPm+dnNWNZF4eBefLpAtsvW3gVGaxsSxmJ/ZYwswnbNlWAXZWrJit4mb1iBYTvcCcmvSFqbYYe5+uN3lR4b7d0O0jvkOrOkWXUflDytfyKhBDj6BtGFnRDJTMgu35NFYGfYdmYEA98U060dZLsltSczHmO360Zj55Ffj5UEfmUzSzDPPpOzQD8+mJv3PMhxtK88ZVuWMTNU+IFeSMN8Da8x7QDqSHqhh8+a1bN5TZeFY+a4ZH/Jv5TkvHuE71hOtUuAYqulWzjorqy/W/UFyRIvSIGSGmprQoy/KKYvn1a4mO0oLwR/n7y6qoRWYmqC9Qxb6qVb580Xxn+Kp9xEzgUa569HgUVeg2LxIEYunLn7S48C8S3gihaYu0KD7mcZQmf6JVO4Nwp3gofdc+RtltHd3C2Noys86hi6qgMZwlZT5p9zg4LfIzVKyTsky695YhxDyMFun42XMR4fhuXNfDPE3BXuHvRpWbsFUZii562HBIquFokbS+FCBNevcTXUfImVayapoygxXTJpX5hKq7HJzyMYQeIZYiCK+KeyxawZ6NAIyJTcVVVqlo3oJoUR6m+S15RBDC1ZXpuakx2IKs1FnONSi6R2ogHMO7VDr6qESnsdw8S+KqLkAcbZEpQVSYRhDG5CHZpJICrSWzD4DpUaM0uUfF02WyBnvKlpsOfEiQoxg7m3bIFm0fZnySpJVEiGnqmDaqZKoxjAFvNfCfoqy+iejUq/rOgmlR63BaIWs7cLFBcXKTxFQX6umn6DFcQS8nwWpf6A0SKDYV8I6NmTdjSrzLCNS9hlJTRN+iIomyIbT+KF9fJ1kkI46+lrbhf9QR/fI1S0A5w5a7jsKi66ZNmOC2Rsp3hiZLsOh8mxBDJ4XbOzpQAvf3d7rTQ4IKfIyDVY2+UIvmM3ooZbK7K9MiOX7EMjaL0oO6uiNntmYRyZVpFby2sf4tZAgz80C1CRrpCY59i1qPKJJhMOsFfZte2gtaqkVEMyBAONp0EoY6B/NGBrwJwk89arADL3DA2OFHVAyxqxCa0U+2EJh3rUzQkDcepGialzY0aMTc7jC9wBzwBgcfmU45JLU2RCKj2Dj9tna0TPJUeJij3Lbavo0zlMH94zPJ6Y4bfM4b8NghpiiyRSvdcWTJl7S0hVJNyLhJkkrEsg0T3Ho7wRC6DZoK2Ih6c1TUuKdG1+R60NqjwF6Z9EaI4JRK/rHTm1aDYWOlYJ1lHJemo1oXMQPSawhB0q1G5p4DXImj2x/tTKZyrYIJZtGg+Voo0PSF+o0HZQgrWUqRwMPo9bo7fJSneuU1bDgcARhYpXLYKNH6rGutUNSJWmJl6f3STTrxKaISV9qXtlxvxYKdD0GjlsyB1OJ4fYnWm1SyCiA4U8PVkwoxD2N8DFXg5EAMpDoBWynsfWMI/cCLvCxxxVSBkofRD7zZTuRW3BGAwWEY8FCBT8Wgp5E5eg1S/ZmLce8Cz1gjHzstT643UXILLqKuTL9qmo1FuWDGIEan04+owqdF3VKHIQ36HJV49X5Hye0dSMYRgCm6dwnmhlLSUx5Gi3TktAJh5JyEdML4KYtVsngoNjj3j2+H4bM+f5dvhFQxXO7WnkPHXAurrw+vhstHpo7iHnGowN9is7lzFPV6V4d+yNKLTeGi3LSJzsuBbWK4gOX9FMbEMiUke/2rpyIMrRkfWElKP/7WWkc9GPvEpONvkK+4G2KRfJoa8kGqK0JkBK7AFUTU4AcIyY3Vn5h5mipZbwygGAoLB1KmuXpXUWOEYmouGjJXN5f/8NBZEHXPGUjZ8HvnAQ0RWFQAGUBKOpBgfC9/hWUgloUxVkQZtwCRKga15KPTV4ZoJ7gYKOhn0AK0rIYR+5OVdU+46t0oAEqCgIqhQfAgvTgPChW5QJwTU6jLHKKgDQ8iHwEHCdGDcZVRkIJHNBMROG8WOSnGgPpxjKfWmyxjdABx1FznQKE+rJfpqEgeAEo+GBEYIgzjn6UgDIALoIqUyD4EOUzSlHlYTEUVDtRgOOMaAejDIZyJSK0z15AlQEElAVY/Kr6Kik6D65kBuQTECi0wBL06hzWlFigCyQciwEKkYVzoFDQRUU2sFZIGj/I19dAePPlgeghw6nHw4N4MAyIF6CMltYvWPPLAu+o98QDlGYZUKL5gBVCV5h0FVeo0jBXSqiXoAlCpM8HqqQTlflCOh0v6EIhKXFIHEWsQQd3a5lVcJMAo5CcHCq4vxi1RJYp5VNOyS+cKcnWw2aQJWl3mbD9Foijh5aNSVYOIxXgXK2ilxAptXdIpCEK5gT9N6NZD24yvqxSSZj3OaZdc3zDsGq0gGVjBYIRQvQCEA9ECtIPHGUrAX43dvKXifQynFcMjcIVoN5LpY2QQicau7KFoI3UhZ1//lZPMqLp28CZYFARWuc7rKW/UuHy9j8IBws9K2wm9biKtYkGAcU0jiluSmGthWjEqtm5FRhcCTkm6WYnW61ZD+IeUZiKsdlhCFQXFDLVBKeaptRu+YT2TCaDmo9IzmCO55uEu1oNIdaAA4eSDgcAhEnFxRArygBhnOY6OWh4HNkkZSl5HywDSqgoWMyejthGApErsgeiqX6MguN1A9WvVj5CzbghwvJgBITUVtUNW11cQVxoLpyezps1pCa4KALyShe0B15YOaBQXkPbYwCtPdTCk6gbUoQPQ7ZcZUwRfJ+qoTtvFo8Tmyt1qCs+4zJQdMZ5TCVN5mBE6j2Ia+yq3IrBg+nM/A+1tQ2BxyWVU0/9QVFGE4praXYxRaClgiklBaXVAsn4SjLswn41H1SX9/m1S24sq+p18yjmZVYlSdWQcd+40HSMUXlRhMS00MaMuALPDxfFPNUUOK8R9WTisBSc6L8v1XetK9dW4rvPglYrqFNRWKqY8zIT0p5G0V5JCi7lQ47EmkxKdxRx1IcX2E6XugXzStFzioXQabguWst9UwJvpnQuJ6quLaL1J0ZBCRE4dDlI/pHEFbwpx6CDdb0iS4k2fPh3K+P08gD4SSPmA4AoQfdiELQoKSRDOEFMxtKzY4kUgk6EotnFrssy6VZ+j+wQ9GOg8HKB2BYzhvZ3qYKwzkugDSjc3dZoRT9lRgZZm8pqGw5UiCEtVeTOKtSlrxoHcXTYmpcOrCCQfnQAL0YvJD6UglIhqYodX0mDnRDqkqYLpIcCpx8GDe3MRiBSgj5TUPi732iBDCaTCyxCsEMDffvYgQlUaMqX/vVlF+ZCN6kMU1SRaU1DZrMmJffb77GxK6gJQ8nGJwBDd2JxxCiIByOagCM0zpycJB6YZxhhaSpQuA56OKhy6Ochyxaa/k9CEhdGMgAGVUCPSk4FFAtBglM4vJHO02QSVnNHAGM1jM5YwPNHggokhUNRLaLBJGVVig4EzWevMWEKIDgadjENgAjvQhmYUNAirA+Hkg4HAIdp0SScVdAFRTRxK17SpkqcchK77KilqTIO5ZCeUofNqSKsud5aHK8iHpayncpbn04AaeM7DbSg85yejZJtP1ZCMDbTl+Bp+mZCATQMA9WBmdl2C9KAjpRcPolk/A6R0FerPqTymeUigsB6JQCadV1iNrAkxj62oy8N7dYZ7fBeVaPU9qe6YvLoiaXRV5IPT1ITIxuQQVlBNh1jGTqFO9VB65KshxbGchnAF/UDBeir6WUgmdRsAKaVzFIqSX9kJNSbnuJbleEeVpyTsuKHJ5R7Jcq0R/QyIRk4NkFKJ16bc1kk8BtOUJADScivVUSW8fEiqahClJFnFFURTtjCxHgu1rV2h+kp2g3VcNpZ4XefKMXkWtURLSchByMc1BpQlwAJjV1V4plyYbFr6qyHZvZwIY0D9GEbwKpLoVTQYJeTuK6Oy89G5S0amtSjwgLozMAfvdZ7mcU1sVBi9QaCR5iCkSkBAFWCpMH4pQSl1QKQTC+2h83oWksIa3BUZMJLl7dPM7MQ/QKEJL1eBq0SItBYsm4SHM5TySY58rvBwoQ+qdSkHthikanV60m+uNSo0rLBPSGEtxqWwVnhSbCY/lzxNv+UVTbdMr0vZJ1MB5xYFuMLVRF7L341FgVuSp1WS8tVlRwDfrbkCnsoBdgjTugr5bogCJLLscR7VfmLaHrTIgTeGAugt44d3rk6zhLyqrThCqSqoFA5FPViZEZ4NUuozKvTTnubBZ5GuxCeN9MSU1jUfuAyFCYlNT/2GLQJU106qS8rZ4XGmK+GhJpHmKnD5oBW1wCS0o+epFMRU4YV0TOGtqqDkU+lHIJzZwFRakROl5tKFdDQxpIWOBtqxzzlm4W0xvesRDK3xhgArSV0suAdedK4WMPKJKTd+Qe3qpMjXKtKpwBXamrwWHHbBvfum9GeWo56XdJe5BeEYYOOxDXUCE41BPDHJ+jf2rhRGFBFIIWB5WFBcM8/+qYS1gGtig0n/up82hkcCqdp4oArwXpaaONtKEM7g0nxBXtLD5+JkHRVPx4/xXZTdonM8TcNbfcAhX1tJcSbX1YUfesh1qX31eEFqDq8VhiUl/cOYhmNow0GOKoWg2hjh1OSSvLWo1C20deQj1VUFk7dLX45UkFTb0NR57iXtK4x2uir2g1WY8IISdR6DHvh249VJAm8nCmj5AOWVIBrKXp1UUFDRwMTBtEzLV+NHJpW0G8MaDWxURUM3Y2qNkQK04h7WnIjbRk99mjIdW8mWNVgSTMmCbDtg6Lt8yhyIO3r68mr8lrRIVQW0fJjyShAd+cc6FfRTIIZu+cfvaAcIfrOhnAJaPkB5JTi2zZhyCsSzUW540fTqrHuKVEY3AFY3OLGKnGajB1i1ZAMwQ1JQORsKqv32usFCbqaiJENFX/bbazIT66j98NtrDBKjTVVH6ad8hdKyK+hepx1qtl9eXGyimFyy/I+Lly8e12lW/v7yrqo2v75+XVLU5at1Ehd5md9Ur+J8/Tpa5a/f/vTTv79+8+b1usHxOh4dkX/jetu3hJVnfNznSslTait0khRl9S6qouuoxPNxtFoLYBdYAa++XP8LxRW9onvktrzfelJ3DbY5GJrIIHEOCTQxCHfg5Hd7TiFNUV3/FenTKzDwaaDhCR4WmXw6QsTMuKQernkRR2lUnLUPpnaSd4VHnqf1Ohv+5nlPXvviqazQmvweY2G/m2M7zeK0XiGsUSa4drTheiaUWmAuz6KyfMBHeVxQIfIgI4ccAjDH31UeIx2+mmO6TKqUI2b7yRzHYb56GqNovphj+ISq6D/Q00NjImExjUvsML5DzIvePNJRoR1egGbMZ3NcH5M1Zq3VZd4d0VmMQqE53nOUrVBxUH5PVlQ+s2j5MnOsTY1/5hk3dPa7LbbvRbRpfREgpKNiW9wXd/kDMFNCoS3ew5zcEPMLmi+zWMtFkhdYmnJruf9quZYvo1tgOdOvIqbfXnPind9BXgtbCLeh8xuS2XYVPSrfcLLYtXpMgsHMZO9S1Z5mBxP3Lttd611SbtLoqXXEYDGNS7ZmtvEH4kTkN9EtEodJltbc1gmmrj9jFO0nC0WJEIEfSP9xa1jjY447n/yJVm33fcUBj89FKBjgmIZzmj7wOIavFopFmzKJx8V+t8BGCIKwEtbm1Bhh5MocsEoQ2uMC1s2oYHu4vs9o5cXrklRdJiwurbqtMrHr8VGdNm/dQGzdF5rj/Zolf9ToAuXkgD7GyhWZ4zxJo9vTNe4PMauLQweKLVT7KuX0efJh+SPHWX2dJuUdrxUzn5+xgtNImYuqoK7SJbW3BdjHOIyuW5kWzTRrPuwe1PVeXE7jEnuMwK7BFdmYfYhv1Fla3yacxWFcYoPxMq9jYV0xn7dmFZyhYp2UZTIklvNZATw2B+7Xo9jW3S6smXN4epDFNXzdGg7SpZI05x6VB5cB56irbyvXnBQIdeGInMoxKrEwKEWPx49oveGMc8xnK1zt9t044HMIR2XmWKnzN4et+2YjZZuV1bgGjgUtWzL1Cl5Kcudp6imtMQYXCQ1W2wV9JJSMb682ICbpi5bRwomF/Ev2AYvBM5o0etRBrsxivaZp/vCehqFf5t/yil+6YvEc5wa5FQ2zOWZw9LWKeVMaW2Jj41mB+Njv853mFpQ3XaSor9SBI2QNZY+s8jQSiLTIY+i+zSl5Ptfra1R8ufnWJD8aoRoXPeMzuxAJ7cuIbKS0IzuqUUzHlM0ygFhzKAk/cRoa4yMu/v3l5r/JdPt25v67h37f3S3PROuuWR4L+91Cad308T2jLg2fbRTgg82myO9FO8Pw3WKcBcJ72epLJmxz4xILM+1mJcE4LpmdS3nmPEzz2/bRBge+VNaeiCeb5i6Jq9l4qtgCC18gPASSz5rvFft98VlSvhdjIqzV9SeS1E2jgpgePs/r9dWMXmQc9rsFtqgSzBbdN3Ms7WM7/4Xw8aGKuKsSodAa7+dcjrYv2y7uZp4f8mV0JapJeb5pX8L5Q6GFJ1dUtqPhvLiY74vPI/MAkMPUKWtPu5eIwmVcstzu1L2VxI+T/b51R5QwpnAPNXlu/fh9nUg05KbEQm8syetF/IF5+GphuGlCrkY2m+bTEl7bXZ2TvFhHokoglNpjvojSCsbalFiY/FbrJOsk0djaNyqxuhSFLyZGBRY97JIS8IQcFcx9KfEOpUiIG+g/2l9u9PGW0P1GX7jUJeXHCJ8N4BMtV7TkOZR05WN+m2SgEVcstcPcZTKSIhcAtmavGlJ2+OxVklwkBluVtOY0OxUN5RJZn/lsN/MiquHrvHsUFsebKON9BbqPNniwOCkEb1bms9U1TIXwt/skiwGnZq7Qoo9C1MWRZcRFy3Zv+H2t+2qN6S2I6a0Npn8mG2JoiVLRpZErstAK7vIMNZcDnFLAFlisn+gRwsZ8nkPKL6XX0zXg6+reriQXtV5WcxpZKco2W7lGb4XL9jE44MJ4KLLFCbvL8GUWdrWH/COqKlScloBHsVhqgfmuQEiFGyi3uhJERRKDmPkyC7ld00jmy/xbxKmd45JdczN+ZtfxHaN/QtVd7um2OcblEq2lQbCtMkoaa+wYZxyeO09vM5LY644kbeDvDMdF28OZrG7nyZgsKhe+VNefyqAaTokOsA1fXxfovknewW2Yo5JdE+cLMXd3I+rH1x0Wx9tmuOo03BzWA3M7EldQVbE1YZaAFjkUWeBsXS7aukei1Q6GsJAFeaVvRApk4/N9yxGl+fJ8PDD3iUtCRhHu3FH+rMsuGeC+3v16fubbeDISyV18V2Rz31TgkdHocRpxD7q4SGDMW/mWlMl1ik6zVXKfrOooTTm5DwLMGg5wR3OsSda9WGpha6vTVIpYKFzyXq9jIrTG+pp4GwcULx3O0NWRK58wxH6D0fAW1Z26KDzq4ANqVzyEvZLV+Jhd1GtYw2KKndQrCXoYwr731OUMpg8I4TQGeSNSIAcb4UHMXV2NS5ZXTi5+1FwHyQeL9RFl9U0UkwwVBd7RKugCRAZj3sr7ig89b77Y+AsMT8OPXQWG7xb9aetASgNfZuN7+kedFOhLdYeKXgfjvFAhCOsWBnUDxj8qt1i/NZZbeOHHRNE4WK04bPxa1kLbzG6X1Juf3eG7hdGlrcPPLPvdwlsrS5vlyeQdH/ltAeU26++xC2+S4Ich7Klx/LhJCmoMexc9lTBleBj7VqgzCMUArS05lIV2E5UXEVa2EMwyQLGFNB3VFC71hVKrXhMHv4PbAiFRNxVL7bwJ+4qiTypQbLMu+yfw+IXJFNjIr7bS0VOcoo8ou63ueAkGQbi2cIaKJBfmUQbj0ApVMCgaQRJDEFb+cXfJ5jiL8PlPEIqjIhuc8mQMfJmVj0hCVnKUdrWbKxvBZ0QCtZyH5Wl5XAq0pZ9sjIl9sk6ezbgiK52MGJ6ze7xiceXm0pHHLgWyMWPm8Y9/1BE13fB2zFGR9YUHrX9wHyVpdJ2kAno5lFtL8CBgCIt5SDIFdrHU4jSQPzRjb10nhcsHoNzqlJTcPFF7x0ledP07RPhsKpyU5IAWFxZR/IOmGCaZ74UQO77Q8rwtf5JHOHibv94jb5OaQvDUJut6Dc87DGHbQvSoa4GHsKQbWrVVE35rAoqtVh3Zdw7rp8O6qgS3AqHUGvP3pLxLk7JSoOdBLCjTyJoUYXY/w+cw0TIGQ1hcFuDTEK2axHy00ajExv4ooLLG8SVdAWiGr9bW0CNyQwvZQZsCiz1og+IkSoHejUvcMPbXcZfJGrisU0K6tdhe2Gnb4+EsOKw1Lx5nFSpKiNEgAKtdjwieERYEsY8S0OoEbNieCtDqJHaZoGYdcpKRK7Laz1E5PHV/lK+vk4yeb4FxaIGtxoJlYvO618Fmkyb8WQEEMMf/HSW3d/wjCO03C+oA5zz7k933ZMUjaT9Z0AsYzwfr8fR7hFq8KMAc2lIJFinQkp59Qd2oAkWdbVMOEMVYk3tUPJFJFOxnXJk51k6P/Jolwk02X2aj+Zeo3c4AQ4VY6oD5E4rKukCkbxLsIwiHFg7WopuRUOiAl/xQ4mYBLPDX2SpF9PJStPYJhbZ4z1BBQs5hI5EExLENQgN1Ez2E8yjyxtSE5a9yJCzY1rjx9Bu3lx9Ph8XBkUdedRpPHvKvp79722Pw7oErs7KwY06JMYkEJw2uyL6nMsRQuT12SPzwZc/H7bG9gSyP8nqTZ2LMEFRu5fYOYnXD1s0Cvfqnyr+MX8cQlh4HzR0b1hYgjwO2cGukXqAnuDze39q9x7eIv5yIZfhqo3yGDRg6zCt8WJdiBYptzJxJJJg26Scbc10z1zLvPqh87yanxtU4xspuxcRSC8wJdUISUA6fbXp5iy6SP3nLav/V0S2wvMwv8Lk0rmD8Olj7/n+BrkOEQktz83mUCeF4o4LFXVwnNnztjgPgNppZwhuTdsNwcxPVafUtQQ+fBC1RKNwahauVnp7RIg0Sl2gRWc1plK22ucMki/i3Z7gimxuTNRKfDxm+WogdlJP31zJBlxsV2Hi4fEbcnX/7ycrrpoiyMhGc1kYFcyy8ZcOpwqyTES734Kq5V03ALL8hlbidC6/udJLWFNFE4YYw7zEIPSx9SizTsFbXtMz84YaNGch5nSJZVkADcCs3ANjiNiqwODs3+YMk6RbEUhudvHXBhFEDxTZGuLLCJwWqzbIvlolOuHI4l9bOpCEREIRTC9ETYZAuIY2sFQ7KpaVuAsCznQLMbZbAV7lBAAvD7mNVRERAcXbd4fP2iGTmTtVTFjOYXISwsvq22h5x3bz4gB6BF0u5IuttvXkoFtzXu6JnrCq04qq5qSTXlEE0zwGdu/KpwrH9+md79QtjFArtPSxg3woXJQbWVbbnltFfeEyYXyBJK1T0TnbcHiqWWpgekxW6vKvX15mQ15YrMsfZJnoYY+s/LmQMfNZGvG0R6j0PNiwZWMZz2EOIfC3KaXaAoQOwyJa/kyG9tukGIuTXZwsc8FF1R4q0L7WxwJ0VqLkpEmMBR0XbxueBLvPH2Fzu9LUYtl9vkV2oul2lnpYnWOTWQ7w+z1dC8fNXtdk8F0EYlkXozrRqLNvPuGz/gSjWUdmegZ0YOBznerLsErzqfwwJ7fXEUkHmWSSD2XsXqXHtfYB2zwdo7/uyPzaPy90UNBpAe5PE1HGZsd4EUNVg1O5Kmym+7Vff4JE0UkW4OdTAWgrUxsQgxAjxZbbOiJ0PhcQfcVT8jJVG1WR5PmugwOzyyoEVumlWFNwHM/5fIArqOXDiFDwYjPt26HCza9zQyl+alD/Azo7xuG/jYOWlZ3rZefkWFUmUgfkrgsyXAr/7PFohnWgH8czU659Dd55cvyHyFT23jCG7mmvgoCyT2wyteusKH28HlFsduXcm08VpCb2fPXxd5mw26Er/ueZ8EbgiCzk1QVZIemL7UldfbigKqnFAWelEkK3Z/VjW8dvnWEwOO5q6+tK6yVwm9u1yEZRLjTbuixcb/eet4W+JojSJShdOj9tFg1pbSWpEgMrNsZO0P/iTkCOZ/W7DwF1af56Dh+8O29VRnlVFnkJpWmQwz//AG3bNBVhss6+yEBkdltuqtktwUz+zSaR3484fTIRL0E0qx/k+SEQwBLZr3EyDMC7+qKMClee310CEBltoecw/WP2rLivxPQuh0OLQTg/XMsRi6TxulPOpjHQ5QBvjqMDm7j37IX3DUCicMtxhqfiu/tEcH1nYvyZjL/fkVaeRcV177+uEN6qMSiz8Bcr+1ZuvBWdn4Mvs+ymg9MQH5Xbgy2zkZ1ahJmsgLzuZAqvAFCC/g0tyh+NH3H4paCnMZxvZuE8UIZZbShomd7iPqOnROMgaRd3tPxiDgcgOwceqsHP3UPNw8X02NyQLsTJe+qV/zGmHxYGR5VWn4eOPUXZbkweVeA2J+W5hhBGdXa0dXWn0IxAQabXZ1Cm3tzZflnGx3Pb8sY1jbvNCNPiwb19kgbN9ibateyRe0cEQFkfE4blbeSNSoOWdeMO7tz5vx/iFdgSiVhZZlB7U1R1utPWHOkcxpaXPLqHC7LBz2KGbZjfptAyZ9mGZTGUtRJq3n+wOC4QqpytCk5uEN1dA5fbYW6OHrhEAzLytL2RiL/MfiFuG7HdLbAcx1tlLGc5RqZVmfJ+sUCHLPQWVb81qP8mLen2Wl55X3T0ah3WsqDvNor3MN0nMo+g/LrX4xTc+rB/xPTtYrfCmzO2FzOd9LIrt0qBsEWBtUDyui0NSeZrVQVvkUfQfF1sdhASQtXxUYHFEabI7c6eT7qOFUt5Jz7EW3n+1uA9I8EmYuwloPtkcbsuKNCwebofv9thkMwmV22OnWdBAvE3JXmbZyazIX1i5yqlZRdT7Iq83oJzqS3bBmUAhWNq9h5cs3eclhBRZmqD6NCrYCyxDntmHEE+ktlEREEBto3hcxaGk8jQycfsk2PPm7qmspwutmtYN32fBNNNpv1Yk9aZZJrQxKB5qVGCJT3TAYD4vdy0a5uTV5h9vbQhCUj6h1Obiq0lrLUENFFvOy0UVVbWAlyuy7y+MViy1MCE2ucRhxEKhNd4mR7nUPikDsue4o7ooUBY/AY8nghA2LTT1zrFQ5jGzJfZ9vowe2/0IMi/IoWw8/cAITOazLV/X11VeRelpFqe4YxB78xCOLRw/6lroIexbuCT1++cfVGOBIT1bVI4NhrRtsRUJirHxEI4tKMbCQzi2gOuKaw+GcJRPWM4nRMeM0hOEQJIZgIdoGySmAXiItkEyG4BbWFKbKpxmOny15A+Y61w4Dc6GzhXZ9o4sY6h/zXdzbOfops5WaAXFXvNlNlgfomJ1lidZVX5HBcJzyzvgSEAs9rk7FP/I6yG4QXrOU0N6tCgGLUtA7HdvmTsXVG7hzHNzk6QJkMxxVOCgkW8kGvnG2n2JnAuw0MUHXywOjjCLQCqGGtKi/7gW4Bjdf7XDJKqgw1dLTMCY3Ub4KSp/oJWamjIYuz4f3d+/FXvcfLXDdPy4SQrq9PIpz/gEGCCAK/7/QhFAZb7cjYPfJQWKq3foOuH95WRANiaovtpBTHeQD3kKmKNkUD4tQRwkh3Jq6TDKfohHLRDAGb+4WEEAN/ynR3LUpMwJa/uamRRzX+6E/fQ64s2jfKH9vkB1ktZfE94hxhAWK63GOmOR/EmXKY3OiGIooaMKzr81kUnVkP4tnqNSyMagg7WRjhuSBkdBTxjCpwVoRHIoK0+IXstTDEgBZuNkXsR3UYmkVlgQwOZclcB+4qMCe4tfc/KC7X1dmT1WcuDCS3pTV80erbTSGVeyufAJkwl5F65l2GPUOVpHSSZkm5WAmLfxISKRdO1h/XNe9ckkx+0owCzkXhyjTXV5l+AeR/gz9RP+EGWrL/fCIUANujVXUN0h/2uJz2sfkrLyf08GQOnyqIwZmmkurMI+m0vlKnghYrk9OS/5hZjrfXJDz2wBmQtA6cJcZmimYa6ubR4L+91Capdo9T2p7kAmEwrt8AKvjjKf/wKMG4ZXPfhztkDjVgVkuAV4DE4OZc/90L0fX2axMwMWYnvL8GnZ9YAmnoyATCYAgP3Y8WF4A53PoHIbbStONiQNgKjHckUOOIEoKb7MQhdHGTlpiOo2890WG9DBUYGFVRKVpfB4Qf/RhpsGslOFE0rzJgA8Y6naS4wALk+Oge+KuhO6PpEGJe5KQ9H8+uRZwOwS4fI3kMyrNGEDeBstljpgBu+axVIbSsr669pXeT9d+whecjtcZXdnoGZ7BQctAXFtAySDBMRCZdBezfpeyU6RP7pLtQRkIeCKbDaqrqpU7QEAbLxzY5QNiazENGlCsUXfsfz8DmSRZ79buF3S147JLsM5XDLfreXrEYmyhSRsU7Bd2zNWRDxP6D0a1+0Zrjvl9uy/ldI+jwMBcmuxSh0d8+7yCfTeZ4ufsabIWoXD2I1EjC5mIyMs03BqWP/yZgicVGq/2WI5xCWZkHJrXGRnd4JO8+z3+c9yO7eCyM1Vk07JZ9V0WFwe9pJW3W5JfllE8Q88MOiilC+zwEpcHyE9ZVRgeZuJ4GtXvszqbpI+CwOiFQr/AqvH31jBYvJYRXOaLPo2gbv37ruDAQRem9b2453JjHyOqrrIyEMOyDfRzwiVk9qirD8RGwUKrAvNQGEVqXB2r3MUlXl2khfNbPG2dq7QBi+ddkTP7ryJQyh0xiv3HlEC2s8bnEJSLLXh1Ojmpjn4csw6fLew+qzWSQa6FY5LbCjNLF84XFECspvuRItt+jn98wgL3xAb/xib0+avQ7HdkvssKlrVRYyuZUts7XMQxnGJjbIz0Bi6kobKFzu0b/nbht2oMAujgjx8ISTihiGW2G338g4IB/MSeAI6B4lngGMakUf+5ey3kZ0Xwja/DEctAtF1ilpRAeOWQ9n0+zJ6PH5EAhlGBVaXlkd47dzmxZOQg3Bc5CD6tvSdPDltbR/J3RbBEuC1NBhnCBEz6wtpQuuCZgABzCnH9i+P+a3exaxNcV2QoM02xCHUrRmE1e3mzBDTNOuOb148P4vlewd/a64Ly20B2Gw2uf4Up+gjym6FiHC2wBLfGSqSXHCX4Yos76BobT4NCVtgZTYL/OCMvYajuMkKEmV3miVVEqXgAufLnvE6pxOACz7mt35LnEHksLqVtadZ2EyT4DlBLF7KOgRHsttbm3eZOQnmYAxKs6x4MSmMYSKjLY1Gbp9f4sTxuGjO08RxRgwKXH/6j1vDQt5yzU2ezSjHcFMf0T1KBedb5ruV1byoQN+qcYk5RvJyHohwVGCxcW/gZ1k2Ls+yhLXi45EIj/L2H22ONDeoKFAh4BoV7KZFXMolBbnhzGLuAoD5bLNf0heGP0Qlr7KzBVsjomhyczYfSIBE6yw613zrahwT7XVMm2JclVi61FIP9apPsLezdk21OytotEkr//04fozLgd11CLb7Mv6kyNcy7ubLbDhThnNcYrW2g7wRFeBlt/IcRcD9W2R5I9aaHQ6fmlRYQmgVX+yEu48klqJnIJ6xxOhzhXoeAzs0LgdAed2JbBRwKn+nFP6hDGeQIW/rLiov4ju0qlN0GZU/PL29GEwunl7K6tNwjf/h/gBzufCSN/1kI2Dy7PhxQxhVTI3MlVkIfyFtrm3KXFvDhWL73nzJjouCF/yjAotZw5vYeS2KY/a7hVUgot6iRSXgG5fYYTzOViC+7rtl/2r6QjDcQ6bMso/ijDCfbbbgD8lqxb9sPHy18u67Jax+hooYuHjnCu3xgtYVodDC/pA/fEOFuGrZ71sj6Q/iNMRj6j0aJ7uwtO40Ar5pm8cxfLXFJG4Y7Hf7M/Z5nkozyHdlNgvxdJUKN4fNt61hw69FEDbs0TiwoaLuX4sNL9Kay0XafFniulr2oIL6IYWlkuOhDBVJHMhtmMfmkixPi2LbOfs/0FPzbuUI0/DVCpOAxKY+kPLROt2jrclqIT6+vENr9C0qEqLU+zHxCJUDB2vqT8O+tFHulNR8mvMQ+RdiuPb9US87A/npYmCA622rZUG407S8y7woU/AAz363wEacCsU7W+azOa4P4pPgH6zfA89v87MkJo8QANf5bNGS4QofqnV6mK+E/ZH9bn1b2yWh+Iyqh7z4AV7cCjBWzu94wT3R1dI9cSmG28Ew1q0cP8Z3WL9D9HEBdWMy0K0RbW2nfP3Nu7G5xHVIq26rkFO92+r2XquY39EhuSNZ7B9zXCi8QDQqsj3vn+TFOqqqhH8AQiy1EISez35LF2h9nSblHb97MJ+XFKy74KjzLidPeRxnKzyj3LxwRVsjwiibtJ8CKGktmKuuJq2+7UfZwEaa3dDsD6P4x2mG+xX/COeiJUHqwFHGmKZhrmD5JALmPwjse79rbhKfoqy+ieg5obhEa7wP+drxIIwOrGqGZltVum8JeWOKjwEbvs6hNix1Amhj8cNwE4/N5UCgRbHnoq3jolaWh2EiDpmTy6cGw56Fto6FzhGZqlU7db4RySwup3hkNYJp+KfXiN5INKU3TtjeSrBZvaG9c/x0VORleYHSNAhH8dhcNjYtiufLVUttS002gebdV89NiUXlsiWp60809WyjssdypUB2UYDS51iEwq1hj3/UqEYr+tDYQVVF8Z1/LgwQpQO7GOKZhm2YxnlEXJGdMSG6RcQMLbKIUGjD4ry2ZKspnSSiP1v3zWJ/FB6fsX12xl97/JSskehCPXzdxoUXbLn5LbL5NuMkLxI+E+bw1S4wTAwHs8UgMt3w1SacjA8is6st9qL7ZnGJhjbpE9+R/qM1HrFLowILG+MRZ1k8sql9GHOGSfph/rA8cvPPdYR+WfL66wKv6UuaK43z7+g/2+ECusZ8trhKobt2DL5EzpdZ9XBFrKxRmj4JnWRKtkbIs0P1k/IsJgcxr64+0cWc+Kyt9YO27cFalESjAjsfJtGFyWr3ygs+wIV+sbiyLlGRCQMavtoofmUp5swZvtrGjF2U/HwNn63G9w7dRHVaYam2wtyYRGkpDBYC2Zp1exStN1Fy63kL2mFxugGQVd1Ws+1z3mV9nXWWuiltIuMCXZKOkbncj+owbCtrY32z2T+bqHSeAYDiXVw24UKCplH+pvKYc1cqZRgHa9UbyLACFLvgfqvG/dYH989q3D/LcS8k6j6jh/IjqipUhHNignE6CD5TRBPJP7B1MfeUCm5epd8uAceMh+3d8Oj7hKKyLlCTl95302dQOW35yvrbuuFPkZD0nJj1BQ/3xMout3MX0O38v0twk6W3QOaxuTOkAsWeJ585T56uN3lB3jW6SXxjZ0eoHLhRU39bWfEkT/GUinjY73Z3j1B+cva7rQ8/hG9cYmsaC5BO6UfCBck3XywuiaIf/BUR/WIbLfMl40867HdzbFjinCQoXZG/uPMYV2TPDUd5dpPc1gVwUy4BsQnIJMi6MBUuDnNUZHNlVtZpdZrdCLdvw3cLXmmSquE+kLRqgsoplG6NcL14yuIwgUgDIpc4JFXtiW47goUhXeR1ESMhGwbzeamQJhql/FiJ6EYFtiMV01iz323jYk6FdySGz7a4LqpCEvTZldhiPMzzFMLXfLfRBrMYPNuOCrZGLBw/EkXnHdqkeYDHlnhsLvehWhTTSIlW1wPiwG6szXX+ilwo1ea0pPHFHCsOX22NNv/kNSXms8XJpJ9dSB0US3f7Btc378iHqtpcFlFWrhOasxSimQzGrxV9G7YKZHMgFt0i+TLbKw34/sf98ofWBG+AxiVLX70Qfkzu0Sch28OowGr9CK4G3bct27OC2AlGqJx3q72dgJIXL90KC1pxZY8KbQ1rYi/dwgDwn/cJHp000gAot9loW/HVsgK333KFC9g4vM+titnHEyto48xnqzkiclUwLLDf7We8MUeIPvBQ+VJ2hS83NyXitprum+VNPHD/buWvEFXx3UXyJ8fDzGeLGcDLiSaJG9O9/7r09nmUrzf0FQKVFiEFclCeD4r4TrhBFUstMKcoyvgcn/3H+bfsg7LM44Qa/sSAN1S0qmOTLPiKTcyM6amIb9PUFOLZOHgWHNAXViLbKpq7aowggLZgtNcLyCGWJWTue+Xd4cuouEXQYdyowywuy87+9hrkB3OWubhL6J9N/N8Vlq1VkZB314/IYm/86BWmSoPagoFyVKcBFPK/AVOgb8uTb7gGAnCNQZc9Oaeh3txM0zHsFSOrVekBRWgxD2AD073zqKetiNVz+nuEASYe6JzfRLd4FpvowyQlymv/DKfBbPNVZFNuM9djnJ405ZCFnHYOtR9jWncMq9urhMzni9Pyc52mv7+8idKSVzd1o/dmnndJSTXGq4PNJk2Ih2qb3Eezqajr8WzUQXeJgwzYSdWA51z1qANwk7KbnvtGS6y55Yk4pLMuPYcVQwy1ZOxwBmfZMCJzh32reaHvpB8ntGjmZoSu2fb/y+hWfTyBwCVpnwYYk4OIiNj39OFEUOPOBZltjGkxTcL4SCqrIdMjLI+gMPpt1CGfy1HzHD1ExeosT7Kq/JDgfuD952uJVt+T6q71lFTl9NJWFrN4CVUM+ELbkOcMjHEF4BN9h7dR/dSRIZzA6Q7hNmcXoU6IwwuH1JOPeGwhJQ6PexsZSD9+PQt1tlbiwxUlGSp4kN6Y237p/y67D23uInINnJZDPXLfs44oQcpNFFNTzQqdJEVZEU67jkrUgLx80V2SdNdurWXtj/Qobd597gA+RVlyg8rqMv+Bst9fvv3pzduXL+jTrSReL715+eJxnWblrzGdxijL8ooO/feXd1W1+fX165K2WL5aJ3GRl/lN9SrO16+jVf4a4/r59Zs3r9Fq/Zqv3qI1wvLTv3dYynI1yqrDXBp3Lr35JonHPPXbfyCBGTomOUc3L2T89NtrvuJvAE+Stn9/mRCS0uVMHyCjeR6bm1MChWgvX74gbEeuAHvWe61Ez15pNs1k91ER30XFf1tHj/+dxVcV4itJQm+zOK1X6DS7SDDaaNMhvSYXTZZdOy07lxpcUKGYvgjtjm7wzwkwzsukSsNQrIn4DYDoE6qiNpykDIZwlP8uEM5wtBMCiN254xxlWFAdlN+TFdnaPDA1GP6ZZ2HG2KD7XkSb9rkhSd/McV3c5Q+jOXAf5WFOlCDPddnnEGPEnCUOOhxybranOHurqZb60SNsqdtZ2Q9J/ZcvPkWPH1F2W939/vJvP/1kjXQcQGY6pcazgLWhSggQeL4z8NZhBtrnDDUrwUw76OKkg88jfdYp+RP1qvQzmdEhdoRphFdMfz3Fm83j7y//H1rp1xen/3kl0OOK+Jbkdbb6txd0Lf364s2L/9e6O+yDvsE79DeXDtHnvIaXZnneD9Ozn0nPPAVZ39OpOvk2WCedV7z5cm356JmsUq3cfeMyFy2NjuqUPCejkevW6L9myR81ukB580qfCrmtXniSRrena9z1ziNXif6Xn2zxn1epj4YYUM1nntRzRzKxitOs6SZY7RyVgAVrZ9edx3bU1+zl508O209Hz0kUrw55QAXstCRpjc/S+jbJPA5+p+VlXsdytvc4FvH+hc+EU40sYp4WtjHD/fKLNer+POqH2Hiu5ReVz2qeveflpECos/L7bDSX0ePxI1pvvOxRGEm7YTVReeB+ZSJEumxePhbdZjFQ/vHA47akfIRcnqbPhOEX34JDS9Y+hVIAY2sQ9ZDYV79kH3IS2HbrxecHaZo/vK9RWeH9+1teeSFz01ohm1BUkKtARLMVNHhIIpoqIfNqR+/jbBUIk/MZwUoGHGTlA391vrOSgAzIXgo0tbZEAnyu19eo+HJD1kbpw9QTH+l6T6/2Cud5MBD7drEdEw01vRjpdDNyjTE7ypmdlw42myK/99sIxilL5fLNzPxDs305IbNm02fGn0063KahmlrREoryJiGL3XYe+oQhShOZNcu1CXfDIpX5PITCe5IX66iyuSCS47qI0ip0Pw9W6yQ7ytdr5t7cz7+lDHImO7i5SdIEc7kf6fxPZO8QDV8eoZDLbwcJ3Z74uldsJjr0Kbvsx0Ifo7Iy22fe2CMPukOQnn7Mb5MslEqN8VEOw7LXAKVRH4HYLTvNQUAgUyBMRgh5Ytp1R8Rg3h/jbRkMLd3ZXZm6khodAUyZNBgycA92wkQue6MsjGNfGyrtoGV3Fb3WCEaPqCNvFjsYjbjqPj05YpzEvAjarqc3IZG9DYLsn8nmLC+rKIXueN0sWnd5hppjchB8J9FjQGwe+5/5oQbK07Cz0nMSzwVqZyzbrdXbYFkGufp4yJt3TU7LCRweLu8KhMzx/2yLHy8RVCQxh9vJ2tqkE7zMv0Ve2vOCjg9LGGvH+Uv+KmvfyV83sId/KB45vc0w9Y7uSKjZJBzCKibPhEGm0xQX3Juurwt0n0QQg/oev3fBG+wwzW+JTvhMWHTx62ez6CMzY4ZJ6Ja53tTaKb38G7trixbXEWt0c5HCn/MqNMohY4bnTrOdF9GqMDzVZr11EXm+nQ3otTvHMfEMSruzs2K2HQ1pwfd2psBdoMEaNJaBYPXD+C0pEwyNBXxyn6zad1rdeWMSpfjijj40EXalnWDo0DiDX3B1jNO+9+w31eE8oTocoU4Xeyk90jw6v16saKOHIAoIeZIwu72o14G0jyD4OmSXWMtMucF69i8Uyt7ecxBvS8zMxY86+BKJsvomikmYVIF3mAo2Kfu28r5K1EdGB5yn5fvkpjqKCq8zY4fDf38+R3/USYG+VHeoOBtlJHTNg0DxDVu99s7bXuTU5KhXJTHZ+g9WK65Jr+6flu/yhyzNI78jfYvDb2q+ZmmzCDt0XiP71F28fLkR8Dl5PLZIjh83SfMa47voSYbRyPDXIqQuAxShP3d/iMqLiKTYDzGrY0wOV5lcfZ+7TDww4kh2cFsgxOpuLuMaIbpEj6E8nc5RXBeF5z1Oj+ToKU5RIzb85B2L7wwVSe65THuMdAunaL0W1im9/+rfYfGRZaHiiLCQpVnJorTD1tjV+7M0ipN1lJKEXfhXSTNvvfk7PpWSQFS8TTp0PYib3Gl5XHqRkMnZ4sckWGEhpsfsHi8xjKy5VvI8HFV5/OMfddQe8D0keXMmovgO7qME101SBqeHURvso9PulWTBxvsxf2jG2vqm+U0D1uGTmyd6jj7Ji65/hwgfi3zQHkbxD5ocieTX8/TyJEc0gu+0oSE+IvSHSy+Vgm5feGaSdb0OMTENvugxFD46cLRqUSUovCqK2ZwAH9ZPh3VV5bIQa9MlQ6C/J+VdmpSVP8J2LacI8yUWzCObh5MVFevbFFUSe9liRgiCbx1f0tW0DbTHjCNypzZVGxcbjCdKnQdidP3BtNFfhVwm6xCXGCzu9mokEObO1HScVagovXmxlV4jrGhiBmol3Kxt4vPCZYKatesl8/HmicrqoKqK5Lqu0FG+vk4yeuKZlFlx/7uHAMr2IQCfUXxHye3ddMt3fEwJjv57spoQ+4dpadPvSqFlTo84rMAJdWMQxldk4nCbbYk4BEee3KPiicyqvellXNvH8NLppV+zhL/HNOjHuLZPPw6jErXbq7ehoMf1CUVlXSDSO6WqbJ/Bq2/iYM36f4TeJvpmyI9xUw4WycM6W6WoyQ0LmMV8rwIa9GeoOK3QOoSZZoSQkCEkvou7vLH8YKk9gTtlt7s/E/8JrVeBS36wjkb+dvGvJZnWGA/O8169f3RKwBZcceiacpcgO5yepL3jKo/yepNnbFCA00lewBLKI7abJHp9THdBP0Yd8JCt28Kubh7es8/JrUVKPI9sERtxS2Avl8O8whpYcB8p8tBI6IXSsV0YR6m9O9LI3TDMnc1ZQh1MHFIcdRV9FHzirXaR/Il8RjByziov8wt8fIwrHrPbYygNji/jW4RQ+XOoDn8eZbcam70DhwT0KQxrk9pCz62dsFmEMtBss+3jJqrT6luCHj65xXWbe7E3ouuZKEPtaA6TLBqS3GKqXdMPbjsXnkCCRyOW7Fc/ykny9EynZf3s4nL4GT34rODT8rKIsjLh/YwMtsJ+HVwxSLxepFCvLO8u6UPCbCNCntmSAkM4TJSirqJX9N10Otk2xGp26kR72l8/n1SP3cgcTPd9Td+Z40l7Xqco8POFFxs0uYGqTbMBx2rb7jqdO18IZOeorLDeTnVPNtO7FKuZ4atDeib1KHeaKgZx9ETYofHJC428o7DkoOSJvVXyvSh8/FgVEXl5dUq9kr2AeyYiTWsQ+8VeCzzK07z4gB7BF0B8kbebV/M8TOD7vYk3xnbtNxdS5DbqmfDQwtpUe8Hn2gmuuldXAjkyx1PeLssvu+YIPDY0bk0Qd5ykmM17ly8vi9KHZIUu7+r1dcZkSnRB1EaBL2/dembWJ3eR3DNIwy/PREIP43OVj01dL+HYk9YzEKBDQ9ULP1ynJRaqzX2CzQKyZa9ndju78H4vuRczSfDV1/R7cKE8wWKuHoJ15xe9thzIhprvuTAEF7IUte/DuPZfhhufIRtO4qoS3FOBofveVyOsr8bev0Lbu93zr9i7H/y1DoA01O8miSkh+xPGM9miFtaUYNo2MsEh/4gSm98BlQjC5tzvGXPQuGd1V+f+3ulT6mQqgj6TBQAPMRTvbc07kIswyjNhkZ2ZhVaoXEa3z5jylgcoW+J9i4okysD472dC1AmyFU6RR3CeDIiTxvS7R8mbYHcOkjdKR+AcI/9sg2UPyjK5zfAK7A7nE2Tb2ceAc/ZL/wc8Fz7hDLfK/7kO83JesDRh9Ezzpa6+3FCUdJBTPCTD8sMz2UOncI1SmDXD+C5tkV8UtNTbIJDprlp51e6Z8KKvgOv+7+ly9SmiHp2DBLxiZ9kruIFvy7nXoY7YJO8F/sTk4fROCtska/bZs5jTR1YVeUow+d3JSJfpDHzyNnjEyXNbwpMEYAc0W3hduPmK6cZV+HlMtGSIzlIQpJWsMKQcX45jzR6ETvPi4o86KlB5fnsdup/N0XX1LzxNbGry8Gnw6CF8hoYcfd0m2WaCsLR+yxH7Thvy32k/JtmPQA9B2R/yzMNVuucCnodc7Ybzvk76huos+aNGCUV5kxB+DHkr/rXsHzr4Wng5egNo/N6XbfGFDAoneihq8nKF8rgHI8GdkB0/4r6VobyB9sHkywST99l7n4lIWtqzQhk2aeawIw9xNXGoHtUOYY32GUvI5NPGLI0FSfmMYvme08vENI4sDCqSOy4MpiDugNuZ3vGv9H7zdrzrC9yo/zX9so3FNdEjiyxKD+rqjojIxpvpHMWYYM9EhHebsvt27inCj9dMWKy31k9m65Q54AVE25oVAmP/QnjrMv+BwqwQiu4gjlFZhkOK/7zHp+bCK3OM8aI7yYt6fUaemnkeK+wy3ySx/fJqq/kFPy69uI2eVjQ7Q58drFZ47wyflXbXghDo+qDc8UwWCB2QPYu21XZ8gZB59Dcxt4lU1WqRgwfK50YSez3u+c33fe+PUVmRXnha4Vsskil3xEaTO3mehHZTAj0n4fO+yOuNowRq6waP6vB/OiPw4etzu+t4reUQAoWsPUip2ouVSfzwdlM80WX5TGTUToiH58ZuAa14xpzbEO95MC0dCxyi42bZIPjUt/nLXYgtdn5oM+O2Z2OHfF1cfb8bxjbxq3NvBAQ+l4S05gVuq+Z74jisELjaxLsBUTU5fAMnde6486guCpTFT9C7WY6IG4TnWMgYXr7+3XlVXkaP7a7kf8L+FkniDd2FGT60V3gtpKdZnOKuTubLN2rs+HGexi5JY30i85lGOGp0npG2smGeEbaNzToy3JDFYrVvbCTGsOBPyN4QpScITU1TectTE1je8tTUbvGHScpO+WRyRvRJcWzeChUdkzWBNfUaazurid9JOEcPUbE6y/EGV35HBcKs5ec6cnSH4h95PbhXhz41Cg0EC4rtlACJj5Ktk8nNTZIm3onweq1/E2SM1IeGHGPIexYFwnLkCM//WE9xmnaMhdQOMxGkS8EUU2F8nqkOyx9oJSOdd0+P7u/fBkN2/LhJCnpy+pRnQ56HgHj/C0Vhxs7y5bsEC7fqHboeHlh3iyPp0RzEdD/4kKerQHMlIg/ICAzywyj7EewoxeENtsRYvKdHoVG2z+6ERnt6HQXakFoJTZWC1v8vzJqosbJXJH82LzsTv/koHie1mwR9MHaTNXCOSiZG31MabUgelfDEEREH7DU+d/Y6Ufiun9WkbolCm1PPoiSUJ293UmyOM2Fo2qIkZxW8CDd1YzKcxNY1dbbU7b3QYI8L52gdJRmTydIhH9KHiERKtYfbz3nV5//zcu+OY7SpLu8S3NMIf6b+oh+ibPXl3kbJtX6Q8GuJDw0fEswHz+YphsVfJKRV7Vtvq/l5ermvQmPWeZ/c0FPEM2Sdbmj2kzfU9Jq/ryVafU+qO0cW4qp7d2X0Wl1wO86czPpMGLTTk5hZdnrASIbH5yayI7X/LdTE9sPTsusqzbIXeeZ+6JDhM98m4EnkHA92Q4KOg6mAPcZwwSgXKCMKe6geNujCde8TKksm07h3hqtuRqjS52lznkH49Qv7mUi/fjxBHWQWVNfOlo2VDxFgTpJb0iDzqe8x+4Ymv7acYzSzjGTqa9buvNTs11OTbNza1LSb7C4w/B1gl/jGO4D7tOxQBVGMPmIWz4Z0QV5vmROp/33a3Nz0TU/H59Klov2IRHiG4VG7bR/rKM8lo8yCGzSloo4dTKUXddHLu6uXbVceWbPp87LyLO7O3BDVyyuWYjiM0igb0vs4qW/l5O46IQ9hYW8ZWPvHlae17crGeGL+vtBdsqF3s89j5S0oyS+LKP6RZLcBLxepM920Sgm9IUShrjC7hzICoZtjF+oWwDOyYvRDcgocaWoGuJ8S7caTWw8sdI+qLjKSUR49mwQpAeKwAk3c4vpPCLvTOYrKPDvJi4ZXwijpLcchev41OO+7IDX1bbAKYOISzHm+sxPd3JCDTxh0B6t1kklczPiEZtZpbUaCIkS42ba4sVhskDn1ITqKime1SfpLy7OoaBUHL8tTYxdyu2Zl6/pcrbKT7H+9urj8X/ja43+3d229ceNK+q8M5nGxONnM4gCLRc4CtuNMDCQTj+1ksPsiKN10W4ha6pXUSXyA/e9LUTdeileRuiUvM3GzWKwqfuK1WBXuFQb+OlFRB8H3F3Q3zOy5umFOeEGzkXEOujq3zPQKbEyXmNuJbGVr+vYj9sHzpnyIv19/R5SmLmwwkyvcnYe8eB7puDJd/iprS9lmlHT/NLeUuEhQzmHSFFmEDaPlMmT8TO3j9wu0OOrYnYv6BVnrUL6tqxZeO/uvR+Tg3Td7kZdvrNYbgcPV8y5FbHZwpx6o2dyiIsnlTgVmE399gk+4jbqNM009EGimBg/6hcc+tmJkSZXEqeONDVt78U8aiMXxT+/yw0a+NEojYBFq0IMCg3VHIDZ947pESNa/bgSWzaPGNhmHchH70mERq10Zu0SQvM5qYoux1LiLtzPaYE3eoa8oHRuBLi8qU28RI451iiKvbwBOJuH5f3MIzx9wfDR7J31AvvIwYqSiokCFL36znFUaJXMs6nunbDfuJLbJMPk2LoVYJ9ym3pNLEYkbTL/m38gYRKvk9X3I7GsXKHOE9UZndCDxKRY9twXxOu+G7I0Ac/xV5psiP7rDkK09MpGHuxh03XEBeAOlAfGYxqe8Q/HIm412D3/53ISk8cSsf8O4dPfzPhTdRkYB52jRfoJWezgRCnaBdL97Qvtzih7i8stGelu7E/27w070An8P6p3H310Sg+XZ9fdTDTHA443fkluzJ2cXAlOT2aLGx/8x7f+Hp927Qet/4Kng7pxFbfVRqcgxtk4fsuuiGDeItyK55jOXaeSwWazTt5Ajb1dZegbkX9fZaGkwC/+yvHS0zJnkpfSwvyQC0bDx9lXelG+T/R6NiliF/zzUo8YtKnbUesLBw67jJDmpGa3tXf7tEyro4a3Iv33tfpEM/U08tnrk8HWem24qo26j4LhtTcMjiD9Dt9W4y1MXlw2m9qiF1w1euaUhVk0fi5+AmhBQ9+n54J2pj7tel8jf5uGz6v5NdlvzuvSBNaxCk33NNygwY+88mUBu4842rI+zjMH28ISO6FNcJDWrjSCN6GTTnUY5Ki0HOBOeIEL4ay1rruHAQjhvBCRBJizg+m38JFim1tfhAJfauQ26bhyL4bd0zlVv24d3+SG/TXZ1aO1l+HK/rY7pZb6n5q9x96vNDWT3OPwPVH3Liy++++a2SI5x8Uw+nS49mssDIIjLyMdIhOX1d6xldkAkFvdY+WBmFmKaO9+33H+UkdDhWkeV3Y/lbe/eHDK/XztcvMtrBhZGMfc3eZMXR9xlVDh0T+ydc8gCn+f5c5qUT3P48Hu9+AImaJ9B7l/ndWD762yPO3PsFGi3Bnsfk6epGxmAln4KEWw9fRnvvtxkmP/uyxadgzw8J5/30fLsXkhTXPq/j7PzY0yW2sUDOuIxezNHTkGGg09JnYhEzJI2mnHgR2/da9uffbzdPm6HvZ9dvN0uvkMpsWrT0xvp4X66fulltfDbGC6hh+EiL8t7lKY/e9BPD5oPjnRq7K2YndZJnr1OGfPL+FWPIiq7ZVAx4z7784zOaE+Sk1xUVbx72tDDbEo3+w0GU3mcZzdWKz6g+siL7lm36F+a+dTlKuBN4uJN0tQa4+NGR6XHunxOsrh4djrs136RLk8k3+MtIOzoacXY5UvcyPd3WyR5MTKmV/3Mw7vPcc3U4FDe3i849y7qQx5C0Dt0Sp/tpLVgG0LiKyGb9ViOl7udb5Ymb3hcRuj6XtTPrajPo/57PCw8FMnIKCOYiZd3q80SZueYJJWtPW66R9m+PvKL07TvNI8ekrSkG5knwNR47DT7d/trzHabqV8bsLxNXTK8y3ubFzJ/c7ObtxL3gqWyhovOstRGtnBh3Hjd3Jep+WcCqv0aPcbntMKDG0EgrhXgu7uKj6c4OWzl7gj6LBxdJ+FJz42Z0Uw3/a2289W/+SVR80zkRztYdtmY4YVbMwk2b0jFiO5j45Ysfhnnw8NesV6a6uPwtXYS+QxHWS+B0w07Yw+8fvPI698teRkPJH+gb+U7VH+4G/R4gJXzGhhFshg1eoz6rUwF4ZqtRdT6arDvMEeun8YPAn73g4A7im+z/ebPyfQ9istzgdpUVxv5PnTzrUsAurDh7e5qXIR2Nw1119hi6DX+XLJyQ8PsTxhNCqOb4wlv/nGbj8lm3ogFwdCbPK2zAAdhjU1fs/ThJeuDz/gHSV+S0xgRHuIvaEz9xhP8QzZuJY9R/CZB6b7+y/8rqK7Tr/LsMTmcixi6end8f5Sej1nnRu6D4x0qz2l1kz0K1zZuCdGa+ERYujpCkf2yja8f4h3Q/XO2+wE88U2sPVgiunxuuAwW/9dfbsqPZOPzn7884L53ubXIz8UOga+0rcVreKnFoz9ch+dPqtcIY835UpTXVFWg6uiJibxk/F6F0fU3d12BquPXiKSJ0WFjASMSO92MiujT8LivCl8jesPwMs9lFyBG4zjuo8APGK6/18vT1+iU5lvK29Gut53evjwqvZ/mWyB7WDfelOR14m4Mj+Zc6X+GBahbvo8OcOPX00aXoy4PwE3uRh34GsQRsA8kUFWnhyLOymNCwiKOtyrEcZSzF/6qmmMC24cD5h4iRpdGLuYl7I2i+LqK7uOypgZs8hW9p56NO966W93bW841P49C5j4Kqf/Vau4bzOZe9NajcZF/TbBVQvrp35TtCNVCdMwVrIdDnvl28SBuMF58rY7xn/VQ6utApsNGc9RD+54v6VTmw+NjiUa5gZHb+DEMLuNq93Sf/HPUTH6LP0ISy2oRHgp12FMSKdxuCeDmXYtXvReY3dib2RTF2dli/Wz+SgsV7XKtibRJhwV1mXB5fpE4A4P16HaBOvpZG2zYcicnsvASz6DXyU4crnqQI9U6YTUeopsHbVf1t+WWl5VlZNzpTYNO/Q20aDnZ8QxGJt8bVLHs5r5mmOBZDYi6pF4OfduxMO3VtimnXmXacvtcxvYkLb6dBEPNED35OinJ5NcFlnDpyo6H8QfatuXUl0xjdpakqo77Kmn5LeE0VA3Zm7fuz9JtO7Ntaq19SYtvfULb1gzRky339n8PsdPlpLxvVOS4Mbe10rSmlArQye8kQ1M55Jw5di1sO3GOXgMvYPZc/pr3Nk/TT3kdEXLW3AImGcZHfmRY0Yus/OYSUI2uG6ITajffLl/1Vu3f6veQVGJsU0cvooahSQ4/o0AJuBNuKsTn3TT00m5qhgDHZZoffhRw+OrL2ma3eenw2HmoGXB1dIe+JujbW5SeHs9p5rgRXUXHMgo7L2666qNE+SsuW4uHODikBd16b84VEdXf3NH0k7fhpnawH252nLwFGmT+NyofsBVTD6z+yC05ybB+UZb5LiE9212jkADljbPXHSqJX1rUZSXiwH+d7X+pl69D2qJOonuUPv5t+PH9Oa2SU5rssAj/+PXlr/wn8yFrMpr+ckHuverTjHIX70VzYDX2UhkAyVl5QAJWtn8RmsSfMSqa5/VXeVZWRYzNLX7zSbZLTnHK24MjNBweak17lnzJa3RCWX0lr9LbpF0615TYft8M1wM6e7x6QYHKAGvJP0nMSSLbioBGiy2ijC3dBsQYnVaBL+GWi95zl9E98KlQ3czXZnpZLJwEeso7TJV8LGEQQAommQCY5ne6kvbNLnEXANaHuDggfpM4AEMKBFXH/4AgtQbI3ADVnJROBc48TdcxOdeSsiAjP6x+CiZqrGPW7Q9UI1Fo254KiJJGRkGC7ucwc6RpL3pAS6uI0SyIyWfDC+edgrcweCuzq1DrpJPoV2osB6ZH+aJJ0AX7GsnF6imCYI4zwQToM/C1kjRs4ly1EEzqFmRAr0v6+YdAoQ0KZoOf3EtvKtxVcYWIx3a2QxHspbgojNHysthiS1aPKUYdCyzNBqXORc8jiP7tb397KfTcwKlzvKQ59b+tHQCgV+nCu14B2vHf8OLAYP+JTggJRrjZgNG7PvVPQXRL664GeKY00STDO3ZDogQeZzqFJ0CV0o1d0qTa/W4mZGkWyBYDxGZxZdPHM8BK/rxhalRdJmn9VhN+2uGEKc30ZTXurRcNRm2xxp8fDSQwUFZFsBILnbxaoUFR+rLNTF6dRjaT12y46twC13HB0UnLyDD8uPqLjl4Vk7bmv+xon8QkJM111P5ffufRljOXDt1v09x8MAKzgnBFYW5AIAuFQRKnj0mLLe1i4AQ+mRM6E+rFHwVOkIWWA6fBp3aeFVP3stPjuKRbOHdvdpmFTv/j2scU+EmyrPtnHk3696gXJ2zyOlNJK77BnWpXl+nH4cdJBhfhHTgkS2Bs9SpPAC71u3dJm+q30bPDTHdNajNcbBhmVl0+B8wU8RFmg1k7va5qLIOWbELZZkYym+XZEgeyHmGaYWz+hfj8+JpwKe4CL2XciUnR1f3jDv3vOSlQ/ThWfqS9pLGLEhgUhynfzBhGa2Uzjs19iBDhnk++ouL5oQ6JLu1bmojpVKbAag9oMRr6woVU1TDYsJrXKNnmxsTlOdunqI5ncFFVRfL5XKEmeGk0lOgmOYoS6GC6dMoTKKlmaiEF4pBTo8zGQTEq19VEhqH2cqDbYnU1h6OuH8z61mVuOGe6c0Ew8wow7Wz5EyXqRheCj/4cRBKccllHW5zQELI2dLDFa2RzrrUYXK1mWpsRVNMPVnb3PjMPVXUm+ceY3D0VE1790c0y7NiCtV8BMtoYwWEZTgUMKuRKjO3QsIOOVCSwPAjUrPvfzwhk3SxdYVHYW80MtwCwTT/T2Q9vy1iY35/QLnlMdqSoP+pYD9hg+SG5ZJQbAaBEvTVAERb9w6n+MzLRi36Wp8eDGRBCPf1V6GogZUcY5imgO4TGvhNWKWvSPsxgoYOrubahgfNDjs2jwTb7gK3SYG7MU9kX9LECl7KKGGQGfbGp0o2sFiiVLFYIM0ZSA8Bl5lwk6VOgM38EiJl2+Jwog5PWTAu0T3GRxFnVj61X+fFzkhHC2R1EFLJB0FKSb8mtRKWoiRRL8jhR4W81e/PFA3X6eXcsRufeqhvA889zTILmf8wSOUYZIhoLbMEmh0e5gRYNPVrspeFvvWOiCSQ3O/qtecjrIKc/khQIgS6f+hhysWic9NjRAywXc9bIK/EpTs89SNUahsHFtMgl6pqI2RKGxLATnsJAudHWAs88g7lhPftycsLnGDMtDFe3+ovu4+MpRa/zb1max3x+VuoMpiNgzl/6HxeOBli9+ZHAGn82LDwkqMD61fGe5VmYKbdil0wvBBNBZrFeekYW6tdNpHEZ9DFpjJZuAbBa/pZyHhBNuG20w8/cG8Uu5/E6AuJ10jIyDD+uPiBer4pJW7MHxOtDtlrl2ltUpE7dBDthmrJJQlpBatlMc/NHhF1vrrwlgm6qRdVo4NUV5gPf9fcKFVmcXpyrp5pj48TEJW9c9Iin0oCRS024+hFQqZ4NIGfD4pu8OB9JDGPfwJMfJPRtMpyoX1ePi0GX9YDgIT8lu6lRQBoVYdD+vA0cNMqsBwgR+e/vRX4+SVFAkQid1/48yUREGhRFCAQdmWECgseooUGuJQwhgNz2PRYSLzMMOuZ9Oe2IQ8gXsPiQiT2m60JCaPLVi2W/Trp+IWLNBqIPxT5Inh/V2oW0yTBpf1l7ep9GDZOGWIPP3Pur2DRPC5opl7nmqJl9hdvF2PxYxgf0NsHSFM8RHBx0obFUaclBeViCzURTZdQyaXf2cKog1oBvxX6M+AEgZj6mzIUvIuG8Ex+5a10yonopRQkCXrJOhp1BkTUBZvluHfPAZkK3DjvgzO3W8XvyWF3FxT66PRe7p7hE+7+S6kmig3s3avwPOykYZsOP4UaSqeIu97oYYQLsitkhwqx1YIVcuzTQWANJDsozwaLHCgGe0Ga97ukqLgtrH+lPYaGroSVBbbI1kjPOmB6dd9n0R16h5a+zaylFCZpf142hQZHlr7Pv0DeM9tscMyi7wWkV55OA4Iw4YPnqzy4hrVZxkgnhzOskqFmOLwcukw1DrlhhumU+v8P7p+RUJ6Na9EzWCckG9Ot/XDeAej2WP411opITI1hu114LjBzhwIEtCBNQ0aZjPYHI+FiirzDvJW0txsnvHb1mivp5Tf8rZ/QZVyvVucjqjIgogK9xoPUwJTK3tGFKNrAGpvVZxeq3f8Az/ahihc31jS1WwOOsP+faNieCXMVFRaX6C5mWUgMTXiJuScIXbid9pKCbSZsLSBcpQGgVs9QSYDblXOWErtmnKwFby7+DXwKwJryRd8LV3BfzV09o9yU/8yHPhJ/lI5hAyQxlYuk0b5tBtdSihQxqprFnGEBKNDQa7viqM277ducCa3y4jZ/J0eNNltR8PZ1Aqk6n2Ya5/RtfuO5jRUEfozapnlgMPrqLDLVGvvo52MEBqJRStLA3JG4A8QtKmwsTvu5s+Kxh8BX3xLv8EFH/rjtSftLA0TEnDnzZJICkWpVJE+rcQmWzMLijlTJpjhNxEVBbxc5zPlRNud+0hdPsW03/+AkXA5KHzjYgsxqokGe+9+fP5a5ImlSTE4f/oNsWn1OzpauHhajTKkCC9fsaV+g9KmsHzuhNkR+nQwnbOHcaxhatHh+cQiYt0p2xFIA85D/hsRB4DF0x36r28TFJ8S8omig0Q98gy2j4de33s4MqRrubmf0+LnYpF9iw1kI7MhCimSNs9qJzu5t0kvCFopkCwanXx2Y1Uted7zKtyos6OHlyjIvn6++7pzg7oDv8RVydC9zE7lkBr5aAhVb3o/koQ0Rgb8SaXwJhAtIrDB4aPUwaUnTAMqBB/viJiRkwwVh+NjBcxrsvNxmWZfcl6CY3yOwjEZ4RSUqz+sWuTLNV7JhluFu+z8fyQDehB8gYzM3tCPLnGZ3R/voYJ+lFVcW7J3LZ/iZRLLXtU0MFgRwoOZfADKRYfcYpWC+jY59kxtU3JXbUCL9TB09jiJjg8EzBJAMcJbwMY4GQJTdVcHQZNUfLtwRsUcOYVJVxHftDDGi2MJhtTKNqzga/m+MpLyos2yMeYKP73RPan1P0EJdf5A8faSJm78cUmO8iGRkYjlxJmHeMUp3D4IXVyaTBVsIkO9QyzpjDZX6oMDJwaVe2BxVWp9VBBTeU5o0PLKjD+H4NluuHFR8QiC4Ms3iy7nsvYKP0MtwEPs65Mr/N0/RTXmG8t1cudUcNTMnVfX/4hX+vajA+5BFfT3su1taF0411ZRa3uHz7zAGIUGjwCm/U+VanwQQwU1veqs058FX/cJGV3xRHqRQJ36vdz5OMYqMw5ussS2KuBWFrEHHWHLJX+ZHMlIYDGFVl6rGLbppPI9v/vqERS2pqq+YmhhGcCNc1E3DAbMSWSPI0LlnlCZ4YP51s813mpPnBcjiiqkw9HNFNM5c29O8bGo6kprZqbmIY1f8WkytxvShkmBp+nOYq0B5JnoYj2DzLwE8n24zOtuSi8Q59TdC3tyg9PZ7TrI68YrrXk9SffM8nkwO4/AaINjSEmfWIVdtz4pAp0Lk6tFTSPp8NUX69FyBjLBBMTOVlIMtpWJt1LDMH83ZGrRUNVStwwnKE0/ocruwxNK2b1TWuUz3XHxWugYrOvTDfozdJUVav4yr+HJfiPU5d6x5V/SMckpu2+ZnqzPb3+pLqGP/j1/3nHPd1/DkdqggDDsc4/n4VV+hAwkaI7OlSsBGaQNMU/ld9kAg005dATfSFGvbv8l2cJv9E+67HgYYAGqhJgEzXeJwdzuSJmNhmXwQ21ZeaqIfuq4KcxZb5udiBrYFkUiUFSo0Ut6g4JmWJMd4dcgsSiCRQ6yKVpmX27Y7QKlsMtchS6PTM0xTSjfwM6kNKDLh2FxYg765Q1kJXbmirfhkiNVdPobJYT2TYrKI9dUPaFvqnfEIDfQnEvy/UKVD7rYEDYV8Cit8V6gbANs7pe1Q95dCnwxOAwyFHo2uzwsMzHsa+4ukU+m64crBFlkTT4HDCJLQ1FEHNDKW6r6hbSImfUFcCfj9doYb9kJFX4D8UQQ0MpTqYySdc9WxrPNXeJrvqXED93ZeAJuoKzXpA0QpHoOiPqCWK3scE18b9U0eaTgp0hAc4kErVawyhTgSUJl9R8fyQHCHt2WKwUYbCzNp0DGGZwWkahc1pMtvG+3CAb5K0gicybRUj0YRaZpIqPi6BQgXLjsoYl23F93F2fowJpBXGYalUctCUxrJohNC3zlKYaX5/QrvkMdmRLRAV7VNmAxm9yhpwHWO7wNU/tG+PxMlRSQ7OlcoaTtIZy2UjkWmfPsTQfo0uVPQWKTdr51NcJHE2xBq9yo+fkyyW9ItJJYVcynoaef88x+SXj1kCTTtsMSQDS+FmHXOTaKbe5v/23xFfUS6QmSTWuOT1bAP3mpqlJbfASFtDtzDoX76Ji4K+CFwQ9KW685oEFbdFAq7lqTLwrGYo1jQyOK0IbQxFUBN1qZb79Xc8nWdxenGunuoTtWZokp4nqMkhKdQ1NNKRAFuSDQxVBrVLisvIaBNDaGWnenShoiGzEz5CLGtEyb+lMOH/e5GfT7JG2kJFSy2FpqU2frPQSPs7xL8tMtxSsBlzpXsKlky1qWApNVLAeXsFKWAySAqY0lAKRcvq1sy6UTK8UGXS7jTat1BZa+FGmjJpI02xphEwO6XQHEgFNQwSGpwpSbbAQ5HsHMlos8smvpO2Iu80lkJrUiYrFmBMphw2I0OiVY9PQgGoyJPAavJUumMgMTGCeB4k0oAHQyKZbeOyFZSM0EgMs1WTNIw50PsSShgHEmJLcQzkMBNAf7xNBz0Wj7jpUvCYmyYwb6qJIa5qrqHQNNkQaW+xIM2kGploAkR5hZchLI1iNcISajckbIhIYAvCEqhWjhyprg+HeIRi7w1lYL8NxbrxkXlXKo6NTDE4LjIUWlim0o0AVQZDMTVc4n8s5I1QZVAjVLFuHYUyhDdbqsFdJAHXVQKVbouIeSCyf/0MXpVy5eBWkSXR3jHl4C1A+zt8p5Qb3DMMAdTEaakvgi8Uu1IT0ftTD1iDvliqiOmxiTTqkdCwlBK8OZMRW5wSP6DjKYWHEphMd2o8UBrehikkEElUt2PGLXfHlPKGBQrVaadps3eoJtvLrzF5AnhtwdLojFzkZYmZp/JWRRLQyAKVzsjN2kd6582VgwZmSbQntGBgFOCoFqSDz2xBUnNB1M1rG9UfqTFBkMQjNKYYPDJjKLRf7PEUJwdo3BqK4C+0K9WNTM0KSDUo8RTgeMQTGZx7vkNVhQrN8CwjlJ2JQrRaE8QlHkf/QsnhCepTrhxWnyExa/B1gsFdwmqLJIpmKSpNy1xMGqFZrhxqkyPRzfrP2U4x6dOl4JxPE2gPu/nYFsABN08CH2rzVEYty63KlcvbNLUq79Kn8dhTk5v4DEY1qfndL+M4JXf1ktDpfbHuUImX6MRn28DVrNVW7tEmUKg851oipG+4O5pW+EmIJKoz7ujidEoTtH/IW/rEQgr5ckSgMJOhJddL0LkoGdxzG153RwOdMSh7F0sz71ZzL1cmUr75va4Qw0K0iUAic1ZlqQym3v5FKDjf9qWySbYnMPBQlDfFlMr8FE2bkr8Uk2ENIFVADqC2kUgnhkHbQoPUMwi183o0uL5TdRRu7EMF/tUGlQuM88PHIsh97NmaCv/6houJv/wLVn1T09BvDfR2gamDGQV6UzFYRPlMwt4cwhxPj2NldN+aXbSMWUW5qrKXDURT3WsFJSdobAe5Kl8oeDflQ1wcUOVgyrai3ABShVUKLtOEeBpTfo8sQYhPkH6J0qgMvTRxU615ThI1HGHlaJKRQgo12KcwfT3JIxd7FdnledStz1G7qE+U44l5ZbmS8IMPoqj6GYeCC7dxAZjJnqMEMaB8FDGvrBhJAGUl6i3FZPRjmajnC5gHJAxgCugBUGMC5bsee9XbPahKaZ7Ev7rcfprUkz3/cleRM51cUZbQey9NqHq/seyPGhSDp5xYv2oAVwxKxSVnKQyDoIaQD4JyYi/9uAQzXCZpHR2556wwAkcazgRGGBqhdBc0ZGAt11qgDfkJcC9+GQayZ7z2ZuieaiqXxiJRiOUx/9yU1JQ+JXVYJjMvGaP+RSOwWoYpFUtg9vlks/6FX2Cy9cDXl011rsi7+t1hrF59OAKIqAYk/2LU7x8BKvpdoPHf4/xZffNZyx4D26upOspXzPIm1eRK8e+siVKyN9RgTdAsYmFY88jnfpNqnvp8YebpLmGssMNXCokcaOwRykIaxgY1fCX/Q+pMZgGe3oPrR3WFkDgBYgwwTFRBA5wn24iNDiCdalk6hTJAtIJGC0UUAldcuastffkf0eEKpNYwqq5VUQyyQCsrj5ug5iWJmQCz1sU98GDg1iT61Zy0SrBlnWsHeDGLlUE2aIp+KTusDqSWEGlDrGUkQT1oQ3hcyfCq6fEgkAZDw0QmYMKNKHY9IJ3/nQ8UyIRUVgYoce55LtgK3YQUAvI6ntWCUCFlBJYHMZP+IwHJg30o85hEEphGbxxNxWBmUgebobkZRpBxuCtTBMuRRPoBb9Ic2ChuYvR2MTOIAVc28JCCtySAkG+0qhu3hbCS28SmWugXIHqOKl2c5NThjlkF/1jmpJUqDWIMhZOSnFqrlEQbQI1lmEQRsMr0sMCYxQTnCAbBvmiL2oTrCmJi/SRuUjvYJ7o2c7IR0JxsyrCQGwgK1kZMoQrCtlYTO8B0emyaWCu8iZTLSeO6WuWVWputGxdpRhLVJZI2a2xSNZ9JjGLCl4mMo2IOR7txXwgZDpSTj4Zh7wGi+/h4StEQEVGuOEepWLpwcRmbZYsk5uKEKvchGSPWcV1UWUKp9whyd8IXQk2S6vIokmPUV8xdIpH/2Sq8ql3cTKXLlEgkF9ndZYqP7klqDj/68ww0fbqjrqCH+Ah3Od1H4vk9iURTA//RyZ/gzGUiVfBWpb+lWcUQaDIJaEu4WQWmtTddH55WaSeAKoRRhDi6pCr1qx91SQxcvb4cWTCFmYi+g8ZwpF5HlSM6qK5EX5pGIzITuncQGQzJK9YWK/rt2ZalslsbmtGCztOb5Euk2aq+WIrOo+gTf7ckvq7BswGQTi6465sBJqo0qQUHjXZVVDU2cRQhhqVw6kFBrqPBvU7uxwdXUOzeRvvxQSG1GS7KENmeTNPa3dAuDfXofl2EOfqw3XIb8CR+FRdCjg/1PG16BvkVW1mRyP9WNryqXaD36Bbvz57iEu3/SqonqgVRcV0Vb+owdflQ9qSqNEy9uyGY73bgLzcDXMGTImBN6RdvlCPAk2k+0v1ubB+2lt9xYS7z9CkGNAMiRRJgQKTTIwz1wMwH9ioCyQyUSyElfYiFkSJ5A2Fiko3Bj1m034W+kl90zGOaLk+E/JvgKPwqzWfSuO/CRHh63UGnwYgGvnItWUJPgoM1hflVmbLDdSPUhdzQbvl4wjXt+ZjkI5oBD6QMM9QBSVXaL1mVK2XEAbdBZ0tp/fd36LgAfO4XzZMlFbl2R+DliZIs60379Wsy2XgwkOrLkBOH+DhmN4Viwyil9b9vnNoMcK6gCMhkBIwcpnUVeJElX2qAo8unpOYleowYZkZymW/YHEPRTZZUSZwq1pKqCr7XkXAmpXbq0WRHGm+Mbq0sNqW3i7RuIHWVnOBFuFl6K4coV0NSp0hI8CRaTkWumMrhBFTNlK7OKiXjI+Ph2SSqqQukCzFrTaO2Tt3gavLq+VFLSAemv0CGqYPdI0P5J4aLOGUiCRfHOzrtWPSmyI8qe6jIQxgEzrDWLmyUCdNGm+IhtzAERbx2M/Tp4SLF/k0k8r9xE1LcNTWl2etcRvYUiHIMj+0gpb6v3Z2whHR47RgvS0LnEq80J9G6kmNcPF9/3z3F2QHdYdMOKdSAbYm2ksoobE631iBwvjZ2h0KnmWu2JWAWuZFGIH8Ya89Sr0ttSd445WSorRNi6NPkzSOMTJPg+TOTYt+uq+J/+z6vicAEadGbBB5IFdQK3wVnF35lQrj75gGESX63UWaJ2AxrSqOwtHLFoKxvRB9VNjeZaWQGCYYOpg1TkNCVvKq1EMQwecwiNnWuaCMFtWI+ARL6NtOKIlEvwwHM10ZYqNOwuTiD25hDQR3SHGCiteY7VOZPczXHkAMu6lnLjAHQelcE4CHmu6PYKHLYuaWxoBNNRfWB0VWelVURJ/Wsgnen/eKkC8X8kEdigipgf+eLt35N5BC3mV02SBJ3NesHXTIuD2anE3cYWJIiVytllSNkVpNQKchMgcJkLROtNpJjeNQBSdvuu8dkshRp4ww7PFNTm6unUwtv/AxuBrWpPHOmvc+kpgN2JeM4hscTkJmv2bgoUu6NM+wQKV5trp5OLbxxJPoZ1G53kULKQONZTVJf8UDbf1sTzKRSNcUduT79osduYgqsjM7WNFRdru96zOcEN61p3bhOjFzz7httZH3cGY7Q/+mXf9VfvWiY1IbHvYyKvuzViyYpavsD/rPKi/iA3ud7lJbk11cv7s649hE1f71GZXIYWLzCPDNE8kQPTDuam+yxDvhO0mlyEnUkXXGft72K93EVXxRVUse1xMU7/C2RdL/Ey6Q+D/qM9jfZh3N1OldYZXT8nDKHzq9eqNt/9UKQ+VUTaK/0oQIWM8EqoA/Z5TlJ973cb+K05LabMhZX2Pq/I/x705f406zQ4bnn9EeeGTJqzYc3qSjb40/uAR1PKWZWfsju46/IRbaPJXqHDvHu+bZO40WcZmRM9B3Bmv3V6yQ+FPGxbHkM9fGfGMP74/f/+n+5YQwPS7MIAA== + + + 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 d6f664d19d..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); @@ -3977,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, }, @@ -3985,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, }, @@ -3993,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, }, @@ -4002,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, }, @@ -4010,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, }, @@ -4018,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, }, @@ -4042,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, }, @@ -4082,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, }, @@ -4244,8 +4250,6 @@ public IList Settings() }, new CurrencySettings() { - PrimaryStoreCurrencyId = _ctx.Set().First().Id, - PrimaryExchangeRateCurrencyId = _ctx.Set().First().Id, }, new MeasureSettings() { @@ -4767,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, @@ -4791,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, @@ -4799,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, @@ -4807,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; @@ -7894,7 +7914,7 @@ public IList Products() #region Antonio Vivaldi: then spring - var productInstantDownloadVivaldi = new Product() + var productInstantDownloadVivaldi = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -7918,7 +7938,7 @@ public IList Products() AllowBackInStockSubscriptions = false, IsDownload = true, HasSampleDownload = true, - SampleDownload = new Download() + SampleDownload = new Download { DownloadGuid = Guid.NewGuid(), ContentType = "audio/mp3", 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 0eeab2be55..ff7fd39e2b 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -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 @@ -272,6 +284,132 @@ 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 + + + @@ -280,7 +418,6 @@ - @@ -529,6 +666,99 @@ 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 + @@ -547,6 +777,7 @@ + diff --git a/src/Libraries/SmartStore.Data/Sql/LatestProductLoadAllPaged.sql b/src/Libraries/SmartStore.Data/Sql/LatestProductLoadAllPaged.sql index f5b367a08b..522b70dc27 100644 --- a/src/Libraries/SmartStore.Data/Sql/LatestProductLoadAllPaged.sql +++ b/src/Libraries/SmartStore.Data/Sql/LatestProductLoadAllPaged.sql @@ -24,8 +24,16 @@ @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 = 0, - @WithoutManufacturers bit = 0, + @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 ) @@ -396,12 +404,38 @@ 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.Published = 1 AND p.Deleted = 0 AND (getutcdate() BETWEEN ISNULL(p.AvailableStartDateTimeUtc, ''1/1/1900'') and ISNULL(p.AvailableEndDateTimeUtc, ''1/1/2999''))' END @@ -472,6 +506,45 @@ BEGIN 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 @@ -488,22 +561,32 @@ BEGIN )' END - IF @WithoutCategories = 1 + IF (@WithoutCategories is not null) BEGIN - SET @sql = @sql + ' - AND NOT EXISTS ( - SELECT 1 FROM [Product_Category_Mapping] pcm with (NOLOCK) - WHERE [pcm].[ProductId] = p.Id - )' + 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 = 1 + IF (@WithoutManufacturers is not null) BEGIN - SET @sql = @sql + ' - AND NOT EXISTS ( - SELECT 1 FROM [Product_Manufacturer_Mapping] pmm with (NOLOCK) - WHERE [pmm].[ProductId] = p.Id - )' + 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 diff --git a/src/Libraries/SmartStore.Data/app.config b/src/Libraries/SmartStore.Data/app.config index 4f592fbcd8..284b7ee573 100644 --- a/src/Libraries/SmartStore.Data/app.config +++ b/src/Libraries/SmartStore.Data/app.config @@ -1,15 +1,15 @@ - + - - + + - - + + - + 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 c43d075957..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; } @@ -108,5 +131,5 @@ public virtual Customer GetAuthenticatedCustomerFromTicket(FormsAuthenticationTi 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/CategoryService.cs b/src/Libraries/SmartStore.Services/Catalog/CategoryService.cs index d563031d2b..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,35 +314,24 @@ public virtual void DeleteCategory(Category category, bool deleteChilds = false) var childCategories = GetAllCategoriesByParentCategoryId(category.Id, true); DeleteAllCategories(childCategories, deleteChilds); } - - /// - /// Gets all categories - /// - /// Category name - /// Page index - /// Page size - /// A value indicating whether to show hidden records - /// Alias to be filtered - /// Whether to apply instances to the actual categories query. Never applied when is true - /// A value indicating whether categories without parent category in provided category list (source) should be ignored - /// Store identifier; 0 to load all records - /// Categories - public virtual IPagedList GetAllCategories(string categoryName = "", int pageIndex = 0, int pageSize = int.MaxValue, bool showHidden = false, string alias = null, - bool applyNavigationFilters = true, bool ignoreCategoriesWithoutExistingParent = true, int storeId = 0) - { - var query = _categoryRepository.Table; - if (!showHidden) - query = query.Where(c => c.Published); + public virtual IQueryable GetCategories( + string categoryName = "", + bool showHidden = false, + string alias = null, + bool applyNavigationFilters = true, + int storeId = 0) + { + var query = _categoryRepository.Table; - if (!String.IsNullOrWhiteSpace(categoryName)) - query = query.Where(c => c.Name.Contains(categoryName) || c.FullName.Contains(categoryName)); + if (!showHidden) + query = query.Where(c => c.Published); - if (!String.IsNullOrWhiteSpace(alias)) - query = query.Where(c => c.Alias.Contains(alias)); + if (categoryName.HasValue()) + query = query.Where(c => c.Name.Contains(categoryName) || c.FullName.Contains(categoryName)); - query = query.Where(c => !c.Deleted); - query = query.OrderBy(c => c.ParentCategoryId).ThenBy(c => c.DisplayOrder); + if (alias.HasValue()) + query = query.Where(c => c.Alias.Contains(alias)); if (showHidden) { @@ -180,10 +351,36 @@ orderby cGroup.Key } } else - { + { query = ApplyHiddenCategoriesFilter(query, applyNavigationFilters, _storeContext.CurrentStore.Id); - query = query.OrderBy(c => c.ParentCategoryId).ThenBy(c => c.DisplayOrder); - } + } + + query = query.Where(c => !c.Deleted); + + return query; + } + + /// + /// Gets all categories + /// + /// Category name + /// Page index + /// Page size + /// A value indicating whether to show hidden records + /// Alias to be filtered + /// Whether to apply instances to the actual categories query. Never applied when is true + /// A value indicating whether categories without parent category in provided category list (source) should be ignored + /// Store identifier; 0 to load all records + /// Categories + public virtual IPagedList GetAllCategories(string categoryName = "", int pageIndex = 0, int pageSize = int.MaxValue, bool showHidden = false, string alias = null, + bool applyNavigationFilters = true, bool ignoreCategoriesWithoutExistingParent = true, int storeId = 0) + { + 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(); @@ -400,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; }); } @@ -465,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; } @@ -549,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; @@ -561,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 9f84b9a8de..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 @@ -504,8 +516,9 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli // associated products 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 6b7650d69b..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 /// @@ -96,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 /// @@ -123,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 703033f375..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,33 +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); + decimal GetPreselectedPrice(Product product, PriceCalculationContext context); /// /// Gets the product cost @@ -162,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 aa3199641f..0b1f7649d5 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IProductService.cs @@ -2,8 +2,11 @@ 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 @@ -105,7 +108,21 @@ public partial interface IProductService /// GTIN /// Product 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 /// @@ -151,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 @@ -264,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 /// @@ -293,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 /// @@ -349,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 e0d765eb5b..e9bc4a786e 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ISpecificationAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ISpecificationAttributeService.cs @@ -131,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 2bbf0d4f65..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); } @@ -335,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 e7d0550cde..ca45f495d7 100644 --- a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; @@ -26,7 +27,7 @@ public partial class PriceCalculationService : IPriceCalculationService private readonly CatalogSettings _catalogSettings; private readonly IProductAttributeService _productAttributeService; private readonly IDownloadService _downloadService; - private readonly ICommonServices _commonServices; + private readonly ICommonServices _services; private readonly HttpRequestBase _httpRequestBase; private readonly ITaxService _taxService; @@ -39,7 +40,7 @@ public PriceCalculationService( CatalogSettings catalogSettings, IProductAttributeService productAttributeService, IDownloadService downloadService, - ICommonServices commonServices, + ICommonServices services, HttpRequestBase httpRequestBase, ITaxService taxService) { @@ -51,7 +52,7 @@ public PriceCalculationService( this._catalogSettings = catalogSettings; this._productAttributeService = productAttributeService; this._downloadService = downloadService; - this._commonServices = commonServices; + this._services = services; this._httpRequestBase = httpRequestBase; this._taxService = taxService; } @@ -64,31 +65,44 @@ public PriceCalculationService( /// 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)) + IEnumerable appliedDiscounts = null; + + if (context == null) + appliedDiscounts = product.AppliedDiscounts; + else + appliedDiscounts = context.AppliedDiscounts.Load(product.Id); + + if (appliedDiscounts != null) + { + foreach (var discount in appliedDiscounts) { - allowedDiscounts.Add(discount); + 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) @@ -99,20 +113,20 @@ protected virtual IList GetAllowedDiscounts(Product product, Customer { //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)) + if (discount.DiscountType == DiscountType.AssignedToCategories && !result.Any(x => x.Id == discount.Id) && _discountService.IsDiscountValid(discount, customer)) { - allowedDiscounts.Add(discount); + result.Add(discount); } } } } } } - return allowedDiscounts; + + return result; } /// @@ -122,20 +136,34 @@ protected virtual IList GetAllowedDiscounts(Product product, Customer /// 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) return decimal.Zero; - var tierPrices = product.TierPrices - .OrderBy(tp => tp.Quantity) - .FilterByStore(_commonServices.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 @@ -152,7 +180,7 @@ protected virtual IList GetAllowedDiscounts(Product product, Customer return previousPrice; } - protected virtual decimal GetPreselectedPrice(Product product, ProductBundleItemData bundleItem, IList bundleItems) + protected virtual decimal GetPreselectedPrice(Product product, PriceCalculationContext context, ProductBundleItemData bundleItem, IEnumerable bundleItems) { var taxRate = decimal.Zero; var attributesTotalPriceBase = decimal.Zero; @@ -161,73 +189,80 @@ protected virtual decimal GetPreselectedPrice(Product product, ProductBundleItem 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 attributes = (isBundle ? new List() : _productAttributeService.GetProductVariantAttributesByProductId(product.Id)); + var selectedAttributes = new NameValueCollection(); - List selectedAttributeValues = null; + var selectedAttributeValues = new List(); + var attributes = context.Attributes.Load(product.Id); - foreach (var attribute in attributes) + // 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; - - if (attribute.ShouldHaveValues()) + var selectedValueIds = new List(); + var pvaValues = attribute.ProductVariantAttributeValues; + + foreach (var pvaValue in pvaValues) { - var pvaValues = _productAttributeService.GetProductVariantAttributeValues(attribute.Id); - if (pvaValues.Count == 0) - continue; - - foreach (var pvaValue in pvaValues) - { - ProductBundleItemAttributeFilter attributeFilter = null; + ProductBundleItemAttributeFilter attributeFilter = null; - if (bundleItem.FilterOut(pvaValue, out attributeFilter)) - continue; + if (bundleItem.FilterOut(pvaValue, out attributeFilter)) + continue; - if (preSelectedValueId == 0 && attributeFilter != null && attributeFilter.IsPreSelected) - preSelectedValueId = attributeFilter.AttributeValueId; + 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); + if (!isBundlePricing && pvaValue.IsPreSelected) + { + decimal attributeValuePriceAdjustment = GetProductVariantAttributeValuePriceAdjustment(pvaValue); + decimal priceAdjustmentBase = _taxService.GetProductPrice(product, attributeValuePriceAdjustment, out taxRate); - preSelectedPriceAdjustmentBase = decimal.Add(preSelectedPriceAdjustmentBase, priceAdjustmentBase); - } + 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; - - if (defaultValue == null) - defaultValue = pvaValues.FirstOrDefault(x => x.IsPreSelected); - - if (defaultValue == null && attribute.IsRequired) - defaultValue = pvaValues.First(); - - if (defaultValue != null) - selectedAttributes.AddProductAttribute(attribute.ProductAttributeId, attribute.Id, defaultValue.Id, product.Id, bundleItemId); + // 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) { - string attributeXml = selectedAttributes.CreateSelectedAttributesXml(product.Id, attributes, _productAttributeParser, _commonServices.Localization, + var attributeXml = selectedAttributes.CreateSelectedAttributesXml(product.Id, attributes, _productAttributeParser, _services.Localization, _downloadService, _catalogSettings, _httpRequestBase, new List(), true, bundleItemId); - selectedAttributeValues = _productAttributeParser.ParseProductVariantAttributeValues(attributeXml).ToList(); - - var combinations = _productAttributeService.GetAllProductVariantAttributeCombinations(product.Id); + var combinations = context.AttributeCombinations.Load(product.Id); var selectedCombination = combinations.FirstOrDefault(x => _productAttributeParser.AreProductAttributesEqual(x.AttributesXml, attributeXml)); - if (selectedCombination != null && selectedCombination.IsActive) - product.MergeWithCombination(selectedCombination); + 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 != null) + if (selectedAttributeValues.Count > 0) { selectedAttributeValues.Each(x => attributesTotalPriceBase += GetProductVariantAttributeValuePriceAdjustment(x)); } @@ -242,10 +277,23 @@ protected virtual decimal GetPreselectedPrice(Product product, ProductBundleItem bundleItem.AdditionalCharge = attributesTotalPriceBase; } - var result = GetFinalPrice(product, bundleItems, _commonServices.WorkContext.CurrentCustomer, attributesTotalPriceBase, true, 1, bundleItem); + 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 @@ -290,7 +338,7 @@ protected virtual decimal GetPreselectedPrice(Product product, ProductBundleItem public virtual decimal GetFinalPrice(Product product, bool includeDiscounts) { - var customer = _commonServices.WorkContext.CurrentCustomer; + var customer = _services.WorkContext.CurrentCustomer; return GetFinalPrice(product, customer, includeDiscounts); } @@ -324,23 +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, Customer customer, decimal additionalCharge, bool includeDiscounts, int quantity, - ProductBundleItemData bundleItem = null) + ProductBundleItemData bundleItem = null, + PriceCalculationContext context = null) { //initial price decimal result = product.Price; @@ -353,7 +392,7 @@ public virtual decimal GetFinalPrice( //tier prices if (product.HasTierPrices && !bundleItem.IsValid()) { - decimal? tierPrice = GetMinimumTierPrice(product, customer, quantity); + decimal? tierPrice = GetMinimumTierPrice(product, customer, quantity, context); if (tierPrice.HasValue) result = Math.Min(result, tierPrice.Value); } @@ -362,37 +401,29 @@ public virtual decimal GetFinalPrice( 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, + IEnumerable bundleItems, Customer customer, decimal additionalCharge, bool includeDiscounts, int quantity, - ProductBundleItemData bundleItem = null) + ProductBundleItemData bundleItem = null, + PriceCalculationContext context = null) { if (product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing) { @@ -401,23 +432,24 @@ public virtual decimal GetFinalPrice( foreach (var itemData in items.Where(x => 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"); @@ -425,38 +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; - // sepcial case: one bundle item with one attribute and one attribute value and one attribute combination - if (bundleItems.Count == 1) - { - var firstBundleItem = bundleItems.First(); - if (firstBundleItem.Item.Product.ProductVariantAttributes.Count == 1) - { - var firstAttribute = firstBundleItem.Item.Product.ProductVariantAttributes.First(); - if (firstAttribute.ProductVariantAttributeValues.Count == 1) - { - var firstAttributeValue = firstAttribute.ProductVariantAttributeValues.First(); - firstBundleItem.AdditionalCharge = firstAttributeValue.PriceAdjustment; - - var combinations = _productAttributeService.GetAllProductVariantAttributeCombinations(firstBundleItem.Item.ProductId); - if (combinations.Count == 1) - { - firstBundleItem.Item.Product.MergeWithCombination(combinations.First()); - } - } - } - } - } - - decimal lowestPrice = GetFinalPrice(product, bundleItems, _commonServices.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) { @@ -464,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"); @@ -497,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, _commonServices.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) { @@ -515,6 +529,10 @@ public virtual decimal GetLowestPrice(Product product, out bool displayFromMessa lowestPriceProduct = associatedProduct; } } + + if (lowestPriceProduct == null) + lowestPriceProduct = associatedProducts.FirstOrDefault(); + return lowestPrice; } @@ -522,33 +540,44 @@ public virtual decimal GetLowestPrice(Product product, out bool displayFromMessa /// Get the initial price including preselected attributes /// /// Product + /// Object with cargo data for better performance /// Preselected price - public virtual decimal GetPreselectedPrice(Product product) + 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, bundleItem, bundleItems); + var unused = GetPreselectedPrice(bundleItem.Item.Product, context, bundleItem, bundleItems); } - result = GetPreselectedPrice(product, null, bundleItems); + result = GetPreselectedPrice(product, context, null, bundleItems); } else { - result = GetPreselectedPrice(product, null, null); + result = GetPreselectedPrice(product, context, null, null); } + return result; } + /// /// Gets the product cost /// @@ -565,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); @@ -583,7 +613,7 @@ public virtual decimal GetProductCost(Product product, string attributesXml) /// Discount amount public virtual decimal GetDiscountAmount(Product product) { - var customer = _commonServices.WorkContext.CurrentCustomer; + var customer = _services.WorkContext.CurrentCustomer; return GetDiscountAmount(product, customer, decimal.Zero); } @@ -630,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; @@ -656,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); } } @@ -675,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) @@ -729,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(); @@ -739,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; + + var pvaValuesEnum = _productAttributeParser.ParseProductVariantAttributeValues(shoppingCartItem.Item.AttributesXml); - if (pvaValues != null) + 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); @@ -787,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 3e8fd2d1f0..001e18789b 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductSearchContext.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductSearchContext.cs @@ -31,6 +31,16 @@ 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) /// @@ -40,7 +50,7 @@ public ProductSearchContext() /// /// A value indicating whether to load products without any catgory mapping /// - public bool WithoutCategories { get; set; } + public bool? WithoutCategories { get; set; } /// /// Manufacturer identifier; 0 to load all records @@ -50,7 +60,7 @@ public ProductSearchContext() /// /// A value indicating whether to load products without any manufacturer mapping /// - public bool WithoutManufacturers { get; set; } + 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 @@ -158,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 f3963aaf34..6dc93b582d 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductService.cs @@ -1,13 +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; @@ -50,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 @@ -85,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, @@ -101,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; @@ -125,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; } @@ -235,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(); + } } /// @@ -243,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; } @@ -309,7 +321,7 @@ public virtual void InsertProduct(Product product) _cacheManager.RemoveByPattern(PRODUCTS_PATTERN_KEY); //event notification - _eventPublisher.EntityInserted(product); + _services.EventPublisher.EntityInserted(product); } /// @@ -327,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); } } @@ -355,7 +367,7 @@ public virtual IPagedList SearchProducts(ProductSearchContext ctx) ctx.FilterableSpecificationAttributeOptionIds = new List(); - _eventPublisher.Publish(new ProductsSearchingEvent(ctx)); + _services.EventPublisher.Publish(new ProductsSearchingEvent(ctx)); //search by keyword bool searchLocalizedValue = false; @@ -378,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) @@ -390,7 +402,7 @@ public virtual IPagedList SearchProducts(ProductSearchContext ctx) //pass categry identifiers as comma-delimited string string commaSeparatedCategoryIds = ""; - if (ctx.CategoryIds != null && !ctx.WithoutCategories) + if (ctx.CategoryIds != null && !(ctx.WithoutCategories ?? false)) { for (int i = 0; i < ctx.CategoryIds.Count; i++) { @@ -440,7 +452,7 @@ public virtual IPagedList SearchProducts(ProductSearchContext ctx) var pManufacturerId = _dataProvider.GetParameter(); pManufacturerId.ParameterName = "ManufacturerId"; - pManufacturerId.Value = (ctx.WithoutManufacturers ? 0 : ctx.ManufacturerId); + pManufacturerId.Value = (ctx.WithoutManufacturers ?? false) ? 0 : ctx.ManufacturerId; pManufacturerId.DbType = DbType.Int32; var pStoreId = _dataProvider.GetParameter(); @@ -555,14 +567,55 @@ public virtual IPagedList SearchProducts(ProductSearchContext ctx) var pWithoutCategories = _dataProvider.GetParameter(); pWithoutCategories.ParameterName = "WithoutCategories"; - pWithoutCategories.Value = ctx.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; + 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; @@ -603,6 +656,14 @@ public virtual IPagedList SearchProducts(ProductSearchContext ctx) pLoadFilterableSpecificationAttributeOptionIds, pWithoutCategories, pWithoutManufacturers, + pIsPublished, + pHomePageProducts, + pIdMin, + pIdMax, + pAvailabilityMin, + pAvailabilityMax, + pCreatedFromUtc, + pCreatedToUtc, pFilterableSpecificationAttributeOptionIds, pTotalRecords); @@ -735,16 +796,21 @@ public virtual IQueryable PrepareProductSearchQuery( if (allowedCustomerRolesIds == null) { - allowedCustomerRolesIds = _workContext.CurrentCustomer.CustomerRoles.Where(cr => cr.Active).Select(cr => cr.Id).ToList(); + 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.ShowHidden) + if (!ctx.IsPublished.HasValue) + { + if (!ctx.ShowHidden) + query = query.Where(p => p.Published); + } + else { - query = query.Where(p => p.Published); + query = query.Where(p => p.Published == ctx.IsPublished.Value); } if (ctx.ParentGroupedProductId > 0) @@ -757,6 +823,11 @@ public virtual IQueryable PrepareProductSearchQuery( 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; @@ -767,6 +838,34 @@ public virtual IQueryable PrepareProductSearchQuery( { 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 @@ -875,9 +974,12 @@ from p in query } // category filtering - if (ctx.WithoutCategories) + if (ctx.WithoutCategories.HasValue) { - query = query.Where(x => x.ProductCategories.Count == 0); + 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) { @@ -900,9 +1002,12 @@ from pc in p.ProductCategories.Where(pc => ctx.CategoryIds.Contains(pc.CategoryI } // manufacturer filtering - if (ctx.WithoutManufacturers) + if (ctx.WithoutManufacturers.HasValue) { - query = query.Where(x => x.ProductManufacturers.Count == 0); + 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) { @@ -973,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 @@ -1016,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; @@ -1044,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 /// @@ -1169,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; @@ -1249,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 @@ -1265,7 +1465,7 @@ public virtual void DeleteRelatedProduct(RelatedProduct relatedProduct) _relatedProductRepository.Delete(relatedProduct); //event notification - _eventPublisher.EntityDeleted(relatedProduct); + _services.EventPublisher.EntityDeleted(relatedProduct); } /// @@ -1312,7 +1512,7 @@ public virtual void InsertRelatedProduct(RelatedProduct relatedProduct) _relatedProductRepository.Insert(relatedProduct); //event notification - _eventPublisher.EntityInserted(relatedProduct); + _services.EventPublisher.EntityInserted(relatedProduct); } /// @@ -1327,7 +1527,7 @@ public virtual void UpdateRelatedProduct(RelatedProduct relatedProduct) _relatedProductRepository.Update(relatedProduct); //event notification - _eventPublisher.EntityUpdated(relatedProduct); + _services.EventPublisher.EntityUpdated(relatedProduct); } /// @@ -1363,7 +1563,7 @@ public virtual void DeleteCrossSellProduct(CrossSellProduct crossSellProduct) _crossSellProductRepository.Delete(crossSellProduct); //event notification - _eventPublisher.EntityDeleted(crossSellProduct); + _services.EventPublisher.EntityDeleted(crossSellProduct); } /// @@ -1411,7 +1611,7 @@ public virtual void InsertCrossSellProduct(CrossSellProduct crossSellProduct) _crossSellProductRepository.Insert(crossSellProduct); //event notification - _eventPublisher.EntityInserted(crossSellProduct); + _services.EventPublisher.EntityInserted(crossSellProduct); } /// @@ -1426,7 +1626,7 @@ public virtual void UpdateCrossSellProduct(CrossSellProduct crossSellProduct) _crossSellProductRepository.Update(crossSellProduct); //event notification - _eventPublisher.EntityUpdated(crossSellProduct); + _services.EventPublisher.EntityUpdated(crossSellProduct); } /// @@ -1513,7 +1713,7 @@ public virtual void DeleteTierPrice(TierPrice tierPrice) _cacheManager.RemoveByPattern(PRODUCTS_PATTERN_KEY); //event notification - _eventPublisher.EntityDeleted(tierPrice); + _services.EventPublisher.EntityDeleted(tierPrice); } /// @@ -1530,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 /// @@ -1544,7 +1769,7 @@ public virtual void InsertTierPrice(TierPrice tierPrice) _cacheManager.RemoveByPattern(PRODUCTS_PATTERN_KEY); //event notification - _eventPublisher.EntityInserted(tierPrice); + _services.EventPublisher.EntityInserted(tierPrice); } /// @@ -1561,7 +1786,7 @@ public virtual void UpdateTierPrice(TierPrice tierPrice) _cacheManager.RemoveByPattern(PRODUCTS_PATTERN_KEY); //event notification - _eventPublisher.EntityUpdated(tierPrice); + _services.EventPublisher.EntityUpdated(tierPrice); } #endregion @@ -1582,7 +1807,7 @@ public virtual void DeleteProductPicture(ProductPicture productPicture) _productPictureRepository.Delete(productPicture); //event notification - _eventPublisher.EntityDeleted(productPicture); + _services.EventPublisher.EntityDeleted(productPicture); } private void UnassignDeletedPictureFromVariantCombinations(ProductPicture productPicture) @@ -1630,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 /// @@ -1656,7 +1908,7 @@ public virtual void InsertProductPicture(ProductPicture productPicture) _productPictureRepository.Insert(productPicture); //event notification - _eventPublisher.EntityInserted(productPicture); + _services.EventPublisher.EntityInserted(productPicture); } /// @@ -1671,7 +1923,7 @@ public virtual void UpdateProductPicture(ProductPicture productPicture) _productPictureRepository.Update(productPicture); //event notification - _eventPublisher.EntityUpdated(productPicture); + _services.EventPublisher.EntityUpdated(productPicture); } #endregion @@ -1699,7 +1951,7 @@ public virtual void InsertBundleItem(ProductBundleItem bundleItem) _productBundleItemRepository.Insert(bundleItem); //event notification - _eventPublisher.EntityInserted(bundleItem); + _services.EventPublisher.EntityInserted(bundleItem); } /// @@ -1714,7 +1966,7 @@ public virtual void UpdateBundleItem(ProductBundleItem bundleItem) _productBundleItemRepository.Update(bundleItem); //event notification - _eventPublisher.EntityUpdated(bundleItem); + _services.EventPublisher.EntityUpdated(bundleItem); } /// @@ -1729,7 +1981,7 @@ public virtual void DeleteBundleItem(ProductBundleItem bundleItem) _productBundleItemRepository.Delete(bundleItem); //event notification - _eventPublisher.EntityDeleted(bundleItem); + _services.EventPublisher.EntityDeleted(bundleItem); } /// @@ -1769,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 bb0bb7d806..cc146a0f5b 100644 --- a/src/Libraries/SmartStore.Services/Catalog/SpecificationAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/SpecificationAttributeService.cs @@ -113,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); @@ -356,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 f8d0ae9280..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,49 +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); + } + + public virtual void SaveAttribute(int entityId, string key, string keyGroup, TPropType value, int storeId = 0) + { + Guard.ArgumentNotZero(entityId, "entityId"); - var props = GetAttributesForEntity(entity.Id, keyGroup) + 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 + var prop = props.FirstOrDefault(ga => + ga.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase)); // should be culture invariant - string valueStr = value.Convert(); + 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 = entity.Id, - Key = key, - KeyGroup = keyGroup, - Value = valueStr, + 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 index c6530bda07..a232323b07 100644 --- a/src/Libraries/SmartStore.Services/Common/IUserAgent.cs +++ b/src/Libraries/SmartStore.Services/Common/IUserAgent.cs @@ -67,6 +67,13 @@ private static string FormatVersionString(params string[] parts) 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; @@ -83,6 +90,17 @@ public override string ToString() 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 diff --git a/src/Libraries/SmartStore.Services/Common/KeepAliveTask.cs b/src/Libraries/SmartStore.Services/Common/KeepAliveTask.cs deleted file mode 100644 index 74a382ccae..0000000000 --- a/src/Libraries/SmartStore.Services/Common/KeepAliveTask.cs +++ /dev/null @@ -1,54 +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()) - { - // FAKE a user-agent - wc.Headers.Add("user-agent", @"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36"); - 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/UAParserUserAgent.cs b/src/Libraries/SmartStore.Services/Common/UAParserUserAgent.cs index f0726c906c..a6b8e24305 100644 --- a/src/Libraries/SmartStore.Services/Common/UAParserUserAgent.cs +++ b/src/Libraries/SmartStore.Services/Common/UAParserUserAgent.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Text.RegularExpressions; using System.Web; +using SmartStore.Utilities; using uap = UAParser; namespace SmartStore.Services.Common @@ -14,7 +15,7 @@ public class UAParserUserAgent : IUserAgent # region Mobile UAs, OS & Devices - private static readonly HashSet s_MobileOS = new HashSet + private static readonly HashSet s_MobileOS = new HashSet(StringComparer.InvariantCultureIgnoreCase) { "Android", "iOS", @@ -32,8 +33,10 @@ public class UAParserUserAgent : IUserAgent "Maemo" }; - private static readonly HashSet s_MobileBrowsers = new HashSet + private static readonly HashSet s_MobileBrowsers = new HashSet(StringComparer.InvariantCultureIgnoreCase) { + "Googlebot-Mobile", + "Baiduspider-mobile", "Android", "Firefox Mobile", "Opera Mobile", @@ -80,7 +83,7 @@ public class UAParserUserAgent : IUserAgent "Skyfire" }; - private static readonly HashSet s_MobileDevices = new HashSet + private static readonly HashSet s_MobileDevices = new HashSet(StringComparer.InvariantCultureIgnoreCase) { "BlackBerry", "MI PAD", @@ -116,7 +119,8 @@ public class UAParserUserAgent : IUserAgent static UAParserUserAgent() { - s_uap = uap.Parser.GetDefault(); + //s_uap = uap.Parser.GetDefault(); + s_uap = uap.Parser.FromYamlFile(CommonHelper.MapPath("~/App_Data/ua-parser.regexes.yaml")); } public UAParserUserAgent(HttpContextBase httpContext) @@ -176,7 +180,7 @@ public virtual DeviceInfo Device if (_device == null) { var tmp = s_uap.ParseDevice(this.RawValue); - _device = new DeviceInfo(tmp.Family, tmp.IsSpider); + _device = new DeviceInfo(tmp.Family, tmp.IsSpider()); } return _device; } @@ -201,7 +205,8 @@ public virtual bool IsBot { if (!_isBot.HasValue) { - _isBot = _httpContext.Request.Browser.Crawler || this.Device.IsBot; + // empty useragent > bad bot! + _isBot = this.RawValue.IsEmpty() || _httpContext.Request.Browser.Crawler || this.Device.IsBot || this.UserAgent.IsBot; } return _isBot.Value; } @@ -247,7 +252,14 @@ public virtual bool IsPdfConverter 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 719f144ed5..afc1acdbe0 100644 --- a/src/Libraries/SmartStore.Services/Customers/CustomerExtentions.cs +++ b/src/Libraries/SmartStore.Services/Customers/CustomerExtentions.cs @@ -53,7 +53,7 @@ public static bool IsBackgroundTaskAccount(this Customer customer) } /// - /// Gets a value indicating whether customer a search engine + /// Gets a value indicating whether customer is a search engine /// /// Customer /// Result @@ -84,13 +84,13 @@ public static bool IsPdfConverter(this Customer customer) 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 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); } @@ -287,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 7641b6bd45..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) @@ -586,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) { @@ -604,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); @@ -635,9 +566,6 @@ orderby cGroup.Key // save the rest scope.Commit(); - _customerRepository.AutoCommitEnabled = crAutoCommit; - _gaRepository.AutoCommitEnabled = gaAutoCommit; - return numberOfDeletedCustomers; } } @@ -674,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) @@ -688,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) @@ -707,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 @@ -729,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 @@ -748,10 +656,6 @@ orderby cr.Name }); } - /// - /// Inserts a customer role - /// - /// Customer role public virtual void InsertCustomerRole(CustomerRole customerRole) { if (customerRole == null) @@ -759,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) @@ -776,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) @@ -804,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/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/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 3ffeb19847..0000000000 --- a/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs +++ /dev/null @@ -1,2079 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Globalization; -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.Directory; -using SmartStore.Core.Domain.Localization; -using SmartStore.Core.Domain.Media; -using SmartStore.Core.Domain.Orders; -using SmartStore.Core.Domain.Stores; -using SmartStore.Core.Logging; -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 IProductTemplateService _productTemplateService; - private readonly IPictureService _pictureService; - private readonly INewsLetterSubscriptionService _newsLetterSubscriptionService; - private readonly ILanguageService _languageService; - private readonly MediaSettings _mediaSettings; - private readonly ICommonServices _commonServices; - - #endregion - - #region Ctor - - public ExportManager(ICategoryService categoryService, - IManufacturerService manufacturerService, - IProductService productService, - IProductTemplateService productTemplateService, - IPictureService pictureService, - INewsLetterSubscriptionService newsLetterSubscriptionService, - ILanguageService languageService, - MediaSettings mediaSettings, - ICommonServices commonServices) - { - this._categoryService = categoryService; - this._manufacturerService = manufacturerService; - this._productService = productService; - this._productTemplateService = productTemplateService; - this._pictureService = pictureService; - this._newsLetterSubscriptionService = newsLetterSubscriptionService; - this._languageService = languageService; - this._mediaSettings = mediaSettings; - this._commonServices = commonServices; - - Logger = NullLogger.Instance; - } - - public ILogger Logger { get; set; } - - #endregion - - #region Utilities - - protected Action> WriteLocalized = (writer, context, content) => - { - if (context.Languages.Count > 1) - { - writer.WriteStartElement("Localized"); - foreach (var language in context.Languages) - { - content(language); - } - writer.WriteEndElement(); - } - }; - - protected virtual void WriteCategories(XmlWriter writer, int parentCategoryId) - { - var categories = _categoryService.GetAllCategoriesByParentCategoryId(parentCategoryId, true); - if (categories != null && categories.Count > 0) - { - foreach (var category in categories) - { - writer.WriteStartElement("Category"); - writer.Write("Id", category.Id.ToString()); - writer.Write("Name", category.Name); - writer.Write("FullName", category.FullName); - writer.Write("Description", category.Description); - writer.Write("BottomDescription", category.BottomDescription); - writer.Write("CategoryTemplateId", category.CategoryTemplateId.ToString()); - writer.Write("MetaKeywords", category.MetaKeywords); - writer.Write("MetaDescription", category.MetaDescription); - writer.Write("MetaTitle", category.MetaTitle); - writer.Write("SeName", category.GetSeName(0, true, false)); - writer.Write("ParentCategoryId", category.ParentCategoryId.ToString()); - writer.Write("PageSize", category.PageSize.ToString()); - writer.Write("AllowCustomersToSelectPageSize", category.AllowCustomersToSelectPageSize.ToString()); - writer.Write("PageSizeOptions", category.PageSizeOptions); - writer.Write("PriceRanges", category.PriceRanges); - writer.Write("ShowOnHomePage", category.ShowOnHomePage.ToString()); - writer.Write("HasDiscountsApplied", category.HasDiscountsApplied.ToString()); - writer.Write("Published", category.Published.ToString()); - writer.Write("Deleted", category.Deleted.ToString()); - writer.Write("DisplayOrder", category.DisplayOrder.ToString()); - writer.Write("CreatedOnUtc", category.CreatedOnUtc.ToString()); - writer.Write("UpdatedOnUtc", category.UpdatedOnUtc.ToString()); - writer.Write("SubjectToAcl", category.SubjectToAcl.ToString()); - writer.Write("LimitedToStores", category.LimitedToStores.ToString()); - writer.Write("Alias", category.Alias); - writer.Write("DefaultViewMode", category.DefaultViewMode); - - writer.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) - { - writer.WriteStartElement("ProductCategory"); - writer.Write("ProductCategoryId", productCategory.Id.ToString()); - writer.Write("ProductId", productCategory.ProductId.ToString()); - writer.Write("IsFeaturedProduct", productCategory.IsFeaturedProduct.ToString()); - writer.Write("DisplayOrder", productCategory.DisplayOrder.ToString()); - writer.WriteEndElement(); - } - } - writer.WriteEndElement(); - - writer.WriteStartElement("SubCategories"); - WriteCategories(writer, category.Id); - writer.WriteEndElement(); - writer.WriteEndElement(); - } - } - } - - protected virtual void WritePicture(XmlWriter writer, XmlExportContext context, Picture picture, int thumbSize, int defaultSize) - { - if (picture != null) - { - writer.WriteStartElement("Picture"); - writer.Write("Id", picture.Id.ToString()); - writer.Write("SeoFileName", picture.SeoFilename); - writer.Write("MimeType", picture.MimeType); - writer.Write("ThumbImageUrl", _pictureService.GetPictureUrl(picture, thumbSize, false, context.Store.Url)); - writer.Write("ImageUrl", _pictureService.GetPictureUrl(picture, defaultSize, false, context.Store.Url)); - writer.Write("FullSizeImageUrl", _pictureService.GetPictureUrl(picture, 0, false, context.Store.Url)); - writer.WriteEndElement(); - } - } - - protected virtual void WriteQuantityUnit(XmlWriter writer, XmlExportContext context, QuantityUnit quantityUnit) - { - if (quantityUnit != null) - { - writer.WriteStartElement("QuantityUnit"); - writer.Write("Id", quantityUnit.Id.ToString()); - writer.Write("Name", quantityUnit.Name); - writer.Write("Description", quantityUnit.Description); - writer.Write("DisplayLocale", quantityUnit.DisplayLocale); - writer.Write("DisplayOrder", quantityUnit.DisplayOrder.ToString()); - writer.Write("IsDefault", quantityUnit.IsDefault.ToString()); - WriteLocalized(writer, context, lang => - { - writer.Write("Name", quantityUnit.GetLocalized(x => x.Name, lang.Id, false, false), lang); - writer.Write("Description", quantityUnit.GetLocalized(x => x.Description, lang.Id, false, false), lang); - }); - writer.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("Id", null, manufacturer.Id.ToString()); - xmlWriter.WriteElementString("Name", null, manufacturer.Name); - xmlWriter.WriteElementString("SeName", null, manufacturer.GetSeName(0, true, false)); - 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("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("Id", 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(); - } - - /// - /// Writes a single product - /// - /// The XML writer - /// The product - /// Context objects - public virtual void WriteProductToXml(XmlWriter writer, Product product, XmlExportContext context) - { - var culture = CultureInfo.InvariantCulture; - var productTemplate = context.ProductTemplates.FirstOrDefault(x => x.Id == product.ProductTemplateId); - - writer.Write("Id", product.Id.ToString()); - writer.Write("Name", product.Name); - writer.Write("SeName", product.GetSeName(0, true, false)); - - writer.Write("ShortDescription", product.ShortDescription, null, true); - writer.Write("FullDescription", product.FullDescription, null, true); - - writer.Write("AdminComment", product.AdminComment); - writer.Write("ProductTemplateId", product.ProductTemplateId.ToString()); - writer.Write("ProductTemplateViewPath", productTemplate == null ? "" : productTemplate.ViewPath); - writer.Write("ShowOnHomePage", product.ShowOnHomePage.ToString()); - writer.Write("MetaKeywords", product.MetaKeywords); - writer.Write("MetaDescription", product.MetaDescription); - writer.Write("MetaTitle", product.MetaTitle); - writer.Write("AllowCustomerReviews", product.AllowCustomerReviews.ToString()); - writer.Write("ApprovedRatingSum", product.ApprovedRatingSum.ToString()); - writer.Write("NotApprovedRatingSum", product.NotApprovedRatingSum.ToString()); - writer.Write("ApprovedTotalReviews", product.ApprovedTotalReviews.ToString()); - writer.Write("NotApprovedTotalReviews", product.NotApprovedTotalReviews.ToString()); - writer.Write("Published", product.Published.ToString()); - writer.Write("CreatedOnUtc", product.CreatedOnUtc.ToString(culture)); - writer.Write("UpdatedOnUtc", product.UpdatedOnUtc.ToString(culture)); - writer.Write("SubjectToAcl", product.SubjectToAcl.ToString()); - writer.Write("LimitedToStores", product.LimitedToStores.ToString()); - writer.Write("ProductTypeId", product.ProductTypeId.ToString()); - writer.Write("ParentGroupedProductId", product.ParentGroupedProductId.ToString()); - writer.Write("Sku", product.Sku); - writer.Write("ManufacturerPartNumber", product.ManufacturerPartNumber); - writer.Write("Gtin", product.Gtin); - writer.Write("IsGiftCard", product.IsGiftCard.ToString()); - writer.Write("GiftCardTypeId", product.GiftCardTypeId.ToString()); - writer.Write("RequireOtherProducts", product.RequireOtherProducts.ToString()); - writer.Write("RequiredProductIds", product.RequiredProductIds); - writer.Write("AutomaticallyAddRequiredProducts", product.AutomaticallyAddRequiredProducts.ToString()); - writer.Write("IsDownload", product.IsDownload.ToString()); - writer.Write("DownloadId", product.DownloadId.ToString()); - writer.Write("UnlimitedDownloads", product.UnlimitedDownloads.ToString()); - writer.Write("MaxNumberOfDownloads", product.MaxNumberOfDownloads.ToString()); - writer.Write("DownloadExpirationDays", product.DownloadExpirationDays.HasValue ? product.DownloadExpirationDays.ToString() : ""); - writer.Write("DownloadActivationType", product.DownloadActivationType.ToString()); - writer.Write("HasSampleDownload", product.HasSampleDownload.ToString()); - writer.Write("SampleDownloadId", product.SampleDownloadId.ToString()); - writer.Write("HasUserAgreement", product.HasUserAgreement.ToString()); - writer.Write("UserAgreementText", product.UserAgreementText); - writer.Write("IsRecurring", product.IsRecurring.ToString()); - writer.Write("RecurringCycleLength", product.RecurringCycleLength.ToString()); - writer.Write("RecurringCyclePeriodId", product.RecurringCyclePeriodId.ToString()); - writer.Write("RecurringTotalCycles", product.RecurringTotalCycles.ToString()); - writer.Write("IsShipEnabled", product.IsShipEnabled.ToString()); - writer.Write("IsFreeShipping", product.IsFreeShipping.ToString()); - writer.Write("AdditionalShippingCharge", product.AdditionalShippingCharge.ToString(culture)); - writer.Write("IsTaxExempt", product.IsTaxExempt.ToString()); - writer.Write("TaxCategoryId", product.TaxCategoryId.ToString()); - writer.Write("ManageInventoryMethodId", product.ManageInventoryMethodId.ToString()); - writer.Write("StockQuantity", product.StockQuantity.ToString()); - writer.Write("DisplayStockAvailability", product.DisplayStockAvailability.ToString()); - writer.Write("DisplayStockQuantity", product.DisplayStockQuantity.ToString()); - writer.Write("MinStockQuantity", product.MinStockQuantity.ToString()); - writer.Write("LowStockActivityId", product.LowStockActivityId.ToString()); - writer.Write("NotifyAdminForQuantityBelow", product.NotifyAdminForQuantityBelow.ToString()); - writer.Write("BackorderModeId", product.BackorderModeId.ToString()); - writer.Write("AllowBackInStockSubscriptions", product.AllowBackInStockSubscriptions.ToString()); - writer.Write("OrderMinimumQuantity", product.OrderMinimumQuantity.ToString()); - writer.Write("OrderMaximumQuantity", product.OrderMaximumQuantity.ToString()); - writer.Write("AllowedQuantities", product.AllowedQuantities); - writer.Write("DisableBuyButton", product.DisableBuyButton.ToString()); - writer.Write("DisableWishlistButton", product.DisableWishlistButton.ToString()); - writer.Write("AvailableForPreOrder", product.AvailableForPreOrder.ToString()); - writer.Write("CallForPrice", product.CallForPrice.ToString()); - writer.Write("Price", product.Price.ToString(culture)); - writer.Write("OldPrice", product.OldPrice.ToString(culture)); - writer.Write("ProductCost", product.ProductCost.ToString(culture)); - writer.Write("SpecialPrice", product.SpecialPrice.HasValue ? product.SpecialPrice.Value.ToString(culture) : ""); - writer.Write("SpecialPriceStartDateTimeUtc", product.SpecialPriceStartDateTimeUtc.HasValue ? product.SpecialPriceStartDateTimeUtc.Value.ToString(culture) : ""); - writer.Write("SpecialPriceEndDateTimeUtc", product.SpecialPriceEndDateTimeUtc.HasValue ? product.SpecialPriceEndDateTimeUtc.Value.ToString(culture) : ""); - writer.Write("CustomerEntersPrice", product.CustomerEntersPrice.ToString()); - writer.Write("MinimumCustomerEnteredPrice", product.MinimumCustomerEnteredPrice.ToString(culture)); - writer.Write("MaximumCustomerEnteredPrice", product.MaximumCustomerEnteredPrice.ToString(culture)); - writer.Write("HasTierPrices", product.HasTierPrices.ToString()); - writer.Write("HasDiscountsApplied", product.HasDiscountsApplied.ToString()); - writer.Write("Weight", product.Weight.ToString(culture)); - writer.Write("Length", product.Length.ToString(culture)); - writer.Write("Width", product.Width.ToString(culture)); - writer.Write("Height", product.Height.ToString(culture)); - writer.Write("AvailableStartDateTimeUtc", product.AvailableStartDateTimeUtc.HasValue ? product.AvailableStartDateTimeUtc.Value.ToString(culture) : ""); - writer.Write("AvailableEndDateTimeUtc", product.AvailableEndDateTimeUtc.HasValue ? product.AvailableEndDateTimeUtc.Value.ToString(culture) : ""); - writer.Write("BasePriceEnabled", product.BasePriceEnabled.ToString()); - writer.Write("BasePriceMeasureUnit", product.BasePriceMeasureUnit); - writer.Write("BasePriceAmount", product.BasePriceAmount.HasValue ? product.BasePriceAmount.Value.ToString(culture) : ""); - writer.Write("BasePriceBaseAmount", product.BasePriceBaseAmount.HasValue ? product.BasePriceBaseAmount.Value.ToString() : ""); - writer.Write("VisibleIndividually", product.VisibleIndividually.ToString()); - writer.Write("DisplayOrder", product.DisplayOrder.ToString()); - writer.Write("BundleTitleText", product.BundleTitleText); - writer.Write("BundlePerItemPricing", product.BundlePerItemPricing.ToString()); - writer.Write("BundlePerItemShipping", product.BundlePerItemShipping.ToString()); - writer.Write("BundlePerItemShoppingCart", product.BundlePerItemShoppingCart.ToString()); - writer.Write("LowestAttributeCombinationPrice", product.LowestAttributeCombinationPrice.HasValue ? product.LowestAttributeCombinationPrice.Value.ToString(culture) : ""); - writer.Write("IsEsd", product.IsEsd.ToString()); - - WriteLocalized(writer, context, lang => - { - writer.Write("Name", product.GetLocalized(x => x.Name, lang.Id, false, false), lang); - writer.Write("SeName", product.GetSeName(lang.Id, false, false), lang); - writer.Write("ShortDescription", product.GetLocalized(x => x.ShortDescription, lang.Id, false, false), lang, true); - writer.Write("FullDescription", product.GetLocalized(x => x.FullDescription, lang.Id, false, false), lang, true); - writer.Write("MetaKeywords", product.GetLocalized(x => x.MetaKeywords, lang.Id, false, false), lang); - writer.Write("MetaDescription", product.GetLocalized(x => x.MetaDescription, lang.Id, false, false), lang); - writer.Write("MetaTitle", product.GetLocalized(x => x.MetaTitle, lang.Id, false, false), lang); - writer.Write("BundleTitleText", product.GetLocalized(x => x.BundleTitleText, lang.Id, false, false), lang); - }); - - if (product.DeliveryTime != null) - { - writer.WriteStartElement("DeliveryTime"); - writer.Write("Id", product.DeliveryTime.Id.ToString()); - writer.Write("Name", product.DeliveryTime.Name); - writer.Write("DisplayLocale", product.DeliveryTime.DisplayLocale); - writer.Write("ColorHexValue", product.DeliveryTime.ColorHexValue); - writer.Write("DisplayOrder", product.DeliveryTime.DisplayOrder.ToString()); - WriteLocalized(writer, context, lang => - { - writer.Write("Name", product.DeliveryTime.GetLocalized(x => x.Name, lang.Id, false, false), lang); - }); - writer.WriteEndElement(); - } - - WriteQuantityUnit(writer, context, product.QuantityUnit); - - writer.WriteStartElement("ProductTags"); - foreach (var tag in product.ProductTags) - { - writer.WriteStartElement("ProductTag"); - writer.Write("Id", tag.Id.ToString()); - writer.Write("Name", tag.Name); - - WriteLocalized(writer, context, lang => - { - writer.Write("Name", tag.GetLocalized(x => x.Name, lang.Id, false, false), lang); - }); - - writer.WriteEndElement(); - } - writer.WriteEndElement(); - - writer.WriteStartElement("ProductDiscounts"); - foreach (var discount in product.AppliedDiscounts) - { - writer.WriteStartElement("ProductDiscount"); - writer.Write("DiscountId", discount.Id.ToString()); - writer.WriteEndElement(); - } - writer.WriteEndElement(); - - writer.WriteStartElement("TierPrices"); - foreach (var tierPrice in product.TierPrices) - { - writer.WriteStartElement("TierPrice"); - writer.Write("Id", tierPrice.Id.ToString()); - writer.Write("StoreId", tierPrice.StoreId.ToString()); - writer.Write("CustomerRoleId", tierPrice.CustomerRoleId.HasValue ? tierPrice.CustomerRoleId.ToString() : "0"); - writer.Write("Quantity", tierPrice.Quantity.ToString()); - writer.Write("Price", tierPrice.Price.ToString(culture)); - writer.WriteEndElement(); - } - writer.WriteEndElement(); - - writer.WriteStartElement("ProductAttributes"); - foreach (var pva in product.ProductVariantAttributes.OrderBy(x => x.DisplayOrder)) - { - writer.WriteStartElement("ProductAttribute"); - - writer.Write("Id", pva.Id.ToString()); - writer.Write("TextPrompt", pva.TextPrompt); - writer.Write("IsRequired", pva.IsRequired.ToString()); - writer.Write("AttributeControlTypeId", pva.AttributeControlTypeId.ToString()); - writer.Write("DisplayOrder", pva.DisplayOrder.ToString()); - - writer.WriteStartElement("Attribute"); - writer.Write("Id", pva.ProductAttribute.Id.ToString()); - writer.Write("Alias", pva.ProductAttribute.Alias); - writer.Write("Name", pva.ProductAttribute.Name); - writer.Write("Description", pva.ProductAttribute.Description); - WriteLocalized(writer, context, lang => - { - writer.Write("Name", pva.ProductAttribute.GetLocalized(x => x.Name, lang.Id, false, false), lang); - writer.Write("Description", pva.ProductAttribute.GetLocalized(x => x.Description, lang.Id, false, false), lang); - }); - writer.WriteEndElement(); // Attribute - - writer.WriteStartElement("AttributeValues"); - foreach (var value in pva.ProductVariantAttributeValues.OrderBy(x => x.DisplayOrder)) - { - writer.WriteStartElement("AttributeValue"); - writer.Write("Id", value.Id.ToString()); - writer.Write("Alias", value.Alias); - writer.Write("Name", value.Name); - writer.Write("ColorSquaresRgb", value.ColorSquaresRgb); - writer.Write("PriceAdjustment", value.PriceAdjustment.ToString(culture)); - writer.Write("WeightAdjustment", value.WeightAdjustment.ToString(culture)); - writer.Write("IsPreSelected", value.IsPreSelected.ToString()); - writer.Write("DisplayOrder", value.DisplayOrder.ToString()); - writer.Write("ValueTypeId", value.ValueTypeId.ToString()); - writer.Write("LinkedProductId", value.LinkedProductId.ToString()); - writer.Write("Quantity", value.Quantity.ToString()); - WriteLocalized(writer, context, lang => - { - writer.Write("Name", value.GetLocalized(x => x.Name, lang.Id, false, false), lang); - }); - writer.WriteEndElement(); // AttributeValue - } - writer.WriteEndElement(); // AttributeValues - - writer.WriteEndElement(); // ProductAttribute - } - writer.WriteEndElement(); // ProductAttributes - - writer.WriteStartElement("ProductAttributeCombinations"); - foreach (var combination in product.ProductVariantAttributeCombinations) - { - writer.WriteStartElement("ProductAttributeCombination"); - - writer.Write("Id", combination.Id.ToString()); - writer.Write("StockQuantity", combination.StockQuantity.ToString()); - writer.Write("AllowOutOfStockOrders", combination.AllowOutOfStockOrders.ToString()); - writer.Write("AttributesXml", combination.AttributesXml, null, true); - writer.Write("Sku", combination.Sku); - writer.Write("Gtin", combination.Gtin); - writer.Write("ManufacturerPartNumber", combination.ManufacturerPartNumber); - writer.Write("Price", combination.Price.HasValue ? combination.Price.Value.ToString(culture) : ""); - writer.Write("Length", combination.Length.HasValue ? combination.Length.Value.ToString(culture) : ""); - writer.Write("Width", combination.Width.HasValue ? combination.Width.Value.ToString(culture) : ""); - writer.Write("Height", combination.Height.HasValue ? combination.Height.Value.ToString(culture) : ""); - writer.Write("BasePriceAmount", combination.BasePriceAmount.HasValue ? combination.BasePriceAmount.Value.ToString(culture) : ""); - writer.Write("BasePriceBaseAmount", combination.BasePriceBaseAmount.HasValue ? combination.BasePriceBaseAmount.Value.ToString() : ""); - writer.Write("DeliveryTimeId", combination.DeliveryTimeId.HasValue ? combination.DeliveryTimeId.Value.ToString() : ""); - writer.Write("IsActive", combination.IsActive.ToString()); - - WriteQuantityUnit(writer, context, combination.QuantityUnit); - - writer.WriteStartElement("Pictures"); - foreach (int pictureId in combination.GetAssignedPictureIds()) - { - WritePicture(writer, context, _pictureService.GetPictureById(pictureId), _mediaSettings.ProductThumbPictureSize, _mediaSettings.ProductDetailsPictureSize); - } - writer.WriteEndElement(); // Pictures - - writer.WriteEndElement(); // ProductAttributeCombination - } - writer.WriteEndElement(); // ProductAttributeCombinations - - writer.WriteStartElement("ProductPictures"); - foreach (var productPicture in product.ProductPictures.OrderBy(x => x.DisplayOrder)) - { - writer.WriteStartElement("ProductPicture"); - writer.Write("Id", productPicture.Id.ToString()); - writer.Write("DisplayOrder", productPicture.DisplayOrder.ToString()); - - WritePicture(writer, context, productPicture.Picture, _mediaSettings.ProductThumbPictureSize, _mediaSettings.ProductDetailsPictureSize); - - writer.WriteEndElement(); - } - writer.WriteEndElement(); - - writer.WriteStartElement("ProductCategories"); - var productCategories = _categoryService.GetProductCategoriesByProductId(product.Id); - if (productCategories != null) - { - foreach (var productCategory in productCategories.OrderBy(x => x.DisplayOrder)) - { - var category = productCategory.Category; - writer.WriteStartElement("ProductCategory"); - writer.Write("IsFeaturedProduct", productCategory.IsFeaturedProduct.ToString()); - writer.Write("DisplayOrder", productCategory.DisplayOrder.ToString()); - - writer.WriteStartElement("Category"); - writer.Write("Id", category.Id.ToString()); - writer.Write("Name", category.Name); - writer.Write("FullName", category.FullName); - writer.Write("Description", category.Description); - writer.Write("BottomDescription", category.BottomDescription); - writer.Write("CategoryTemplateId", category.CategoryTemplateId.ToString()); - writer.Write("MetaKeywords", category.MetaKeywords); - writer.Write("MetaDescription", category.MetaDescription); - writer.Write("MetaTitle", category.MetaTitle); - writer.Write("SeName", category.GetSeName(0)); - writer.Write("ParentCategoryId", category.ParentCategoryId.ToString()); - writer.Write("PageSize", category.PageSize.ToString()); - writer.Write("AllowCustomersToSelectPageSize", category.AllowCustomersToSelectPageSize.ToString()); - writer.Write("PageSizeOptions", category.PageSizeOptions); - writer.Write("PriceRanges", category.PriceRanges); - writer.Write("ShowOnHomePage", category.ShowOnHomePage.ToString()); - writer.Write("HasDiscountsApplied", category.HasDiscountsApplied.ToString()); - writer.Write("Published", category.Published.ToString()); - writer.Write("Deleted", category.Deleted.ToString()); - writer.Write("DisplayOrder", category.DisplayOrder.ToString()); - writer.Write("CreatedOnUtc", category.CreatedOnUtc.ToString(culture)); - writer.Write("UpdatedOnUtc", category.UpdatedOnUtc.ToString(culture)); - writer.Write("SubjectToAcl", category.SubjectToAcl.ToString()); - writer.Write("LimitedToStores", category.LimitedToStores.ToString()); - writer.Write("Alias", category.Alias); - writer.Write("DefaultViewMode", category.DefaultViewMode); - - WritePicture(writer, context, category.Picture, _mediaSettings.CategoryThumbPictureSize, _mediaSettings.CategoryThumbPictureSize); - - WriteLocalized(writer, context, lang => - { - writer.Write("Name", category.GetLocalized(x => x.Name, lang.Id, false, false), lang); - writer.Write("FullName", category.GetLocalized(x => x.FullName, lang.Id, false, false), lang); - writer.Write("Description", category.GetLocalized(x => x.Description, lang.Id, false, false), lang); - writer.Write("BottomDescription", category.GetLocalized(x => x.BottomDescription, lang.Id, false, false), lang); - writer.Write("MetaKeywords", category.GetLocalized(x => x.MetaKeywords, lang.Id, false, false), lang); - writer.Write("MetaDescription", category.GetLocalized(x => x.MetaDescription, lang.Id, false, false), lang); - writer.Write("MetaTitle", category.GetLocalized(x => x.MetaTitle, lang.Id, false, false), lang); - writer.Write("SeName", category.GetSeName(lang.Id, false, false)); - }); - - writer.WriteEndElement(); - - writer.WriteEndElement(); - } - } - writer.WriteEndElement(); - - writer.WriteStartElement("ProductManufacturers"); - var productManufacturers = _manufacturerService.GetProductManufacturersByProductId(product.Id); - if (productManufacturers != null) - { - foreach (var productManufacturer in productManufacturers.OrderBy(x => x.DisplayOrder)) - { - var manu = productManufacturer.Manufacturer; - writer.WriteStartElement("ProductManufacturer"); - - writer.Write("ProductManufacturerId", productManufacturer.Id.ToString()); - writer.Write("IsFeaturedProduct", productManufacturer.IsFeaturedProduct.ToString()); - writer.Write("DisplayOrder", productManufacturer.DisplayOrder.ToString()); - - writer.WriteStartElement("Manufacturer"); - writer.Write("Id", manu.Id.ToString()); - writer.Write("Name", manu.Name); - writer.Write("SeName", manu.GetSeName(0, true, false)); - writer.Write("Description", manu.Description); - writer.Write("MetaKeywords", manu.MetaKeywords); - writer.Write("MetaDescription", manu.MetaDescription); - writer.Write("MetaTitle", manu.MetaTitle); - - WritePicture(writer, context, manu.Picture, _mediaSettings.ManufacturerThumbPictureSize, _mediaSettings.ManufacturerThumbPictureSize); - - WriteLocalized(writer, context, lang => - { - writer.Write("Name", manu.GetLocalized(x => x.Name, lang.Id, false, false), lang); - writer.Write("SeName", manu.GetSeName(lang.Id, false, false), lang); - writer.Write("Description", manu.GetLocalized(x => x.Description, lang.Id, false, false), lang); - writer.Write("MetaKeywords", manu.GetLocalized(x => x.MetaKeywords, lang.Id, false, false), lang); - writer.Write("MetaDescription", manu.GetLocalized(x => x.MetaDescription, lang.Id, false, false), lang); - writer.Write("MetaTitle", manu.GetLocalized(x => x.MetaTitle, lang.Id, false, false), lang); - }); - - writer.WriteEndElement(); - - writer.WriteEndElement(); - } - } - writer.WriteEndElement(); - - writer.WriteStartElement("ProductSpecificationAttributes"); - foreach (var pca in product.ProductSpecificationAttributes.OrderBy(x => x.DisplayOrder)) - { - writer.WriteStartElement("ProductSpecificationAttribute"); - writer.Write("Id", pca.Id.ToString()); - writer.Write("AllowFiltering", pca.AllowFiltering.ToString()); - writer.Write("ShowOnProductPage", pca.ShowOnProductPage.ToString()); - writer.Write("DisplayOrder", pca.DisplayOrder.ToString()); - - writer.WriteStartElement("SpecificationAttributeOption"); - writer.Write("Id", pca.SpecificationAttributeOption.Id.ToString()); - writer.Write("DisplayOrder", pca.SpecificationAttributeOption.DisplayOrder.ToString()); - writer.Write("Name", pca.SpecificationAttributeOption.Name); - WriteLocalized(writer, context, lang => - { - writer.Write("Name", pca.SpecificationAttributeOption.GetLocalized(x => x.Name, lang.Id, false, false), lang); - }); - - writer.WriteStartElement("SpecificationAttribute"); - writer.Write("Id", pca.SpecificationAttributeOption.SpecificationAttribute.Id.ToString()); - writer.Write("DisplayOrder", pca.SpecificationAttributeOption.SpecificationAttribute.DisplayOrder.ToString()); - writer.Write("Name", pca.SpecificationAttributeOption.SpecificationAttribute.Name); - WriteLocalized(writer, context, lang => - { - writer.Write("Name", pca.SpecificationAttributeOption.SpecificationAttribute.GetLocalized(x => x.Name, lang.Id, false, false), lang); - }); - writer.WriteEndElement(); // SpecificationAttribute - - writer.WriteEndElement(); // SpecificationAttributeOption - - writer.WriteEndElement(); // ProductSpecificationAttribute - } - writer.WriteEndElement(); - - writer.WriteStartElement("ProductBundleItems"); - var bundleItems = _productService.GetBundleItems(product.Id, true); - foreach (var bundleItem in bundleItems.Select(x => x.Item).OrderBy(x => x.DisplayOrder)) - { - writer.WriteStartElement("ProductBundleItem"); - writer.Write("ProductId", bundleItem.ProductId.ToString()); - writer.Write("BundleProductId", bundleItem.BundleProductId.ToString()); - writer.Write("Quantity", bundleItem.Quantity.ToString()); - writer.Write("Discount", bundleItem.Discount.HasValue ? bundleItem.Discount.Value.ToString(culture) : ""); - writer.Write("DiscountPercentage", bundleItem.DiscountPercentage.ToString()); - writer.Write("Name", bundleItem.GetLocalizedName()); - writer.Write("ShortDescription", bundleItem.ShortDescription); - writer.Write("FilterAttributes", bundleItem.FilterAttributes.ToString()); - writer.Write("HideThumbnail", bundleItem.HideThumbnail.ToString()); - writer.Write("Visible", bundleItem.Visible.ToString()); - writer.Write("Published", bundleItem.Published.ToString()); - writer.Write("DisplayOrder", bundleItem.DisplayOrder.ToString()); - writer.Write("CreatedOnUtc", bundleItem.CreatedOnUtc.ToString(culture)); - writer.Write("UpdatedOnUtc", bundleItem.UpdatedOnUtc.ToString(culture)); - writer.WriteEndElement(); - } - writer.WriteEndElement(); - } - - /// - /// Export product list to XML - /// - /// Stream to write - /// Search context - public virtual void ExportProductsToXml(Stream stream, ProductSearchContext searchContext) - { - var settings = new XmlWriterSettings() - { - Encoding = new UTF8Encoding(false), - CheckCharacters = false - }; - - var context = new XmlExportContext() - { - ProductTemplates = _productTemplateService.GetAllProductTemplates(), - Languages = _languageService.GetAllLanguages(true), - Store = _commonServices.StoreContext.CurrentStore - }; - - using (var writer = XmlWriter.Create(stream, settings)) - { - writer.WriteStartDocument(); - writer.WriteStartElement("Products"); - writer.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); - - for (int i = 0; i < 9999999; ++i) - { - searchContext.PageIndex = i; - - var products = _productService.SearchProducts(searchContext); - - foreach (var product in products) - { - writer.WriteStartElement("Product"); - - try - { - WriteProductToXml(writer, product, context); - } - catch (Exception exc) - { - Logger.Error("{0} (Product.Id {1})".FormatWith(exc.Message, product.Id), exc); - } - - writer.WriteEndElement(); // Product - } - - if (!products.HasNextPage) - break; - } - - writer.WriteEndElement(); - writer.WriteEndDocument(); - writer.Flush(); - writer.Close(); - - stream.Seek(0, SeekOrigin.Begin); - } - } - - /// - /// 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", - "AvailableStartDateTimeUtc", - "AvailableEndDateTimeUtc" - }; - - //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++; - - cells[row, col].Value = p.AvailableStartDateTimeUtc; - col++; - - cells[row, col].Value = p.AvailableEndDateTimeUtc; - 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 had serious memory leak problems in V3. - // 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); - - 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); - - 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() : ""); - xmlWriter.WriteElementString("HasNewPaymentNotification", null, order.HasNewPaymentNotification.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", - "HasNewPaymentNotification", - //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++; - - worksheet.Cells[row, col].Value = order.HasNewPaymentNotification; - 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[] - { - "Id", - "CustomerGuid", - "Email", - "Username", - "PasswordStr",//why can't we use 'Password' name? - "PasswordFormatId", - "PasswordSalt", - "AdminComment", - "IsTaxExempt", - "AffiliateId", - "Active", - "IsSystemAccount", - "SystemName", - "LastIpAddress", - "CreatedOnUtc", - "LastLoginDateUtc", - "LastActivityDateUtc", - - "IsGuest", - "IsRegistered", - "IsAdministrator", - "IsForumModerator", - "FirstName", - "LastName", - "Gender", - "Company", - "StreetAddress", - "StreetAddress2", - "ZipPostalCode", - "City", - "CountryId", - "StateProvinceId", - "Phone", - "Fax", - "VatNumber", - "VatNumberStatusId", - "TimeZoneId", - "Newsletter", - "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.AdminComment; - 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++; - - worksheet.Cells[row, col].Value = customer.IsSystemAccount; - col++; - - worksheet.Cells[row, col].Value = customer.SystemName; - col++; - - worksheet.Cells[row, col].Value = customer.LastIpAddress; - col++; - - worksheet.Cells[row, col].Value = customer.CreatedOnUtc.ToString(); - col++; - - worksheet.Cells[row, col].Value = (customer.LastLoginDateUtc.HasValue ? customer.LastLoginDateUtc.Value.ToString() : null); - col++; - - worksheet.Cells[row, col].Value = customer.LastActivityDateUtc.ToString(); - 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 newsletter = _newsLetterSubscriptionService.GetNewsLetterSubscriptionByEmail(customer.Email); - bool subscribedToNewsletters = newsletter != null && newsletter.Active; - - 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 = timeZoneId; - col++; - - worksheet.Cells[row, col].Value = subscribedToNewsletters; - col++; - - worksheet.Cells[row, col].Value = avatarPictureId; - 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("Id", 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("AdminComment", null, customer.AdminComment); - xmlWriter.WriteElementString("IsTaxExempt", null, customer.IsTaxExempt.ToString()); - xmlWriter.WriteElementString("AffiliateId", null, customer.AffiliateId.ToString()); - xmlWriter.WriteElementString("Active", null, customer.Active.ToString()); - xmlWriter.WriteElementString("IsSystemAccount", null, customer.IsSystemAccount.ToString()); - xmlWriter.WriteElementString("SystemName", null, customer.SystemName); - xmlWriter.WriteElementString("LastIpAddress", null, customer.LastIpAddress); - xmlWriter.WriteElementString("CreatedOnUtc", null, customer.CreatedOnUtc.ToString()); - xmlWriter.WriteElementString("LastLoginDateUtc", null, customer.LastLoginDateUtc.HasValue ? customer.LastLoginDateUtc.Value.ToString() : ""); - xmlWriter.WriteElementString("LastActivityDateUtc", null, customer.LastActivityDateUtc.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.WriteStartElement("Addresses"); - - foreach (var address in customer.Addresses) - { - bool isCurrentBillingAddress = (customer.BillingAddress != null && customer.BillingAddress.Id == address.Id); - bool isCurrentShippingAddress = (customer.ShippingAddress != null && customer.ShippingAddress.Id == address.Id); - - xmlWriter.WriteStartElement("Address"); - xmlWriter.WriteElementString("IsCurrentBillingAddress", null, isCurrentBillingAddress.ToString()); - xmlWriter.WriteElementString("IsCurrentShippingAddress", null, isCurrentShippingAddress.ToString()); - - xmlWriter.WriteElementString("Id", null, address.Id.ToString()); - xmlWriter.WriteElementString("FirstName", null, address.FirstName); - xmlWriter.WriteElementString("LastName", null, address.LastName); - xmlWriter.WriteElementString("Email", null, address.Email); - xmlWriter.WriteElementString("Company", null, address.Company); - xmlWriter.WriteElementString("City", null, address.City); - xmlWriter.WriteElementString("Address1", null, address.Address1); - xmlWriter.WriteElementString("Address2", null, address.Address2); - xmlWriter.WriteElementString("ZipPostalCode", null, address.ZipPostalCode); - xmlWriter.WriteElementString("PhoneNumber", null, address.PhoneNumber); - xmlWriter.WriteElementString("FaxNumber", null, address.FaxNumber); - xmlWriter.WriteElementString("CreatedOnUtc", null, address.CreatedOnUtc.ToString()); - - if (address.Country != null) - { - xmlWriter.WriteStartElement("Country"); - xmlWriter.WriteElementString("Id", null, address.Country.Id.ToString()); - xmlWriter.WriteElementString("Name", null, address.Country.Name); - xmlWriter.WriteElementString("AllowsBilling", null, address.Country.AllowsBilling.ToString()); - xmlWriter.WriteElementString("AllowsShipping", null, address.Country.AllowsShipping.ToString()); - xmlWriter.WriteElementString("TwoLetterIsoCode", null, address.Country.TwoLetterIsoCode); - xmlWriter.WriteElementString("ThreeLetterIsoCode", null, address.Country.ThreeLetterIsoCode); - xmlWriter.WriteElementString("NumericIsoCode", null, address.Country.NumericIsoCode.ToString()); - xmlWriter.WriteElementString("SubjectToVat", null, address.Country.SubjectToVat.ToString()); - xmlWriter.WriteElementString("Published", null, address.Country.Published.ToString()); - xmlWriter.WriteElementString("DisplayOrder", null, address.Country.DisplayOrder.ToString()); - xmlWriter.WriteElementString("LimitedToStores", null, address.Country.LimitedToStores.ToString()); - xmlWriter.WriteEndElement(); // Country - } - - if (address.StateProvince != null) - { - xmlWriter.WriteStartElement("StateProvince"); - xmlWriter.WriteElementString("Id", null, address.StateProvince.Id.ToString()); - xmlWriter.WriteElementString("CountryId", null, address.StateProvince.CountryId.ToString()); - xmlWriter.WriteElementString("Name", null, address.StateProvince.Name); - xmlWriter.WriteElementString("Abbreviation", null, address.StateProvince.Abbreviation); - xmlWriter.WriteElementString("Published", null, address.StateProvince.Published.ToString()); - xmlWriter.WriteElementString("DisplayOrder", null, address.StateProvince.DisplayOrder.ToString()); - xmlWriter.WriteEndElement(); // StateProvince - } - - xmlWriter.WriteEndElement(); // Address - } - - xmlWriter.WriteEndElement(); // Addresses - - xmlWriter.WriteEndElement(); // Customer - } - - xmlWriter.WriteEndElement(); - xmlWriter.WriteEndDocument(); - xmlWriter.Close(); - return stringWriter.ToString(); - } - - #endregion - } - - - public class XmlExportContext - { - public IList ProductTemplates { get; set; } - public IList Languages { get; set; } - public Store Store { get; set; } - } -} diff --git a/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs b/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs deleted file mode 100644 index 0c8afdbcfd..0000000000 --- a/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using System.Xml; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Orders; -using SmartStore.Services.Catalog; - -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(); - - /// - /// Writes a single product - /// - /// The XML writer - /// The product - /// Context objects - void WriteProductToXml(XmlWriter writer, Product product, XmlExportContext context); - - /// - /// Export product list to XML - /// - /// Stream to write - /// Search context - void ExportProductsToXml(Stream stream, ProductSearchContext searchContext); - - /// - /// 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 fdefe47639..0000000000 --- a/src/Libraries/SmartStore.Services/ExportImport/IImportManager.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using SmartStore.Core.Data; - -namespace SmartStore.Services.ExportImport -{ - /// - /// Import manager interface - /// - public interface IImportManager - { - ImportResult ImportProductsFromExcel( - 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 ImportResult ImportProductsFromExcel(this IImportManager importManager, Stream stream) - { - return importManager.ImportProductsFromExcel(stream, CancellationToken.None); - } - } -} diff --git a/src/Libraries/SmartStore.Services/ExportImport/ImportManager.cs b/src/Libraries/SmartStore.Services/ExportImport/ImportManager.cs deleted file mode 100644 index cf489dd468..0000000000 --- a/src/Libraries/SmartStore.Services/ExportImport/ImportManager.cs +++ /dev/null @@ -1,760 +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 ImportResult ImportProductsFromExcel( - Stream stream, - CancellationToken cancellationToken, - IProgress progress = null) - { - Guard.ArgumentNotNull(() => stream); - - 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 = 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))) - { - try - { - _rsProduct.Context.AutoDetectChangesEnabled = true; - ProcessSlugs(batch, result); - } - catch (Exception ex) - { - result.AddError(ex, segmenter.CurrentSegment, "ProcessSeoSlugs"); - } - finally - { - _rsProduct.Context.AutoDetectChangesEnabled = false; - } - } - - // =========================================================================== - // 3.) Import Localizations - // =========================================================================== - try - { - 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 - { - 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 - { - 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; - } - - private int ProcessProducts(ICollection> batch, ImportResult result) - { - _rsProduct.AutoCommitEnabled = true; - - 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.AvailableStartDateTimeUtc, null, OADateToUtcDate); - row.SetProperty(result, product, (x) => x.AvailableEndDateTimeUtc, null, OADateToUtcDate); - - 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 num = _rsProduct.Context.SaveChanges(); - - // Perf: notify only about LAST insertion and update - if (lastInserted != null) - _eventPublisher.EntityInserted(lastInserted); - if (lastUpdated != null) - _eventPublisher.EntityUpdated(lastUpdated); - - return num; - } - - private int ProcessSlugs(ICollection> batch, ImportResult result) - { - 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 - return _rsUrlRecord.Context.SaveChanges(); - } - - private int 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 num = _rsProductManufacturer.Context.SaveChanges(); - - // Perf: notify only about LAST insertion and update - //if (lastInserted != null) - // _eventPublisher.EntityInserted(lastInserted); - - return num; - } - - - private int 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 num = _rsProductCategory.Context.SaveChanges(); - - // Perf: notify only about LAST insertion and update - if (lastInserted != null) - _eventPublisher.EntityInserted(lastInserted); - - return num; - } - - private int 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 num = _rsProductManufacturer.Context.SaveChanges(); - - // Perf: notify only about LAST insertion and update - if (lastInserted != null) - _eventPublisher.EntityInserted(lastInserted); - - return num; - } - - 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; - int equalPictureId = 0; - - 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.TableUntracked.Expand(x => x.Picture).Where(x => x.ProductId == row.Entity.Id).Select(x => x.Picture).ToList(); - var pictureBinary = _pictureService.FindEqualPicture(picture, currentPictures, out equalPictureId); - - 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; - } - } -} diff --git a/src/Libraries/SmartStore.Services/Extensions/NameValueCollectionExtensions.cs b/src/Libraries/SmartStore.Services/Extensions/NameValueCollectionExtensions.cs index 99ee1e5744..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,16 +174,18 @@ 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))); } @@ -163,10 +197,10 @@ public static string CreateSelectedAttributesXml(this NameValueCollection collec 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 4f5ca20ce4..5871f34ab6 100644 --- a/src/Libraries/SmartStore.Services/Forums/ForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/ForumService.cs @@ -41,11 +41,9 @@ 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 IStoreContext _storeContext; private readonly IRepository _storeMappingRepository; + private readonly ICommonServices _services; #endregion @@ -62,11 +60,9 @@ public ForumService(ICacheManager cacheManager, IRepository customerRepository, IGenericAttributeService genericAttributeService, ICustomerService customerService, - IWorkContext workContext, IWorkflowMessageService workflowMessageService, - IEventPublisher eventPublisher, - IStoreContext storeContext, - IRepository storeMappingRepository) + IRepository storeMappingRepository, + ICommonServices services) { _cacheManager = cacheManager; _forumGroupRepository = forumGroupRepository; @@ -79,11 +75,9 @@ public ForumService(ICacheManager cacheManager, _customerRepository = customerRepository; _genericAttributeService = genericAttributeService; _customerService = customerService; - _workContext = workContext; _workflowMessageService = workflowMessageService; - _eventPublisher = eventPublisher; - _storeContext = storeContext; _storeMappingRepository = storeMappingRepository; + _services = services; } public DbQuerySettings QuerySettings { get; set; } @@ -263,7 +257,7 @@ public virtual void DeleteForumGroup(ForumGroup forumGroup) _cacheManager.RemoveByPattern(FORUM_PATTERN_KEY); //event notification - _eventPublisher.EntityDeleted(forumGroup); + _services.EventPublisher.EntityDeleted(forumGroup); } /// @@ -292,7 +286,7 @@ public virtual IList GetAllForumGroups(bool showHidden = false) if (!showHidden && !QuerySettings.IgnoreMultiStore) { - var currentStoreId = _storeContext.CurrentStore.Id; + var currentStoreId = _services.StoreContext.CurrentStore.Id; query = from fg in query @@ -332,7 +326,7 @@ public virtual void InsertForumGroup(ForumGroup forumGroup) _cacheManager.RemoveByPattern(FORUM_PATTERN_KEY); //event notification - _eventPublisher.EntityInserted(forumGroup); + _services.EventPublisher.EntityInserted(forumGroup); } /// @@ -353,7 +347,7 @@ public virtual void UpdateForumGroup(ForumGroup forumGroup) _cacheManager.RemoveByPattern(FORUM_PATTERN_KEY); //event notification - _eventPublisher.EntityUpdated(forumGroup); + _services.EventPublisher.EntityUpdated(forumGroup); } /// @@ -378,7 +372,7 @@ where queryTopicIds.Contains(fs.TopicId) { _forumSubscriptionRepository.Delete(fs); //event notification - _eventPublisher.EntityDeleted(fs); + _services.EventPublisher.EntityDeleted(fs); } //delete forum subscriptions (forum) @@ -389,7 +383,7 @@ where queryTopicIds.Contains(fs.TopicId) { _forumSubscriptionRepository.Delete(fs2); //event notification - _eventPublisher.EntityDeleted(fs2); + _services.EventPublisher.EntityDeleted(fs2); } //delete forum @@ -399,7 +393,7 @@ where queryTopicIds.Contains(fs.TopicId) _cacheManager.RemoveByPattern(FORUM_PATTERN_KEY); //event notification - _eventPublisher.EntityDeleted(forum); + _services.EventPublisher.EntityDeleted(forum); } /// @@ -451,7 +445,7 @@ public virtual void InsertForum(Forum forum) _cacheManager.RemoveByPattern(FORUM_PATTERN_KEY); //event notification - _eventPublisher.EntityInserted(forum); + _services.EventPublisher.EntityInserted(forum); } /// @@ -471,7 +465,7 @@ public virtual void UpdateForum(Forum forum) _cacheManager.RemoveByPattern(FORUM_PATTERN_KEY); //event notification - _eventPublisher.EntityUpdated(forum); + _services.EventPublisher.EntityUpdated(forum); } /// @@ -500,7 +494,7 @@ public virtual void DeleteTopic(ForumTopic forumTopic) { _forumSubscriptionRepository.Delete(fs); //event notification - _eventPublisher.EntityDeleted(fs); + _services.EventPublisher.EntityDeleted(fs); } //update stats @@ -511,7 +505,7 @@ public virtual void DeleteTopic(ForumTopic forumTopic) _cacheManager.RemoveByPattern(FORUM_PATTERN_KEY); //event notification - _eventPublisher.EntityDeleted(forumTopic); + _services.EventPublisher.EntityDeleted(forumTopic); } /// @@ -613,7 +607,7 @@ from ft in _forumTopicRepository.Table if (!QuerySettings.IgnoreMultiStore) { - var currentStoreId = _storeContext.CurrentStore.Id; + var currentStoreId = _services.StoreContext.CurrentStore.Id; query = from ft in query @@ -659,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) { @@ -700,7 +694,7 @@ public virtual void UpdateTopic(ForumTopic forumTopic) _cacheManager.RemoveByPattern(FORUM_PATTERN_KEY); //event notification - _eventPublisher.EntityUpdated(forumTopic); + _services.EventPublisher.EntityUpdated(forumTopic); } /// @@ -715,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); @@ -783,7 +777,7 @@ public virtual void DeletePost(ForumPost forumPost) _cacheManager.RemoveByPattern(FORUM_PATTERN_KEY); //event notification - _eventPublisher.EntityDeleted(forumPost); + _services.EventPublisher.EntityDeleted(forumPost); } @@ -887,7 +881,7 @@ 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) @@ -895,7 +889,7 @@ public virtual void InsertPost(ForumPost forumPost, bool sendNotifications) var forum = forumTopic.Forum; 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; @@ -932,7 +926,7 @@ public virtual void UpdatePost(ForumPost forumPost) _cacheManager.RemoveByPattern(FORUM_PATTERN_KEY); //event notification - _eventPublisher.EntityUpdated(forumPost); + _services.EventPublisher.EntityUpdated(forumPost); } /// @@ -949,7 +943,7 @@ public virtual void DeletePrivateMessage(PrivateMessage privateMessage) _forumPrivateMessageRepository.Delete(privateMessage); //event notification - _eventPublisher.EntityDeleted(privateMessage); + _services.EventPublisher.EntityDeleted(privateMessage); } /// @@ -1028,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) @@ -1042,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); } } @@ -1059,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); } } @@ -1083,7 +1077,7 @@ public virtual void DeleteSubscription(ForumSubscription forumSubscription) _forumSubscriptionRepository.Delete(forumSubscription); //event notification - _eventPublisher.EntityDeleted(forumSubscription); + _services.EventPublisher.EntityDeleted(forumSubscription); } /// @@ -1151,7 +1145,7 @@ public virtual void InsertSubscription(ForumSubscription forumSubscription) _forumSubscriptionRepository.Insert(forumSubscription); //event notification - _eventPublisher.EntityInserted(forumSubscription); + _services.EventPublisher.EntityInserted(forumSubscription); } /// @@ -1168,7 +1162,7 @@ public virtual void UpdateSubscription(ForumSubscription forumSubscription) _forumSubscriptionRepository.Update(forumSubscription); //event notification - _eventPublisher.EntityUpdated(forumSubscription); + _services.EventPublisher.EntityUpdated(forumSubscription); } /// @@ -1477,7 +1471,7 @@ public virtual int CalculateTopicPageIndex(int forumTopicId, int pageSize, int p return pageIndex; } - + #endregion } } 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 f86c307925..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 @@ -22,20 +22,19 @@ public partial interface IPictureService /// 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 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 productPictures, out int equalPictureId); + byte[] FindEqualPicture(string path, IEnumerable pictures, out int equalPictureId); /// /// Finds an equal picture by comparing the binary buffer /// /// Binary picture data - /// The sequence of product pictures to seek within for duplicates + /// 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 productPictures, out int equalPictureId); - + byte[] FindEqualPicture(byte[] pictureBinary, IEnumerable pictures, out int equalPictureId); /// /// Gets the loaded picture binary depending on picture storage settings @@ -137,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/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 bc11e71a36..563993234f 100644 --- a/src/Libraries/SmartStore.Services/Media/PictureService.cs +++ b/src/Libraries/SmartStore.Services/Media/PictureService.cs @@ -51,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, @@ -147,12 +138,6 @@ private string GetDefaultImageFileName(PictureType defaultPictureType = PictureT return defaultImageFileName; } - /// - /// Validates input picture dimensions and prevents that the image size exceeds global max size - /// - /// Picture binary - /// MIME type - /// Picture binary or throws an exception public virtual byte[] ValidatePicture(byte[] pictureBinary) { Size originalSize = this.GetPictureSize(pictureBinary); @@ -169,31 +154,21 @@ public virtual byte[] ValidatePicture(byte[] pictureBinary) } } - /// - /// 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 - /// Id of equal picture if any - /// The picture binary for path when no picture equals in the sequence, null otherwise. - public byte[] FindEqualPicture(string path, IEnumerable productPictures, out int equalPictureId) + #endregion + + #region Methods + + public byte[] FindEqualPicture(string path, IEnumerable pictures, out int equalPictureId) { - return FindEqualPicture(File.ReadAllBytes(path), productPictures, out equalPictureId); + return FindEqualPicture(File.ReadAllBytes(path), pictures, out equalPictureId); } - /// - /// Finds an equal picture by comparing the binary buffer - /// - /// Binary picture data - /// The sequence of product 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. - public byte[] FindEqualPicture(byte[] pictureBinary, IEnumerable productPictures, out int equalPictureId) + public byte[] FindEqualPicture(byte[] pictureBinary, IEnumerable pictures, out int equalPictureId) { equalPictureId = 0; try { - foreach (var picture in productPictures) + foreach (var picture in pictures) { var otherPictureBinary = LoadPictureBinary(picture); @@ -216,27 +191,11 @@ public byte[] FindEqualPicture(byte[] pictureBinary, IEnumerable produc } } - #endregion - - #region Methods - - /// - /// Get picture SEO friendly name - /// - /// Name - /// Result - public virtual string GetPictureSeName(string name) + 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 @@ -271,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); @@ -387,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) @@ -462,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, @@ -482,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, @@ -580,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 @@ -620,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) @@ -644,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); @@ -670,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); @@ -684,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); @@ -718,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); @@ -732,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; @@ -757,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 @@ -789,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()) { @@ -811,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(); @@ -854,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++; @@ -878,8 +765,6 @@ protected int MovePictures(bool toDb) } } - _pictureRepository.AutoCommitEnabled = true; - if (affectedFiles.Count > 0) { if ((toDb && !failed) || (!toDb && failed)) @@ -898,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 add0f4e57a..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, int languageId); + 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 08c82ec8cd..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,22 +48,9 @@ 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 - /// A value indicating whether to load manually send emails + /// 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, - bool? sendManually = null); + IPagedList SearchEmails(SearchEmailsQuery query); /// /// Sends a queued email @@ -65,5 +58,18 @@ IPagedList SearchEmails(string fromEmail, /// 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 ea27f3092b..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(" diff --git a/src/Plugins/SmartStore.Clickatell/Views/Web.config b/src/Plugins/SmartStore.Clickatell/Views/Web.config index ab3e708b7d..31b7dee187 100644 --- a/src/Plugins/SmartStore.Clickatell/Views/Web.config +++ b/src/Plugins/SmartStore.Clickatell/Views/Web.config @@ -14,7 +14,7 @@ - + diff --git a/src/Plugins/SmartStore.Clickatell/web.config b/src/Plugins/SmartStore.Clickatell/web.config index 46b8ba77d4..ba87d0f098 100644 --- a/src/Plugins/SmartStore.Clickatell/web.config +++ b/src/Plugins/SmartStore.Clickatell/web.config @@ -1,117 +1,117 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - \ No newline at end of file + 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 c2fd9f3fd6..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: 2.2.0 -MinAppVersion: 2.2.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 e1c88f8d2c..c8f46ee502 100644 --- a/src/Plugins/SmartStore.DevTools/SmartStore.DevTools.csproj +++ b/src/Plugins/SmartStore.DevTools/SmartStore.DevTools.csproj @@ -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,6 +97,11 @@ ..\..\packages\MiniProfiler.3.1.1.140\lib\net40\MiniProfiler.dll True + + False + ..\..\packages\MiniProfiler.EF6.3.0.11\lib\net40\MiniProfiler.EntityFramework6.dll + True + @@ -144,17 +150,20 @@ + + + @@ -198,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) + +
+ + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + - - - - + + + + + + + + + + + + - - + + + + + + + } +
"); + 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, int languageId) + 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(languageId)); + var forumUrl = string.Format("{0}boards/forum/{1}/{2}", _webHelper.GetStoreLocation(false), forum.Id, forum.GetSeName(language.Id)); tokens.Add(new Token("Forums.ForumURL", forumUrl, true)); - tokens.Add(new Token("Forums.ForumName", forum.GetLocalized(x => x.Name, languageId))); + 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 fb53c2aa04..b03c247f39 100644 --- a/src/Libraries/SmartStore.Services/Messages/QueuedEmailService.cs +++ b/src/Libraries/SmartStore.Services/Messages/QueuedEmailService.cs @@ -1,6 +1,9 @@ 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; @@ -8,40 +11,37 @@ 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; + private readonly IRepository _queuedEmailAttachmentRepository; private readonly IEmailSender _emailSender; - private readonly ILogger _logger; - private readonly ILocalizationService _localizationService; - - /// - /// Ctor - /// - /// Queued email repository - /// Event published + private readonly ICommonServices _services; + public QueuedEmailService( IRepository queuedEmailRepository, - IEventPublisher eventPublisher, - IEmailSender emailSender, - ILogger logger, - ILocalizationService localizationService) + IRepository queuedEmailAttachmentRepository, + IEmailSender emailSender, + ICommonServices services) { - _queuedEmailRepository = queuedEmailRepository; - _eventPublisher = eventPublisher; - _emailSender = emailSender; - _logger = logger; - _localizationService = localizationService; + 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) @@ -50,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) @@ -65,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) @@ -80,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) @@ -95,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) @@ -114,7 +101,7 @@ where queuedEmailIds.Contains(qe.Id) var queuedEmails = query.ToList(); - //sort by passed identifiers + // sort by passed identifiers var sortedQueuedEmails = new List(); foreach (int id in queuedEmailIds) @@ -126,97 +113,59 @@ 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 - /// A value indicating whether to load manually send emails - /// 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, - bool? sendManually = null) + public virtual IPagedList SearchEmails(SearchEmailsQuery query) { - fromEmail = (fromEmail ?? String.Empty).Trim(); - toEmail = (toEmail ?? String.Empty).Trim(); + Guard.ArgumentNotNull(() => query); - var query = _queuedEmailRepository.Table; + var q = _queuedEmailRepository.Table; - if (!String.IsNullOrEmpty(fromEmail)) - query = query.Where(qe => qe.From.Contains(fromEmail)); + if (query.Expand.HasValue()) + { + var expands = query.Expand.Split(','); + foreach (var expand in expands) + { + q = q.Expand(expand.Trim()); + } + } - if (!String.IsNullOrEmpty(toEmail)) - query = query.Where(qe => qe.To.Contains(toEmail)); + if (query.From.HasValue()) + q = q.Where(qe => qe.From.Contains(query.From.Trim())); - if (startTime.HasValue) - query = query.Where(qe => qe.CreatedOnUtc >= startTime); + if (query.To.HasValue()) + q = q.Where(qe => qe.To.Contains(query.To.Trim())); - if (endTime.HasValue) - query = query.Where(qe => qe.CreatedOnUtc <= endTime); + if (query.StartTime.HasValue) + q = q.Where(qe => qe.CreatedOnUtc >= query.StartTime); - if (loadNotSentItemsOnly) - query = query.Where(qe => !qe.SentOnUtc.HasValue); + if (query.EndTime.HasValue) + q = q.Where(qe => qe.CreatedOnUtc <= query.EndTime); - if (sendManually.HasValue) - query = query.Where(qe => qe.SendManually == sendManually.Value); + if (query.UnsentOnly) + q = q.Where(qe => !qe.SentOnUtc.HasValue); - query = query.Where(qe => qe.SentTries < maxSendTries); + if (query.SendManually.HasValue) + q = q.Where(qe => qe.SendManually == query.SendManually.Value); + + q = q.Where(qe => qe.SentTries < query.MaxSendTries); - query = query.OrderByDescending(qe => qe.Priority); + q = q.OrderByDescending(qe => qe.Priority); - query = loadNewest ? - ((IOrderedQueryable)query).ThenByDescending(qe => qe.CreatedOnUtc) : - ((IOrderedQueryable)query).ThenBy(qe => qe.CreatedOnUtc); + q = query.OrderByLatest ? + ((IOrderedQueryable)q).ThenByDescending(qe => qe.CreatedOnUtc) : + ((IOrderedQueryable)q).ThenBy(qe => qe.CreatedOnUtc); - var queuedEmails = new PagedList(query, pageIndex, pageSize); + var queuedEmails = new PagedList(q, query.PageIndex, query.PageSize); return queuedEmails; } - /// - /// Sends a queued email - /// - /// Queued email - /// Whether the operation succeeded public virtual bool SendEmail(QueuedEmail queuedEmail) { var result = false; try { - var bcc = String.IsNullOrWhiteSpace(queuedEmail.Bcc) ? null : queuedEmail.Bcc.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); - var cc = String.IsNullOrWhiteSpace(queuedEmail.CC) ? null : queuedEmail.CC.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); - var smtpContext = new SmtpContext(queuedEmail.EmailAccount); - - var msg = new EmailMessage( - new EmailAddress(queuedEmail.To, queuedEmail.ToName), - queuedEmail.Subject, - queuedEmail.Body, - new EmailAddress(queuedEmail.From, queuedEmail.FromName)); - - if (queuedEmail.ReplyTo.HasValue()) - { - msg.ReplyTo.Add(new EmailAddress(queuedEmail.ReplyTo, queuedEmail.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))); - } + var msg = ConvertEmail(queuedEmail); _emailSender.SendEmail(smtpContext, msg); @@ -225,14 +174,134 @@ public virtual bool SendEmail(QueuedEmail queuedEmail) } catch (Exception exc) { - _logger.Error(string.Concat(_localizationService.GetResource("Admin.Common.ErrorSendingEmail"), ": ", exc.Message), 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 cd3110c81d..a37e7e82c4 100644 --- a/src/Libraries/SmartStore.Services/Messages/QueuedMessagesSendTask.cs +++ b/src/Libraries/SmartStore.Services/Messages/QueuedMessagesSendTask.cs @@ -9,20 +9,27 @@ public partial class QueuedMessagesSendTask : ITask { private readonly IQueuedEmailService _queuedEmailService; - public QueuedMessagesSendTask( - IQueuedEmailService queuedEmailService) + public QueuedMessagesSendTask(IQueuedEmailService queuedEmailService) { _queuedEmailService = queuedEmailService; } public void Execute(TaskExecutionContext ctx) { - const int pageSize = 100; + const int pageSize = 1000; const int maxTries = 3; for (int i = 0; i < 9999999; ++i) { - var queuedEmails = _queuedEmailService.SearchEmails(null, null, null, null, true, maxTries, false, i, pageSize, false); + var q = new SearchEmailsQuery + { + MaxSendTries = maxTries, + PageIndex = i, + PageSize = pageSize, + Expand = "Attachments", + UnsentOnly = true + }; + var queuedEmails = _queuedEmailService.SearchEmails(q); foreach (var queuedEmail in queuedEmails) { 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 bbad203299..1f2746245e 100644 --- a/src/Libraries/SmartStore.Services/Messages/WorkflowMessageService.cs +++ b/src/Libraries/SmartStore.Services/Messages/WorkflowMessageService.cs @@ -8,14 +8,17 @@ using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Forums; +using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Messages; using SmartStore.Core.Domain.News; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Shipping; using SmartStore.Core.Domain.Stores; using SmartStore.Core.Events; +using SmartStore.Core.Localization; using SmartStore.Services.Customers; using SmartStore.Services.Localization; +using SmartStore.Services.Media; using SmartStore.Services.Stores; namespace SmartStore.Services.Messages @@ -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; } + + #endregion - #region Utilities + #region Utilities - protected int SendNotification( + protected int SendNotification( MessageTemplate messageTemplate, EmailAccount emailAccount, int languageId, - IEnumerable tokens, + IList tokens, string toEmailAddress, string toName, string replyTo = null, @@ -90,7 +103,7 @@ protected int SendNotification( var bodyReplaced = _tokenizer.Replace(body, tokens, true); bodyReplaced = WebHelper.MakeAllUrlsAbsolute(bodyReplaced, _httpRequest); - + var email = new QueuedEmail { Priority = 5, @@ -109,16 +122,52 @@ protected int SendNotification( 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, languageId); + _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; @@ -1038,16 +1060,16 @@ public int SendNewForumPostMessage(Customer customer, _messageTokenProvider.AddForumPostTokens(tokens, forumPost); _messageTokenProvider.AddCustomerTokens(tokens, customer); _messageTokenProvider.AddForumTopicTokens(tokens, forumPost.ForumTopic, friendlyForumTopicPageIndex, forumPost.Id); - _messageTokenProvider.AddForumTokens(tokens, forumPost.ForumTopic.Forum, languageId); + _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); } /// @@ -1065,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; @@ -1122,9 +1144,10 @@ public virtual int SendGenericMessage(string messageTemplateName, Action @@ -1190,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; @@ -1211,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); } /// @@ -1236,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); } /// @@ -1265,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; @@ -1288,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); } /// @@ -1313,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; @@ -1327,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); } /// @@ -1352,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; @@ -1366,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); } /// @@ -1391,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; @@ -1406,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 d9a340c451..7e266a98b1 100644 --- a/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeService.cs @@ -4,18 +4,19 @@ 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."; @@ -28,7 +29,8 @@ public partial class CheckoutAttributeService : ICheckoutAttributeService 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 @@ -45,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"); @@ -77,26 +85,56 @@ public virtual void DeleteCheckoutAttribute(CheckoutAttribute checkoutAttribute) _eventPublisher.EntityDeleted(checkoutAttribute); } - /// - /// Gets all checkout attributes - /// - /// Checkout attribute collection - public virtual IList GetAllCheckoutAttributes(bool showHidden = false) + /// + /// 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) { - string key = CHECKOUTATTRIBUTES_ALL_KEY.FormatInvariant(showHidden); + string key = CHECKOUTATTRIBUTES_ALL_KEY.FormatInvariant(storeId, showHidden); return _cacheManager.Get(key, () => { - var query = _checkoutAttributeRepository.Table; + var query = GetCheckoutAttributes(storeId, showHidden); - if (!showHidden) - query = query.Where(x => x.IsActive); - - query = query.OrderBy(x => x.DisplayOrder); - - var checkoutAttributes = query.ToList(); - return checkoutAttributes; - }); + return query.ToList(); + }); } /// diff --git a/src/Libraries/SmartStore.Services/Orders/ICheckoutAttributeService.cs b/src/Libraries/SmartStore.Services/Orders/ICheckoutAttributeService.cs index 2ceb9c3396..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(bool showHidden = false); + /// + /// 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 e8ec6bcd06..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) @@ -170,6 +177,14 @@ List AddToCart(Customer customer, Product product, ShoppingCartType cart /// Add to cart context 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 a1b07c3749..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) @@ -1460,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); @@ -1482,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, @@ -1514,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; } } @@ -1558,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; } /// @@ -1674,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); @@ -1687,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)); } } @@ -1730,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)); } } @@ -1800,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); @@ -1923,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); @@ -1957,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)) { @@ -1985,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; @@ -2007,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; @@ -2020,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 @@ -2047,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; } /// @@ -2112,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); @@ -2173,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; @@ -2182,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 @@ -2191,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; } /// @@ -2273,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; @@ -2284,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); } @@ -2344,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; @@ -2364,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; } /// @@ -2456,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; @@ -2465,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); } @@ -2517,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; @@ -2530,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; } @@ -2611,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); } @@ -2642,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(); @@ -2681,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; @@ -2708,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; @@ -2737,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; } @@ -2744,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 8beaa3da51..e154dd519d 100644 --- a/src/Libraries/SmartStore.Services/Orders/OrderTotalCalculationService.cs +++ b/src/Libraries/SmartStore.Services/Orders/OrderTotalCalculationService.cs @@ -9,6 +9,8 @@ using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Shipping; using SmartStore.Core.Domain.Tax; +using SmartStore.Core.Localization; +using SmartStore.Core.Plugins; using SmartStore.Services.Catalog; using SmartStore.Services.Common; using SmartStore.Services.Discounts; @@ -18,10 +20,10 @@ namespace SmartStore.Services.Orders { - /// - /// Order service - /// - public partial class OrderTotalCalculationService : IOrderTotalCalculationService + /// + /// Order service + /// + public partial class OrderTotalCalculationService : IOrderTotalCalculationService { #region Fields @@ -30,7 +32,7 @@ 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; @@ -69,7 +71,7 @@ public OrderTotalCalculationService(IWorkContext workContext, IPriceCalculationService priceCalculationService, ITaxService taxService, IShippingService shippingService, - IPaymentService paymentService, + IProviderManager providerManager, ICheckoutAttributeParser checkoutAttributeParser, IDiscountService discountService, IGiftCardService giftCardService, @@ -86,7 +88,7 @@ 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; @@ -97,20 +99,24 @@ public OrderTotalCalculationService(IWorkContext workContext, 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) @@ -354,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); } @@ -613,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) @@ -798,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); @@ -852,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); } /// @@ -892,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; @@ -906,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 @@ -1063,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); } @@ -1116,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,30 +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)); + warnings.Add(T("ShoppingCart.QuantityExceedsStock", maximumQuantityCanBeAdded)); } } } @@ -446,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; } } @@ -463,27 +474,22 @@ 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(); @@ -501,7 +507,7 @@ public virtual IList GetShoppingCartItemAttributeWarnings(Customer custo if (pv1 == null || pv1.Id != product.Id) { - warnings.Add(_localizationService.GetResource("ShoppingCart.AttributeError")); + warnings.Add(T("ShoppingCart.AttributeError")); return warnings; } } @@ -537,8 +543,7 @@ public virtual IList GetShoppingCartItemAttributeWarnings(Customer custo if (!found) { - warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.SelectAttribute"), - pva2.TextPrompt.HasValue() ? pva2.TextPrompt : pva2.ProductAttribute.GetLocalized(a => a.Name))); + warnings.Add(T("ShoppingCart.SelectAttribute", pva2.TextPrompt.HasValue() ? pva2.TextPrompt : pva2.ProductAttribute.GetLocalized(a => a.Name))); } } } @@ -546,19 +551,20 @@ public virtual IList GetShoppingCartItemAttributeWarnings(Customer custo // check if there is a selected attribute combination and if it is active if (warnings.Count == 0 && selectedAttributes.HasValue()) { - var combination = product - .ProductVariantAttributeCombinations - .FirstOrDefault(x => _productAttributeParser.AreProductAttributesEqual(x.AttributesXml, selectedAttributes)); + if (combination == null) + { + combination = _productAttributeParser.FindProductVariantAttributeCombination(product.Id, selectedAttributes); + } if (combination != null && !combination.IsActive) { - warnings.Add(_localizationService.GetResource("ShoppingCart.NotAvailable")); + 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) @@ -571,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)); } } } @@ -591,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) /// @@ -616,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")); + } } } @@ -655,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; @@ -767,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))); } } } @@ -875,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; //} @@ -1014,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, @@ -1038,8 +1122,9 @@ public virtual List AddToCart(Customer customer, Product product, Shoppi }; if (bundleItem != null) + { cartItem.BundleItemId = bundleItem.Id; - + } if (ctx == null) { @@ -1085,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); 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 2051adef9c..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? diff --git a/src/Libraries/SmartStore.Services/Payments/PaymentService.cs b/src/Libraries/SmartStore.Services/Payments/PaymentService.cs index 69f2d391de..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 => p.Value.IsActive && _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 22dfb32589..4694512828 100644 --- a/src/Libraries/SmartStore.Services/Pdf/WkHtmlToPdfConverter.cs +++ b/src/Libraries/SmartStore.Services/Pdf/WkHtmlToPdfConverter.cs @@ -1,9 +1,6 @@ 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; @@ -12,11 +9,8 @@ namespace SmartStore.Services.Pdf { public class WkHtmlToPdfConverter : IPdfConverter { - private readonly HttpContextBase _httpContext; - - public WkHtmlToPdfConverter(HttpContextBase httpContext) + public WkHtmlToPdfConverter() { - this._httpContext = httpContext; Logger = NullLogger.Instance; } 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 8bbb6e60fe..29c29d13ef 100644 --- a/src/Libraries/SmartStore.Services/Seo/SeoExtensions.cs +++ b/src/Libraries/SmartStore.Services/Seo/SeoExtensions.cs @@ -9,6 +9,7 @@ using SmartStore.Core.Infrastructure; using SmartStore.Services.Localization; using SmartStore.Utilities; +using SmartStore.Core.Localization; namespace SmartStore.Services.Seo { @@ -160,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); @@ -218,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; @@ -251,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; @@ -294,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 776d424311..497555026d 100644 --- a/src/Libraries/SmartStore.Services/Seo/SitemapGenerator.cs +++ b/src/Libraries/SmartStore.Services/Seo/SitemapGenerator.cs @@ -96,23 +96,31 @@ 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) 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/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 7afbb90cbb..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 @@ -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/ShippingService.cs b/src/Libraries/SmartStore.Services/Shipping/ShippingService.cs index 2b834b4194..4f6db7fa9c 100644 --- a/src/Libraries/SmartStore.Services/Shipping/ShippingService.cs +++ b/src/Libraries/SmartStore.Services/Shipping/ShippingService.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using SmartStore.Core.Caching; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Common; @@ -9,34 +8,36 @@ using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Shipping; using SmartStore.Core.Events; +using SmartStore.Core.Infrastructure; +using SmartStore.Core.Localization; using SmartStore.Core.Logging; using SmartStore.Core.Plugins; using SmartStore.Services.Catalog; using SmartStore.Services.Common; using SmartStore.Services.Configuration; -using SmartStore.Services.Localization; using SmartStore.Services.Orders; namespace SmartStore.Services.Shipping { - public partial class ShippingService : IShippingService + public partial class ShippingService : IShippingService { - #region Fields + #region Fields - private readonly IRepository _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 => 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,7 +382,7 @@ public virtual GetShippingOptionResponse GetShippingOptions(IList @@ -374,7 +391,7 @@ 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 5d5e01f9a6..1edfe58738 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -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\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll False ..\..\packages\EPPlus.4.0.3\lib\net20\EPPlus.dll - - False - ..\..\packages\fasterflect.2.1.3\lib\net40\Fasterflect.dll + + ..\..\packages\ImageResizer.4.0.4\lib\net45\ImageResizer.dll + True - - False - ..\..\packages\ImageResizer.3.4.2\lib\ImageResizer.dll + + ..\..\packages\ImageResizer.Plugins.PrettyGifs.4.0.4\lib\net45\ImageResizer.Plugins.PrettyGifs.dll + True - - False - ..\..\packages\ImageResizer.Plugins.PrettyGifs.3.4.2\lib\ImageResizer.Plugins.PrettyGifs.dll + + ..\..\packages\LumenWorksCsvReader.3.9\lib\net20\LumenWorks.Framework.IO.dll + True ..\..\packages\MaxMind.GeoIP.2.1.17\lib\net35\MaxMind.GeoIP.dll @@ -96,9 +101,12 @@ 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 @@ -110,9 +118,12 @@ + ..\..\packages\System.Linq.Dynamic.1.0.0\lib\net40\System.Linq.Dynamic.dll + + False @@ -143,8 +154,9 @@ - - ..\..\packages\UAParser.1.2.0.0\lib\net35-Client\UAParser.dll + + ..\..\packages\UAParser.2.0.0.0\lib\net40-Client\UAParser.dll + True @@ -154,13 +166,82 @@ Properties\AssemblyVersionInfo.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -204,7 +285,6 @@ - @@ -236,7 +316,6 @@ - @@ -253,7 +332,6 @@ - @@ -287,8 +365,6 @@ - - @@ -307,12 +383,9 @@ - - - @@ -420,6 +493,7 @@ + @@ -435,13 +509,17 @@ + + + + + - - - + + @@ -525,6 +603,7 @@ Nop_Services_EuropaCheckVatService_checkVatService + diff --git a/src/Libraries/SmartStore.Services/Stores/IStoreMappingService.cs b/src/Libraries/SmartStore.Services/Stores/IStoreMappingService.cs index bd5c339ad3..ee938acb15 100644 --- a/src/Libraries/SmartStore.Services/Stores/IStoreMappingService.cs +++ b/src/Libraries/SmartStore.Services/Stores/IStoreMappingService.cs @@ -46,7 +46,7 @@ public partial interface IStoreMappingService IQueryable GetStoreMappingsFor(string entityName, int entityId); /// - /// Save the store napping for an entity + /// Save the store mapping for an entity /// /// Entity type /// The entity diff --git a/src/Libraries/SmartStore.Services/Stores/StoreMappingService.cs b/src/Libraries/SmartStore.Services/Stores/StoreMappingService.cs index da282801ef..c2ccb86f17 100644 --- a/src/Libraries/SmartStore.Services/Stores/StoreMappingService.cs +++ b/src/Libraries/SmartStore.Services/Stores/StoreMappingService.cs @@ -141,12 +141,12 @@ public virtual void SaveStoreMappings(T entity, int[] selectedStoreIds) where { if (selectedStoreIds != null && selectedStoreIds.Contains(store.Id)) { - if (existingStoreMappings.Where(sm => sm.StoreId == store.Id).Count() == 0) + if (!existingStoreMappings.Any(x => x.StoreId == store.Id)) InsertStoreMapping(entity, store.Id); } else { - var storeMappingToDelete = existingStoreMappings.Where(sm => sm.StoreId == store.Id).FirstOrDefault(); + var storeMappingToDelete = existingStoreMappings.FirstOrDefault(x => x.StoreId == store.Id); if (storeMappingToDelete != null) DeleteStoreMapping(storeMappingToDelete); } @@ -185,7 +185,7 @@ public virtual void InsertStoreMapping(T entity, int storeId) where T : BaseE int entityId = entity.Id; string entityName = typeof(T).Name; - var storeMapping = new StoreMapping() + var storeMapping = new StoreMapping { EntityId = entityId, EntityName = entityName, @@ -219,7 +219,7 @@ public virtual void UpdateStoreMapping(StoreMapping storeMapping) public virtual int[] GetStoresIdsWithAccess(T entity) where T : BaseEntity, IStoreMappingSupported { if (entity == null) - throw new ArgumentNullException("entity"); + return new int[0]; int entityId = entity.Id; string entityName = typeof(T).Name; diff --git a/src/Libraries/SmartStore.Services/Stores/StoreService.cs b/src/Libraries/SmartStore.Services/Stores/StoreService.cs index f763985753..a773079402 100644 --- a/src/Libraries/SmartStore.Services/Stores/StoreService.cs +++ b/src/Libraries/SmartStore.Services/Stores/StoreService.cs @@ -81,9 +81,12 @@ public virtual IList GetAllStores() string key = STORES_ALL_KEY; return _cacheManager.Get(key, () => { - var query = from s in _storeRepository.Table - orderby s.DisplayOrder, s.Name - select s; + var query = _storeRepository.Table + .Expand(x => x.PrimaryStoreCurrency) + .Expand(x => x.PrimaryExchangeRateCurrency) + .OrderBy(x => x.DisplayOrder) + .ThenBy(x => x.Name); + var stores = query.ToList(); return stores; }); @@ -147,9 +150,7 @@ public virtual bool IsSingleStoreMode() { if (!_isSingleStoreMode.HasValue) { - var query = from s in _storeRepository.Table - select s; - _isSingleStoreMode = query.Count() <= 1; + _isSingleStoreMode = (_storeRepository.TableUntracked.Count() <= 1); } return _isSingleStoreMode.Value; @@ -176,6 +177,8 @@ public virtual bool IsStoreDataValid(Store store) { case "www.yourstore.com": case "yourstore.com": + case "www.mystore.com": + case "mystore.com": case "www.mein-shop.de": case "mein-shop.de": return false; diff --git a/src/Libraries/SmartStore.Services/Tasks/ChangeTaskSchedulerBaseUrlConsumer.cs b/src/Libraries/SmartStore.Services/Tasks/ChangeTaskSchedulerBaseUrlConsumer.cs new file mode 100644 index 0000000000..a79a13c4ec --- /dev/null +++ b/src/Libraries/SmartStore.Services/Tasks/ChangeTaskSchedulerBaseUrlConsumer.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Web; +using SmartStore.Core.Domain.Stores; +using SmartStore.Core.Events; +using SmartStore.Services.Stores; +using SmartStore.Utilities; + +namespace SmartStore.Services.Tasks +{ + public class ChangeTaskSchedulerBaseUrlConsumer : + IConsumer>, + IConsumer>, + IConsumer> + { + private readonly ITaskScheduler _taskScheduler; + private readonly IStoreService _storeService; + private readonly HttpContextBase _httpContext; + private readonly bool _shouldChange; + + public ChangeTaskSchedulerBaseUrlConsumer(ITaskScheduler taskScheduler, IStoreService storeService, HttpContextBase httpContext) + { + this._taskScheduler = taskScheduler; + this._storeService = storeService; + this._httpContext = httpContext; + this._shouldChange = CommonHelper.GetAppSetting("sm:TaskSchedulerBaseUrl").IsWebUrl() == false; + } + + public void HandleEvent(EntityInserted eventMessage) + { + HandleEventCore(); + } + + public void HandleEvent(EntityUpdated eventMessage) + { + HandleEventCore(); + } + + public void HandleEvent(EntityDeleted eventMessage) + { + HandleEventCore(); + } + + private void HandleEventCore() + { + if (_shouldChange) + { + _taskScheduler.SetBaseUrl(_storeService, _httpContext); + } + } + } +} diff --git a/src/Libraries/SmartStore.Services/Tasks/CronExpression.cs b/src/Libraries/SmartStore.Services/Tasks/CronExpression.cs new file mode 100644 index 0000000000..c35b4b7128 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Tasks/CronExpression.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NCrontab; +using CronExpressionDescriptor; +using System.Threading; + +namespace SmartStore.Services.Tasks +{ + + public static class CronExpression + { + + public static bool IsValid(string expression) + { + try + { + CrontabSchedule.Parse(expression); + return true; + } + catch { } + + return false; + } + + public static DateTime GetNextSchedule(string expression, DateTime baseTime) + { + return GetFutureSchedules(expression, baseTime, 1).FirstOrDefault(); + } + + public static DateTime GetNextSchedule(string expression, DateTime baseTime, DateTime endTime) + { + return GetFutureSchedules(expression, baseTime, endTime, 1).FirstOrDefault(); + } + + public static IEnumerable GetFutureSchedules(string expression, DateTime baseTime, int max = 10) + { + return GetFutureSchedules(expression, baseTime, DateTime.MaxValue); + } + + public static IEnumerable GetFutureSchedules(string expression, DateTime baseTime, DateTime endTime, int max = 10) + { + Guard.ArgumentNotEmpty(() => expression); + + var schedule = CrontabSchedule.Parse(expression); + return schedule.GetNextOccurrences(baseTime, endTime).Take(max); + } + + public static string GetFriendlyDescription(string expression) + { + Guard.ArgumentNotEmpty(() => expression); + + var options = new Options + { + DayOfWeekStartIndexZero = true, + ThrowExceptionOnParseError = true, + Verbose = false, + Use24HourTimeFormat = Thread.CurrentThread.CurrentUICulture.DateTimeFormat.AMDesignator.IsEmpty() + }; + + if (expression.HasValue()) + { + try + { + return ExpressionDescriptor.GetDescription(expression, options); + } + catch { } + } + + return "?"; + } + + } + +} diff --git a/src/Libraries/SmartStore.Services/Tasks/IScheduleTaskService.cs b/src/Libraries/SmartStore.Services/Tasks/IScheduleTaskService.cs index 54207b308f..440fe98379 100644 --- a/src/Libraries/SmartStore.Services/Tasks/IScheduleTaskService.cs +++ b/src/Libraries/SmartStore.Services/Tasks/IScheduleTaskService.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using SmartStore.Core.Domain.Tasks; @@ -31,9 +32,34 @@ public partial interface IScheduleTaskService /// /// Gets all tasks /// - /// A value indicating whether to show hidden records + /// A value indicating whether to load disabled tasks also /// Tasks - IList GetAllTasks(bool showHidden = false); + IList GetAllTasks(bool includeDisabled = false); + + /// + /// Gets all pending tasks + /// + /// Tasks + IList GetPendingTasks(); + + /// + /// Gets a value indicating whether at least one task is running currently. + /// + /// + bool HasRunningTasks(); + + /// + /// Gets a value indicating whether a task is currently running + /// + /// A identifier + /// true if the task is running, false otherwise + bool IsTaskRunning(int taskId); + + /// + /// Gets a list of currently running instances. + /// + /// Tasks + IList GetRunningTasks(); /// /// Inserts a task @@ -48,15 +74,17 @@ public partial interface IScheduleTaskService void UpdateTask(ScheduleTask task); /// - /// Ensures that a task is not marked as running + /// Calculates - according to their cron expressions - all task future schedules + /// and saves them to the database. /// - /// Task type - void EnsureTaskIsNotRunning(string type); + /// When true, determines stale tasks and fixes their states to idle. + void CalculateFutureSchedules(IEnumerable tasks, bool isAppStart = false); /// - /// Ensures that a task is not marked as running + /// Calculates the next schedule according to the task's cron expression /// - /// Task identifier - void EnsureTaskIsNotRunning(int taskId); + /// ScheduleTask + /// The next schedule or null if the task is disabled + DateTime? GetNextSchedule(ScheduleTask task); } } diff --git a/src/Libraries/SmartStore.Services/Tasks/ITaskExecutor.cs b/src/Libraries/SmartStore.Services/Tasks/ITaskExecutor.cs new file mode 100644 index 0000000000..053cf45937 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Tasks/ITaskExecutor.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using SmartStore.Core.Domain.Tasks; + +namespace SmartStore.Services.Tasks +{ + public interface ITaskExecutor + { + void Execute( + ScheduleTask task, + IDictionary taskParameters = null, + bool throwOnError = false); + } +} diff --git a/src/Libraries/SmartStore.Services/Tasks/ITaskScheduler.cs b/src/Libraries/SmartStore.Services/Tasks/ITaskScheduler.cs new file mode 100644 index 0000000000..f852d12952 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Tasks/ITaskScheduler.cs @@ -0,0 +1,98 @@ +using System; +using System.Web; +using System.Linq; +using SmartStore.Core; +using SmartStore.Services.Stores; +using System.Web.Hosting; +using System.Collections.Generic; +using System.Threading; +using SmartStore.Core.Domain.Tasks; + +namespace SmartStore.Services.Tasks +{ + /// + /// Task scheduler interface + /// + public interface ITaskScheduler + { + /// + /// The interval in which the scheduler triggers the sweep url + /// (which determines pending tasks and executes them in the scope of a regular HTTP request). + /// + int SweepIntervalMinutes { get; set; } + + /// + /// The fully qualified base url + /// + string BaseUrl { get; set; } + + /// + /// Gets a value indicating whether the scheduler is active and periodically sweeps all tasks. + /// + bool IsActive { get; } + + /// + /// Gets a instance used + /// to signal a task cancellation request. + /// + /// A identifier + /// A instance if the task is running, null otherwise + CancellationTokenSource GetCancelTokenSourceFor(int scheduleTaskId); + + /// + /// Starts/initializes the scheduler + /// + void Start(); + + /// + /// Stops the scheduler + /// + void Stop(); + + /// + /// Executes a single task immediately + /// + /// + /// Optional task parameters + void RunSingleTask(int scheduleTaskId, IDictionary taskParameters = null); + + /// + /// Verifies the authentication token which is generated right before the HTTP endpoint gets called. + /// + /// The authentication token to verify + /// true if the validation succeeds, false otherwise + /// + /// The task scheduler sends the token as a HTTP request header item. + /// The called endpoint (e.g. a controller action) is reponsible for invoking + /// this method and quitting the tasks's execution - preferrably with HTTP 403 - + /// if the verification fails. + /// + bool VerifyAuthToken(string authToken); + } + + public static class ITaskSchedulerExtensions + { + + internal static void SetBaseUrl(this ITaskScheduler scheduler, IStoreService storeService, HttpContextBase httpContext) + { + string url = ""; + + if (!httpContext.Request.IsLocal) + { + var defaultStore = storeService.GetAllStores().FirstOrDefault(x => storeService.IsStoreDataValid(x)); + if (defaultStore != null) + { + url = defaultStore.Url.EnsureEndsWith("/") + "TaskScheduler"; + } + } + + if (url.IsEmpty()) + { + url = WebHelper.GetAbsoluteUrl(VirtualPathUtility.ToAbsolute("~/TaskScheduler"), httpContext.Request); + } + + scheduler.BaseUrl = url; + } + + } +} diff --git a/src/Libraries/SmartStore.Services/Tasks/InitializeSchedulerFilter.cs b/src/Libraries/SmartStore.Services/Tasks/InitializeSchedulerFilter.cs new file mode 100644 index 0000000000..3329fbf3a7 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Tasks/InitializeSchedulerFilter.cs @@ -0,0 +1,95 @@ +using SmartStore.Core; +using SmartStore.Core.Events; +using SmartStore.Core.Infrastructure; +using SmartStore.Services.Stores; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Web.Mvc; +using SmartStore.Core.Data; +using SmartStore.Core.Logging; +using SmartStore.Utilities; + +namespace SmartStore.Services.Tasks +{ + public class InitializeSchedulerFilter : IAuthorizationFilter + { + private readonly static object s_lock = new object(); + private static int s_errCount; + private static bool s_initializing = false; + + public void OnAuthorization(AuthorizationContext filterContext) + { + if (filterContext == null || filterContext.HttpContext == null) + return; + + var request = filterContext.HttpContext.Request; + if (request == null) + return; + + if (filterContext.IsChildAction) + return; + + lock (s_lock) + { + if (!s_initializing) + { + s_initializing = true; + + ILogger logger = EngineContext.Current.Resolve(); + ITaskScheduler taskScheduler = EngineContext.Current.Resolve(); + + try + { + var taskService = EngineContext.Current.Resolve(); + var storeService = EngineContext.Current.Resolve(); + var eventPublisher = EngineContext.Current.Resolve(); + + var tasks = taskService.GetAllTasks(true); + taskService.CalculateFutureSchedules(tasks, true /* isAppStart */); + + var baseUrl = CommonHelper.GetAppSetting("sm:TaskSchedulerBaseUrl"); + if (baseUrl.IsWebUrl()) + { + taskScheduler.BaseUrl = baseUrl; + } + else + { + // autoresolve base url + taskScheduler.SetBaseUrl(storeService, filterContext.HttpContext); + } + + taskScheduler.SweepIntervalMinutes = CommonHelper.GetAppSetting("sm:TaskSchedulerSweepInterval", 1); + taskScheduler.Start(); + + logger.Information("Initialized TaskScheduler with base url '{0}'".FormatInvariant(taskScheduler.BaseUrl)); + + eventPublisher.Publish(new AppInitScheduledTasksEvent { ScheduledTasks = tasks }); + } + catch (Exception ex) + { + s_errCount++; + s_initializing = false; + logger.Error("Error while initializing Task Scheduler", ex); + } + finally + { + var tooManyFailures = s_errCount >= 10; + + if (tooManyFailures || (taskScheduler != null && taskScheduler.IsActive)) + { + GlobalFilters.Filters.Remove(this); + } + + if (tooManyFailures && logger != null) + { + logger.Warning("Stopped trying to initialize the Task Scheduler: too many failed attempts in succession (10+). Maybe uncommenting the setting 'sm:TaskSchedulerBaseUrl' in web.config solves the problem?"); + } + } + } + } + } + } +} diff --git a/src/Libraries/SmartStore.Services/Tasks/Job.cs b/src/Libraries/SmartStore.Services/Tasks/Job.cs deleted file mode 100644 index c38203e95c..0000000000 --- a/src/Libraries/SmartStore.Services/Tasks/Job.cs +++ /dev/null @@ -1,205 +0,0 @@ -using System; -using System.Threading; -using Autofac; -using SmartStore.Core.Domain.Tasks; -using SmartStore.Core.Infrastructure; -using SmartStore.Core.Logging; - -namespace SmartStore.Services.Tasks -{ - /// - /// Task - /// - /// - /// Formerly Task. Had to rename to Job in order to prevent naming conflicts with System.Threading.Tasks.Task - /// - public partial class Job - { - - /// - /// Ctor for Task - /// - private Job() - { - this.Enabled = true; - } - - /// - /// Ctor for Task - /// - /// Task - public Job(ScheduleTask task) - { - this.Type = task.Type; - this.Enabled = task.Enabled; - this.StopOnError = task.StopOnError; - this.Name = task.Name; - this.LastError = task.LastError; - this.IsRunning = task.IsRunning; //task.LastStartUtc.GetValueOrDefault() > task.LastEndUtc.GetValueOrDefault(); - } - - private ITask CreateTask(ILifetimeScope scope) - { - ITask task = null; - if (this.Enabled) - { - var type2 = System.Type.GetType(this.Type); - if (type2 != null) - { - object instance; - if (!EngineContext.Current.ContainerManager.TryResolve(type2, scope, out instance)) - { - // not resolved - instance = EngineContext.Current.ContainerManager.ResolveUnregistered(type2, scope); - } - task = instance as ITask; - } - } - return task; - } - - /// - /// Executes the task - /// - /// - /// The caller is responsible for disposing the lifetime scope - /// - public void Execute(ILifetimeScope scope = null, bool throwOnError = false) - { - Execute(CancellationToken.None, scope, throwOnError); - } - - /// - /// Executes the task - /// - /// - /// The caller is responsible for disposing the lifetime scope - /// - public void Execute( - CancellationToken cancellationToken, - ILifetimeScope scope = null, - bool throwOnError = false) - { - this.IsRunning = true; - var faulted = false; - scope = scope ?? EngineContext.Current.ContainerManager.Scope(); - - try - { - var task = this.CreateTask(scope); - if (task != null) - { - this.LastStartUtc = DateTime.UtcNow; - - var scheduleTaskService = scope.Resolve(); - var scheduleTask = scheduleTaskService.GetTaskByType(this.Type); - - if (scheduleTask != null) - { - //update appropriate datetime properties - scheduleTask.LastStartUtc = this.LastStartUtc; - scheduleTaskService.UpdateTask(scheduleTask); - } - - //execute task - var ctx = new TaskExecutionContext - { - LifetimeScope = scope, - CancellationToken = cancellationToken - }; - - task.Execute(ctx); - - ctx.CancellationToken.ThrowIfCancellationRequested(); - this.LastEndUtc = this.LastSuccessUtc = DateTime.UtcNow; - this.LastError = null; - } - else - { - faulted = true; - this.LastError = "Could not create task instance"; - } - } - catch (Exception ex) - { - faulted = true; - this.Enabled = !this.StopOnError; - this.LastEndUtc = DateTime.UtcNow; - this.LastError = ex.Message.Truncate(995, "..."); - - //log error - var logger = scope.Resolve(); - logger.Error(string.Format("Error while running the '{0}' schedule task. {1}", this.Name, ex.Message), ex); - if (throwOnError) - { - throw; - } - } - finally - { - var scheduleTaskService = scope.Resolve(); - var scheduleTask = scheduleTaskService.GetTaskByType(this.Type); - - if (scheduleTask != null) - { - // update appropriate properties - scheduleTask.LastError = this.LastError; - scheduleTask.LastEndUtc = this.LastEndUtc; - if (!faulted) - { - scheduleTask.LastSuccessUtc = this.LastSuccessUtc; - } - - scheduleTaskService.UpdateTask(scheduleTask); - } - - this.IsRunning = false; - } - } - - /// - /// A value indicating whether a task is running - /// - public bool IsRunning { get; private set; } - - /// - /// Datetime of the last start - /// - public DateTime? LastStartUtc { get; private set; } - - /// - /// Datetime of the last end - /// - public DateTime? LastEndUtc { get; private set; } - - /// - /// Datetime of the last success - /// - public DateTime? LastSuccessUtc { get; private set; } - - /// - /// Message of the last error - /// - public string LastError { get; private set; } - - /// - /// A value indicating type of the task - /// - public string Type { get; private set; } - - /// - /// A value indicating whether to stop task on error - /// - public bool StopOnError { get; private set; } - - /// - /// Get the task name - /// - public string Name { get; private set; } - - /// - /// A value indicating whether the task is enabled - /// - public bool Enabled { get; set; } - } -} diff --git a/src/Libraries/SmartStore.Services/Tasks/ScheduleTaskService.cs b/src/Libraries/SmartStore.Services/Tasks/ScheduleTaskService.cs index d31eaa1b0f..83a4a97b75 100644 --- a/src/Libraries/SmartStore.Services/Tasks/ScheduleTaskService.cs +++ b/src/Libraries/SmartStore.Services/Tasks/ScheduleTaskService.cs @@ -1,8 +1,11 @@ using System; using System.Collections.Generic; +using System.Data.Entity.Infrastructure; using System.Linq; using SmartStore.Core.Data; using SmartStore.Core.Domain.Tasks; +using SmartStore.Core.Localization; +using SmartStore.Services.Helpers; namespace SmartStore.Services.Tasks { @@ -14,24 +17,26 @@ public partial class ScheduleTaskService : IScheduleTaskService #region Fields private readonly IRepository _taskRepository; + private readonly IDateTimeHelper _dateTimeHelper; #endregion #region Ctor - public ScheduleTaskService(IRepository taskRepository) + public ScheduleTaskService(IRepository taskRepository, IDateTimeHelper dateTimeHelper) { this._taskRepository = taskRepository; + this._dateTimeHelper = dateTimeHelper; + + T = NullLocalizer.Instance; } + public Localizer T { get; set; } + #endregion #region Methods - /// - /// Deletes a task - /// - /// Task public virtual void DeleteTask(ScheduleTask task) { if (task == null) @@ -40,11 +45,6 @@ public virtual void DeleteTask(ScheduleTask task) _taskRepository.Delete(task); } - /// - /// Gets a task - /// - /// Task identifier - /// Task public virtual ScheduleTask GetTaskById(int taskId) { if (taskId == 0) @@ -53,11 +53,6 @@ public virtual ScheduleTask GetTaskById(int taskId) return _taskRepository.GetById(taskId); } - /// - /// Gets a task by its type - /// - /// Task type - /// Task public virtual ScheduleTask GetTaskByType(string type) { try @@ -80,28 +75,63 @@ public virtual ScheduleTask GetTaskByType(string type) return null; } - /// - /// Gets all tasks - /// - /// A value indicating whether to show hidden records - /// Tasks - public virtual IList GetAllTasks(bool showHidden = false) + public virtual IList GetAllTasks(bool includeDisabled = false) { var query = _taskRepository.Table; - if (!showHidden) + if (!includeDisabled) { query = query.Where(t => t.Enabled); } - query = query.OrderBy(t => t.Seconds); + query = query.OrderByDescending(t => t.Enabled); var tasks = query.ToList(); return tasks; } - /// - /// Inserts a task - /// - /// Task + public virtual IList GetPendingTasks() + { + var now = DateTime.UtcNow; + + var query = from t in _taskRepository.Table + where t.NextRunUtc.HasValue && t.NextRunUtc <= now && t.Enabled + orderby t.NextRunUtc + select t; + + return query.ToList(); + } + + public virtual bool HasRunningTasks() + { + var query = GetRunningTasksQuery(); + return query.Any(); + } + + public virtual bool IsTaskRunning(int taskId) + { + if (taskId <= 0) + return false; + + var query = GetRunningTasksQuery(); + query.Where(t => t.Id == taskId); + return query.Any(); + } + + public virtual IList GetRunningTasks() + { + return GetRunningTasksQuery().ToList(); + } + + private IQueryable GetRunningTasksQuery() + { + var query = from t in _taskRepository.Table + where t.LastStartUtc.HasValue && t.LastStartUtc.Value > (t.LastEndUtc ?? DateTime.MinValue) + orderby t.LastStartUtc + select t; + + return query; + } + + public virtual void InsertTask(ScheduleTask task) { if (task == null) @@ -110,46 +140,114 @@ public virtual void InsertTask(ScheduleTask task) _taskRepository.Insert(task); } - /// - /// Updates the task - /// - /// Task public virtual void UpdateTask(ScheduleTask task) { if (task == null) throw new ArgumentNullException("task"); - _taskRepository.Update(task); + bool saveFailed; + bool? autoCommit = null; + + do + { + saveFailed = false; + + // ALWAYS save immediately + try + { + autoCommit = _taskRepository.AutoCommitEnabled; + _taskRepository.AutoCommitEnabled = true; + _taskRepository.Update(task); + } + catch (DbUpdateConcurrencyException ex) + { + saveFailed = true; + + var entry = ex.Entries.Single(); + var current = (ScheduleTask)entry.CurrentValues.ToObject(); // from current scope + + // When 'StopOnError' is true, the 'Enabled' property could have been be set to true on exception. + var enabledModified = entry.Property("Enabled").IsModified; + + // Save current cron expression + var cronExpression = task.CronExpression; + + // Fetch Name, CronExpression, Enabled & StopOnError from database + // (these were possibly edited thru the backend) + _taskRepository.Context.ReloadEntity(task); + + // Do we have to reschedule the task? + var cronModified = cronExpression != task.CronExpression; + + // Copy execution specific data from current to reloaded entity + task.LastEndUtc = current.LastEndUtc; + task.LastError = current.LastError; + task.LastStartUtc = current.LastStartUtc; + task.LastSuccessUtc = current.LastSuccessUtc; + task.ProgressMessage = current.ProgressMessage; + task.ProgressPercent = current.ProgressPercent; + task.NextRunUtc = current.NextRunUtc; + if (enabledModified) + { + task.Enabled = current.Enabled; + } + if (task.NextRunUtc.HasValue && cronModified) + { + // reschedule task + task.NextRunUtc = GetNextSchedule(task); + } + } + finally + { + _taskRepository.AutoCommitEnabled = autoCommit; + } + } while (saveFailed); } - /// - /// Ensures that a task is not marked as running - /// - /// Task type - public virtual void EnsureTaskIsNotRunning(string type) + public void CalculateFutureSchedules(IEnumerable tasks, bool isAppStart = false) { - var task = GetTaskByType(type); - if (task != null && task.IsRunning) + Guard.ArgumentNotNull(() => tasks); + + using (var scope = new DbContextScope(autoCommit: false)) { - task.LastEndUtc = task.LastStartUtc; - UpdateTask(task); + var now = DateTime.UtcNow; + foreach (var task in tasks) + { + task.NextRunUtc = GetNextSchedule(task); + if (isAppStart) + { + task.ProgressPercent = null; + task.ProgressMessage = null; + if (task.LastEndUtc.GetValueOrDefault() < task.LastStartUtc) + { + task.LastEndUtc = task.LastStartUtc; + task.LastError = T("Admin.System.ScheduleTasks.AbnormalAbort"); + } + } + this.UpdateTask(task); + } + + scope.Commit(); } } - /// - /// Ensures that a task is not marked as running - /// - /// Task identifier - public virtual void EnsureTaskIsNotRunning(int taskId) + public virtual DateTime? GetNextSchedule(ScheduleTask task) { - var task = GetTaskById(taskId); - if (task != null && task.IsRunning) + if (task.Enabled) { - task.LastEndUtc = task.LastStartUtc; - UpdateTask(task); + try + { + var baseTime = DateTime.UtcNow; + var next = CronExpression.GetNextSchedule(task.CronExpression, baseTime); + return next; + } + catch { } } + + return null; } #endregion - } + + } } diff --git a/src/Libraries/SmartStore.Services/Tasks/TaskExecutionContext.cs b/src/Libraries/SmartStore.Services/Tasks/TaskExecutionContext.cs index 074a7e619a..9b0f1aaad5 100644 --- a/src/Libraries/SmartStore.Services/Tasks/TaskExecutionContext.cs +++ b/src/Libraries/SmartStore.Services/Tasks/TaskExecutionContext.cs @@ -3,6 +3,9 @@ using System.Linq; using System.Threading; using Autofac; +using SmartStore.Core.Async; +using SmartStore.Core.Data; +using SmartStore.Core.Domain.Tasks; namespace SmartStore.Services.Tasks { @@ -11,16 +14,91 @@ namespace SmartStore.Services.Tasks /// public class TaskExecutionContext { + private readonly IComponentContext _componentContext; + private readonly ScheduleTask _originalTask; + + internal TaskExecutionContext(IComponentContext componentContext, ScheduleTask originalTask) + { + this._componentContext = componentContext; + this._originalTask = originalTask; + this.Parameters = new Dictionary(); + } + + public T Resolve(object key = null) where T : class + { + if (key == null) + { + return _componentContext.Resolve(); + } + return _componentContext.ResolveKeyed(key); + } + + public T ResolveNamed(string name) where T : class + { + return _componentContext.ResolveNamed(name); + } + + /// + /// A cancellation token for the running task. + /// You can use ThrowIfCancellationRequested() for a hard or IsCancellationRequested for a soft break. + /// + public CancellationToken CancellationToken { get; internal set; } + + public ScheduleTask ScheduleTask { get; set; } + + public IDictionary Parameters { get; set; } + /// - /// The shared instance created - /// before the execution of the task's background thread. + /// Persists a task's progress information to the database /// - public object LifetimeScope { get; internal set; } + /// Progress value (numerator) + /// Progress maximum (denominator) + /// Progress message. Can be null. + /// if true, saves the updated task entity immediately, or lazily with the next database commit otherwise. + public void SetProgress(int value, int maximum, string message, bool immediately = false) + { + if (value == 0 && maximum == 0) + { + SetProgress(null, message, immediately); + } + else + { + float fraction = (float)value / (float)Math.Max(maximum, 1f); + int percentage = (int)Math.Round(fraction * 100f, 0); + + SetProgress(Math.Min(Math.Max(percentage, 0), 100), message, immediately); + } + } /// - /// A cancellation token for the running task. - /// You can use ThrowIfCancellationRequested() for a hard or IsCancellationRequested for a soft break. + /// Persists a task's progress information to the database /// - public CancellationToken CancellationToken { get; internal set; } + /// Percentual progress. Can be null or a value between 0 and 100. + /// Progress message. Can be null. + /// if true, saves the updated task entity immediately, or lazily with the next database commit otherwise. + public void SetProgress(int? progress, string message, bool immediately = false) + { + if (progress.HasValue) + Guard.ArgumentInRange(progress.Value, 0, 100, "progress"); + + // update cloned entity + ScheduleTask.ProgressPercent = progress; + ScheduleTask.ProgressMessage = message; + + // update attached entity + _originalTask.ProgressPercent = progress; + _originalTask.ProgressMessage = message; + + if (immediately) + { + try // dont't let this abort the task on failure + { + var dbContext = _componentContext.Resolve(); + dbContext.ChangeState(_originalTask, System.Data.Entity.EntityState.Modified); + dbContext.SaveChanges(); + } + catch { } + } + } } } diff --git a/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs b/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs new file mode 100644 index 0000000000..8fd93c2956 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs @@ -0,0 +1,190 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; +using Autofac; +using SmartStore.Core; +using SmartStore.Core.Async; +using SmartStore.Core.Data; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Tasks; +using SmartStore.Core.Localization; +using SmartStore.Core.Logging; +using SmartStore.Core.Plugins; +using SmartStore.Services.Customers; + +namespace SmartStore.Services.Tasks +{ + + public class TaskExecutor : ITaskExecutor + { + private readonly IScheduleTaskService _scheduledTaskService; + private readonly IDbContext _dbContext; + private readonly ICustomerService _customerService; + private readonly IWorkContext _workContext; + private readonly Func _taskResolver; + private readonly IComponentContext _componentContext; + + public const string CurrentCustomerIdParamName = "CurrentCustomerId"; + + public TaskExecutor( + IScheduleTaskService scheduledTaskService, + IDbContext dbContext, + ICustomerService customerService, + IWorkContext workContext, + IComponentContext componentContext, + Func taskResolver) + { + this._scheduledTaskService = scheduledTaskService; + this._dbContext = dbContext; + this._customerService = customerService; + this._workContext = workContext; + this._componentContext = componentContext; + this._taskResolver = taskResolver; + + Logger = NullLogger.Instance; + T = NullLocalizer.Instance; + } + + public ILogger Logger { get; set; } + public Localizer T { get; set; } + + public void Execute( + ScheduleTask task, + IDictionary taskParameters = null, + bool throwOnError = false) + { + if (task.IsRunning) + return; + + if (AsyncRunner.AppShutdownCancellationToken.IsCancellationRequested) + return; + + bool faulted = false; + bool canceled = false; + string lastError = null; + ITask instance = null; + string stateName = null; + + Type taskType = null; + + try + { + taskType = Type.GetType(task.Type); + + Debug.WriteLineIf(taskType == null, "Invalid task type: " + task.Type.NaIfEmpty()); + + if (taskType == null) + return; + + if (!PluginManager.IsActivePluginAssembly(taskType.Assembly)) + return; + } + catch + { + return; + } + + try + { + Customer customer = null; + + // try virtualize current customer (which is necessary when user manually executes a task) + if (taskParameters != null && taskParameters.ContainsKey(CurrentCustomerIdParamName)) + { + customer = _customerService.GetCustomerById(taskParameters[CurrentCustomerIdParamName].ToInt()); + } + + if (customer == null) + { + // no virtualization: set background task system customer as current customer + customer = _customerService.GetCustomerBySystemName(SystemCustomerNames.BackgroundTask); + } + + _workContext.CurrentCustomer = customer; + + // create task instance + instance = _taskResolver(taskType); + stateName = task.Id.ToString(); + + // prepare and save entity + task.LastStartUtc = DateTime.UtcNow; + task.LastEndUtc = null; + task.NextRunUtc = null; + task.ProgressPercent = null; + task.ProgressMessage = null; + + _scheduledTaskService.UpdateTask(task); + + // create & set a composite CancellationTokenSource which also contains the global app shoutdown token + var cts = CancellationTokenSource.CreateLinkedTokenSource(AsyncRunner.AppShutdownCancellationToken, new CancellationTokenSource().Token); + AsyncState.Current.SetCancelTokenSource(cts, stateName); + + var ctx = new TaskExecutionContext(_componentContext, task) + { + ScheduleTask = task.Clone(), + CancellationToken = cts.Token, + Parameters = taskParameters ?? new Dictionary() + }; + + instance.Execute(ctx); + } + catch (Exception exception) + { + faulted = true; + canceled = exception is OperationCanceledException; + lastError = exception.Message.Truncate(995, "..."); + + if (canceled) + Logger.Warning(T("Admin.System.ScheduleTasks.Cancellation", task.Name), exception); + else + Logger.Error(string.Concat(T("Admin.System.ScheduleTasks.RunningError", task.Name), ": ", exception.Message), exception); + + if (throwOnError) + { + throw; + } + } + finally + { + // remove from AsyncState + if (stateName.HasValue()) + { + AsyncState.Current.Remove(stateName); + } + + task.ProgressPercent = null; + task.ProgressMessage = null; + + var now = DateTime.UtcNow; + task.LastError = lastError; + task.LastEndUtc = now; + + if (faulted) + { + if ((!canceled && task.StopOnError) || instance == null) + { + task.Enabled = false; + } + } + else + { + task.LastSuccessUtc = now; + } + + if (task.Enabled) + { + task.NextRunUtc = _scheduledTaskService.GetNextSchedule(task); + } + + _scheduledTaskService.UpdateTask(task); + } + } + + private CancellationTokenSource CreateCompositeCancellationTokenSource(CancellationToken userCancellationToken) + { + return CancellationTokenSource.CreateLinkedTokenSource(AsyncRunner.AppShutdownCancellationToken, userCancellationToken); + } + } + +} diff --git a/src/Libraries/SmartStore.Services/Tasks/TaskManager.cs b/src/Libraries/SmartStore.Services/Tasks/TaskManager.cs deleted file mode 100644 index 6a8dc82be3..0000000000 --- a/src/Libraries/SmartStore.Services/Tasks/TaskManager.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using SmartStore.Core.Events; -using SmartStore.Core.Infrastructure; -using SmartStore.Core.Plugins; - -namespace SmartStore.Services.Tasks -{ - /// - /// Represents task manager - /// - public partial class TaskManager - { - private static readonly TaskManager _taskManager = new TaskManager(); - private readonly List _taskThreads = new List(); - - private TaskManager() - { - } - - /// - /// Initializes the task manager with the property values specified in the configuration file. - /// - public void Initialize() - { - this._taskThreads.Clear(); - - var taskService = EngineContext.Current.Resolve(); - var scheduleTasks = taskService.GetAllTasks(); - - var eventPublisher = EngineContext.Current.Resolve(); - eventPublisher.Publish(new AppInitScheduledTasksEvent { - ScheduledTasks = scheduleTasks - }); - - // group by threads with the same seconds - foreach (var scheduleTaskGrouped in scheduleTasks.GroupBy(x => x.Seconds)) - { - // create a thread - var taskThread = new TaskThread(); - taskThread.Seconds = scheduleTaskGrouped.Key; - - foreach (var scheduleTask in scheduleTaskGrouped) - { - var taskType = System.Type.GetType(scheduleTask.Type); - if (taskType != null) - { - var isActiveModule = PluginManager.IsActivePluginAssembly(taskType.Assembly); - if (isActiveModule) - { - var job = new Job(scheduleTask); - taskThread.AddJob(job); - } - } - } - - if (taskThread.HasJobs) - { - this._taskThreads.Add(taskThread); - } - } - - - //one thread, one task - //foreach (var scheduleTask in scheduleTasks) - //{ - // var taskThread = new TaskThread(scheduleTask); - // this._taskThreads.Add(taskThread); - // var task = new Task(scheduleTask); - // taskThread.AddTask(task); - //} - } - - /// - /// Starts the task manager - /// - public void Start() - { - foreach (var taskThread in this._taskThreads) - { - taskThread.InitTimer(); - } - } - - /// - /// Stops the task manager - /// - public void Stop() - { - foreach (var taskThread in this._taskThreads) - { - taskThread.Dispose(); - } - } - - /// - /// Gets the task mamanger instance - /// - public static TaskManager Instance - { - get - { - return _taskManager; - } - } - - /// - /// Gets a list of task threads of this task manager - /// - public IList TaskThreads - { - get - { - return new ReadOnlyCollection(this._taskThreads); - } - } - } -} diff --git a/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs b/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs new file mode 100644 index 0000000000..0e055029a3 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs @@ -0,0 +1,263 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Hosting; +using SmartStore.Core.Async; +using SmartStore.Core.Domain.Tasks; +using SmartStore.Core.Logging; +using SmartStore.Collections; +using SmartStore.Core.Infrastructure; +using SmartStore.Core.Caching; +using SmartStore.Core; + +namespace SmartStore.Services.Tasks +{ + public class DefaultTaskScheduler : DisposableObject, ITaskScheduler, IRegisteredObject + { + private bool _intervalFixed; + private int _sweepInterval; + private string _baseUrl; + private System.Timers.Timer _timer; + private bool _shuttingDown; + private int _errCount; + + public DefaultTaskScheduler() + { + _sweepInterval = 1; + _timer = new System.Timers.Timer(); + _timer.Elapsed += Elapsed; + + HostingEnvironment.RegisterObject(this); + } + + public int SweepIntervalMinutes + { + get { return _sweepInterval; } + set { _sweepInterval = value; } + } + + public string BaseUrl + { + // TODO: HTTPS? + get { return _baseUrl; } + set + { + CheckUrl(value); + _baseUrl = value.TrimEnd('/', '\\'); + } + } + + public void Start() + { + if (_timer.Enabled) + return; + + lock (_timer) + { + CheckUrl(_baseUrl); + _timer.Interval = GetFixedInterval(); + _timer.Start(); + } + } + + private double GetFixedInterval() + { + // Gets seconds to next sweep minute + int seconds = (_sweepInterval * 60) - DateTime.Now.Second; + return seconds * 1000; + } + + public void Stop() + { + if (!_timer.Enabled) + return; + + lock (_timer) + { + _timer.Stop(); + } + } + + public bool IsActive + { + get { return _timer.Enabled; } + } + + public CancellationTokenSource GetCancelTokenSourceFor(int scheduleTaskId) + { + var cts = AsyncState.Current.GetCancelTokenSource(scheduleTaskId.ToString()); + return cts; + } + + private string CreateAuthToken() + { + string authToken = Guid.NewGuid().ToString(); + + var cacheManager = EngineContext.Current.Resolve("static"); + cacheManager.Set(GenerateAuthTokenCacheKey(authToken), true, 1); + + return authToken; + } + + private string GenerateAuthTokenCacheKey(string authToken) + { + return "Scheduler.AuthToken." + authToken; + } + + public bool VerifyAuthToken(string authToken) + { + if (authToken.IsEmpty()) + return false; + + var cacheManager = EngineContext.Current.Resolve("static"); + var cacheKey = GenerateAuthTokenCacheKey(authToken); + if (cacheManager.Contains(cacheKey)) + { + cacheManager.Remove(cacheKey); + return true; + } + + return false; + } + + public void RunSingleTask(int scheduleTaskId, IDictionary taskParameters = null) + { + string query = ""; + + if (taskParameters != null && taskParameters.Any()) + { + var qs = new QueryString(); + taskParameters.Each(x => qs.Add(x.Key, x.Value)); + query = qs.ToString(); + } + + CallEndpoint(new Uri("{0}/Execute/{1}{2}".FormatInvariant(_baseUrl, scheduleTaskId, query))); + } + + private void Elapsed(object sender, System.Timers.ElapsedEventArgs e) + { + if (!System.Threading.Monitor.TryEnter(_timer)) + return; + + try + { + if (_timer.Enabled) + { + if (!_intervalFixed) + { + _timer.Interval = TimeSpan.FromMinutes(_sweepInterval).TotalMilliseconds; + _intervalFixed = true; + } + + CallEndpoint(new Uri(_baseUrl + "/Sweep")); + } + } + finally + { + System.Threading.Monitor.Exit(_timer); + } + } + + protected internal virtual void CallEndpoint(Uri uri) + { + if (_shuttingDown) + return; + + var req = WebHelper.CreateHttpRequestForSafeLocalCall(uri); + req.Method = "POST"; + req.ContentType = "text/plain"; + req.ContentLength = 0; + req.Timeout = 10000; // 10 sec. + + string authToken = CreateAuthToken(); + req.Headers.Add("X-AUTH-TOKEN", authToken); + + req.GetResponseAsync().ContinueWith(t => + { + if (t.IsFaulted) + { + HandleException(t.Exception, uri); + _errCount++; + if (_errCount >= 10) + { + // 10 failed attempts in succession. Stop the timer! + this.Stop(); + using (var logger = new TraceLogger()) + { + logger.Information("Stopping TaskScheduler sweep timer. Too many failed requests in succession."); + } + } + } + else + { + _errCount = 0; + var response = t.Result; + + //using (var logger = new TraceLogger()) + //{ + // logger.Debug("TaskScheduler Sweep called successfully: {0}".FormatCurrent(response.GetResponseStream().AsString())); + //} + + response.Dispose(); + } + }); + } + + private void HandleException(AggregateException exception, Uri uri) + { + using (var logger = new TraceLogger()) + { + string msg = "Error while calling TaskScheduler endpoint '{0}'.".FormatInvariant(uri.OriginalString); + var wex = exception.InnerExceptions.OfType().FirstOrDefault(); + + if (wex == null) + { + logger.Error(msg, exception.InnerException); + } + else if (wex.Response == null) + { + logger.Error(msg, wex); + } + else + { + using (var response = wex.Response as HttpWebResponse) + { + if (response != null) + { + msg += " HTTP {0}, {1}".FormatCurrent((int)response.StatusCode, response.StatusDescription); + } + logger.Error(msg); + } + } + } + } + + private void CheckUrl(string url) + { + if (!url.IsWebUrl()) + { + throw Error.InvalidOperation("A valid base url is required for the background task scheduler."); + } + } + + protected override void OnDispose(bool disposing) + { + if (disposing) + { + _timer.Dispose(); + } + } + + void IRegisteredObject.Stop(bool immediate) + { + _shuttingDown = true; + HostingEnvironment.UnregisterObject(this); + } + } + +} diff --git a/src/Libraries/SmartStore.Services/Tasks/TaskThread.cs b/src/Libraries/SmartStore.Services/Tasks/TaskThread.cs deleted file mode 100644 index b165daaeec..0000000000 --- a/src/Libraries/SmartStore.Services/Tasks/TaskThread.cs +++ /dev/null @@ -1,177 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using SmartStore.Core.Async; -using SmartStore.Core.Domain.Tasks; - -namespace SmartStore.Services.Tasks -{ - /// - /// Represents task thread - /// - public partial class TaskThread : IDisposable - { - private Timer _timer; - private bool _disposed; - private DateTime _startedUtc; - private bool _isRunning; - private readonly Dictionary _jobs; - private int _seconds; - - internal TaskThread() - { - this._jobs = new Dictionary(); - this._seconds = 10 * 60; - } - - internal TaskThread(ScheduleTask scheduleTask) - { - this._jobs = new Dictionary(); - this._seconds = scheduleTask.Seconds; - this._isRunning = false; - } - - private void Run() - { - if (_seconds <=0) - return; - - this._startedUtc = DateTime.UtcNow; - this._isRunning = true; - - var jobs = _jobs.Values - .Select(job => AsyncRunner.Run((c, ct) => job.Execute(ct, c))) - .ToArray(); - - try - { - Task.WaitAll(jobs); - } - catch { } - - this._isRunning = false; - } - - private void TimerHandler(object state) - { - this._timer.Change(-1, -1); - this.Run(); - this._timer.Change(this.Interval, this.Interval); - } - - /// - /// Disposes the instance - /// - public void Dispose() - { - if ((this._timer != null) && !this._disposed) - { - lock (this) - { - this._timer.Dispose(); - this._timer = null; - this._disposed = true; - } - } - } - - /// - /// Inits a timer - /// - public void InitTimer() - { - if (this._timer == null) - { - this._timer = new Timer(new TimerCallback(this.TimerHandler), null, this.Interval, this.Interval); - } - } - - /// - /// Adds a job to the thread - /// - /// The task to be added - public void AddJob(Job job) - { - if (!this._jobs.ContainsKey(job.Name)) - { - this._jobs.Add(job.Name, job); - } - } - - - /// - /// Gets or sets the interval in seconds at which to run the jobs - /// - public int Seconds - { - get - { - return this._seconds; - } - internal set - { - this._seconds = value; - } - } - - /// - /// Get a datetime when thread has been started - /// - public DateTime Started - { - get - { - return this._startedUtc; - } - } - - /// - /// Get a value indicating whether thread is running - /// - public bool IsRunning - { - get - { - return this._isRunning; - } - } - - /// - /// Get a list of jobs - /// - public IList Jobs - { - get - { - var list = new List(); - foreach (var jobs in this._jobs.Values) - { - list.Add(jobs); - } - return new ReadOnlyCollection(list); - } - } - - public bool HasJobs - { - get { return _jobs.Count > 0; } - } - - /// - /// Gets the interval at which to run the jobs - /// - public int Interval - { - get - { - if (_seconds > (Int32.MaxValue / 1000)) - return Int32.MaxValue; - - return this._seconds * 1000; - } - } - } -} diff --git a/src/Libraries/SmartStore.Services/Tax/TaxService.cs b/src/Libraries/SmartStore.Services/Tax/TaxService.cs index e07263ec49..056c456606 100644 --- a/src/Libraries/SmartStore.Services/Tax/TaxService.cs +++ b/src/Libraries/SmartStore.Services/Tax/TaxService.cs @@ -8,11 +8,9 @@ using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Tax; -using SmartStore.Core.Infrastructure; using SmartStore.Core.Plugins; using SmartStore.Services.Common; using SmartStore.Services.Directory; -using SmartStore.Services.Configuration; namespace SmartStore.Services.Tax { @@ -44,7 +42,6 @@ public TaxAddressKey(int customerId, bool productIsEsd) private readonly IPluginFinder _pluginFinder; private readonly IDictionary _cachedTaxRates; private readonly IDictionary _cachedTaxAddresses; - private readonly ISettingService _settingService; private readonly IProviderManager _providerManager; private readonly IGeoCountryLookup _geoCountryLookup; @@ -65,10 +62,8 @@ public TaxService( TaxSettings taxSettings, ShoppingCartSettings cartSettings, IPluginFinder pluginFinder, - ISettingService settingService, IGeoCountryLookup geoCountryLookup, - IProviderManager providerManager - ) + IProviderManager providerManager) { this._addressService = addressService; this._workContext = workContext; @@ -77,7 +72,6 @@ IProviderManager providerManager this._pluginFinder = pluginFinder; this._cachedTaxRates = new Dictionary(); this._cachedTaxAddresses = new Dictionary(); - this._settingService = settingService; this._providerManager = providerManager; this._geoCountryLookup = geoCountryLookup; } @@ -274,9 +268,7 @@ public virtual Provider LoadActiveTaxProvider() if (taxProvider == null) { taxProvider = LoadAllTaxProviders().FirstOrDefault(); - _taxSettings.ActiveTaxProviderSystemName = taxProvider.Metadata.SystemName; - _settingService.SaveSetting(_taxSettings); - } + } return taxProvider; } @@ -355,7 +347,12 @@ protected virtual decimal GetTaxRateCore(Product product, int taxCategoryId, Cus { return decimal.Zero; } - + + if (IsTaxExempt(product, customer)) + { + return decimal.Zero; + } + // tax request var calculateTaxRequest = CreateCalculateTaxRequest(product, taxCategoryId, customer); diff --git a/src/Libraries/SmartStore.Services/Themes/ThemeVariablesService.cs b/src/Libraries/SmartStore.Services/Themes/ThemeVariablesService.cs index 3f30e2e79d..4011840398 100644 --- a/src/Libraries/SmartStore.Services/Themes/ThemeVariablesService.cs +++ b/src/Libraries/SmartStore.Services/Themes/ThemeVariablesService.cs @@ -80,20 +80,18 @@ public virtual void DeleteThemeVariables(string themeName, int storeId) if (query.Any()) { - bool autoCommit = _rsVariables.AutoCommitEnabled; - _rsVariables.AutoCommitEnabled = false; - - query.Each(v => - { - _rsVariables.Delete(v); - _eventPublisher.EntityDeleted(v); - }); - - _cacheManager.Remove(THEMEVARS_BY_THEME_KEY.FormatInvariant(themeName, storeId)); - - _rsVariables.Context.SaveChanges(); - - _rsVariables.AutoCommitEnabled = autoCommit; + using (var scope = new DbContextScope(ctx: _rsVariables.Context, autoCommit: false)) + { + query.Each(v => + { + _rsVariables.Delete(v); + _eventPublisher.EntityDeleted(v); + }); + + _cacheManager.Remove(THEMEVARS_BY_THEME_KEY.FormatInvariant(themeName, storeId)); + + _rsVariables.Context.SaveChanges(); + } } } @@ -109,75 +107,73 @@ public virtual int SaveThemeVariables(string themeName, int storeId, IDictionary var count = 0; var infos = _themeRegistry.GetThemeManifest(themeName).Variables; - bool autoCommit = _rsVariables.AutoCommitEnabled; - _rsVariables.AutoCommitEnabled = false; - - var unsavedVars = new List(); - var savedThemeVars = _rsVariables.Table.Where(v => v.StoreId == storeId && v.Theme.Equals(themeName, StringComparison.OrdinalIgnoreCase)).ToList(); - bool touched = false; - - foreach (var v in variables) - { - ThemeVariableInfo info; - if (!infos.TryGetValue(v.Key, out info)) - { - // var not specified in metadata so don't save - // TODO: (MC) delete from db also if it exists - continue; - } - - var value = v.Value == null ? string.Empty : v.Value.ToString(); - - var savedThemeVar = savedThemeVars.FirstOrDefault(x => x.Name == v.Key); - if (savedThemeVar != null) - { - if (value.IsEmpty() || String.Equals(info.DefaultValue, value, StringComparison.CurrentCultureIgnoreCase)) - { - // it's either null or the default value, so delete - _rsVariables.Delete(savedThemeVar); - _eventPublisher.EntityDeleted(savedThemeVar); - touched = true; - count++; - } - else - { - // update entity - if (!savedThemeVar.Value.Equals(value, StringComparison.OrdinalIgnoreCase)) - { - savedThemeVar.Value = value; - _eventPublisher.EntityUpdated(savedThemeVar); - touched = true; - count++; - } - } - } - else - { - if (value.HasValue() && !String.Equals(info.DefaultValue, value, StringComparison.CurrentCultureIgnoreCase)) - { - // insert entity (only when not default value) - unsavedVars.Add(v.Key); - savedThemeVar = new ThemeVariable - { - Theme = themeName, - Name = v.Key, - Value = value, - StoreId = storeId - }; - _rsVariables.Insert(savedThemeVar); - _eventPublisher.EntityInserted(savedThemeVar); - touched = true; - count++; - } - } - } - - if (touched) - { - _rsVariables.Context.SaveChanges(); - } - - _rsVariables.AutoCommitEnabled = autoCommit; + using (var scope = new DbContextScope(ctx: _rsVariables.Context, autoCommit: false)) + { + var unsavedVars = new List(); + var savedThemeVars = _rsVariables.Table.Where(v => v.StoreId == storeId && v.Theme.Equals(themeName, StringComparison.OrdinalIgnoreCase)).ToList(); + bool touched = false; + + foreach (var v in variables) + { + ThemeVariableInfo info; + if (!infos.TryGetValue(v.Key, out info)) + { + // var not specified in metadata so don't save + // TODO: (MC) delete from db also if it exists + continue; + } + + var value = v.Value == null ? string.Empty : v.Value.ToString(); + + var savedThemeVar = savedThemeVars.FirstOrDefault(x => x.Name == v.Key); + if (savedThemeVar != null) + { + if (value.IsEmpty() || String.Equals(info.DefaultValue, value, StringComparison.CurrentCultureIgnoreCase)) + { + // it's either null or the default value, so delete + _rsVariables.Delete(savedThemeVar); + _eventPublisher.EntityDeleted(savedThemeVar); + touched = true; + count++; + } + else + { + // update entity + if (!savedThemeVar.Value.Equals(value, StringComparison.OrdinalIgnoreCase)) + { + savedThemeVar.Value = value; + _eventPublisher.EntityUpdated(savedThemeVar); + touched = true; + count++; + } + } + } + else + { + if (value.HasValue() && !String.Equals(info.DefaultValue, value, StringComparison.CurrentCultureIgnoreCase)) + { + // insert entity (only when not default value) + unsavedVars.Add(v.Key); + savedThemeVar = new ThemeVariable + { + Theme = themeName, + Name = v.Key, + Value = value, + StoreId = storeId + }; + _rsVariables.Insert(savedThemeVar); + _eventPublisher.EntityInserted(savedThemeVar); + touched = true; + count++; + } + } + } + + if (touched) + { + _rsVariables.Context.SaveChanges(); + } + } return count; } diff --git a/src/Libraries/SmartStore.Services/Topics/TopicService.cs b/src/Libraries/SmartStore.Services/Topics/TopicService.cs index daaa424a3c..d9ffb1a6af 100644 --- a/src/Libraries/SmartStore.Services/Topics/TopicService.cs +++ b/src/Libraries/SmartStore.Services/Topics/TopicService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using SmartStore.Core.Caching; using SmartStore.Core.Data; using SmartStore.Core.Domain.Stores; using SmartStore.Core.Domain.Topics; @@ -13,23 +14,29 @@ namespace SmartStore.Services.Topics /// public partial class TopicService : ITopicService { - #region Fields + private const string TOPICS_ALL_KEY = "SmartStore.topics.all-{0}"; + private const string TOPICS_PATTERN_KEY = "SmartStore.topics."; - private readonly IRepository _topicRepository; + #region Fields + + private readonly IRepository _topicRepository; private readonly IRepository _storeMappingRepository; private readonly IEventPublisher _eventPublisher; + private readonly ICacheManager _cacheManager; - #endregion + #endregion - #region Ctor + #region Ctor - public TopicService(IRepository topicRepository, + public TopicService(IRepository topicRepository, IRepository storeMappingRepository, - IEventPublisher eventPublisher) + IEventPublisher eventPublisher, + ICacheManager cacheManager) { _topicRepository = topicRepository; _storeMappingRepository = storeMappingRepository; _eventPublisher = eventPublisher; + _cacheManager = cacheManager; this.QuerySettings = DbQuerySettings.Default; } @@ -51,8 +58,10 @@ public virtual void DeleteTopic(Topic topic) _topicRepository.Delete(topic); - //event notification - _eventPublisher.EntityDeleted(topic); + _cacheManager.RemoveByPattern(TOPICS_PATTERN_KEY); + + //event notification + _eventPublisher.EntityDeleted(topic); } /// @@ -79,29 +88,13 @@ public virtual Topic GetTopicBySystemName(string systemName, int storeId) if (String.IsNullOrEmpty(systemName)) return null; - var query = _topicRepository.Table; - query = query.Where(t => t.SystemName == systemName); - query = query.OrderBy(t => t.Id); + var allTopics = GetAllTopics(storeId); - //Store mapping - if (storeId > 0 && !QuerySettings.IgnoreMultiStore) - { - query = from t in query - join sm in _storeMappingRepository.Table - on new { c1 = t.Id, c2 = "Topic" } equals new { c1 = sm.EntityId, c2 = sm.EntityName } into t_sm - from sm in t_sm.DefaultIfEmpty() - where !t.LimitedToStores || storeId == sm.StoreId - select t; - - //only distinct items (group by ID) - query = from t in query - group t by t.Id into tGroup - orderby tGroup.Key - select tGroup.FirstOrDefault(); - query = query.OrderBy(t => t.Id); - } - - return query.FirstOrDefault(); + var topic = allTopics + .OrderBy(x => x.Id) + .FirstOrDefault(x => x.SystemName.IsCaseInsensitiveEqual(systemName)); + + return topic; } /// @@ -111,28 +104,33 @@ orderby tGroup.Key /// Topics public virtual IList GetAllTopics(int storeId) { - var query = _topicRepository.Table; - query = query.OrderBy(t => t.Priority).ThenBy(t => t.SystemName); - - //Store mapping - if (storeId > 0 && !QuerySettings.IgnoreMultiStore) + var result = _cacheManager.Get(TOPICS_ALL_KEY.FormatInvariant(storeId), () => { - query = from t in query - join sm in _storeMappingRepository.Table - on new { c1 = t.Id, c2 = "Topic" } equals new { c1 = sm.EntityId, c2 = sm.EntityName } into t_sm - from sm in t_sm.DefaultIfEmpty() - where !t.LimitedToStores || storeId == sm.StoreId - select t; - - //only distinct items (group by ID) - query = from t in query - group t by t.Id into tGroup - orderby tGroup.Key - select tGroup.FirstOrDefault(); - query = query.OrderBy(t => t.SystemName); - } - - return query.ToList(); + var query = _topicRepository.Table; + + //Store mapping + if (storeId > 0 && !QuerySettings.IgnoreMultiStore) + { + query = from t in query + join sm in _storeMappingRepository.Table + on new { c1 = t.Id, c2 = "Topic" } equals new { c1 = sm.EntityId, c2 = sm.EntityName } into t_sm + from sm in t_sm.DefaultIfEmpty() + where !t.LimitedToStores || storeId == sm.StoreId + select t; + + //only distinct items (group by ID) + query = from t in query + group t by t.Id into tGroup + orderby tGroup.Key + select tGroup.FirstOrDefault(); + } + + query = query.OrderBy(t => t.Priority).ThenBy(t => t.SystemName); + + return query.ToList(); + }); + + return result; } /// @@ -146,8 +144,10 @@ public virtual void InsertTopic(Topic topic) _topicRepository.Insert(topic); - //event notification - _eventPublisher.EntityInserted(topic); + _cacheManager.RemoveByPattern(TOPICS_PATTERN_KEY); + + //event notification + _eventPublisher.EntityInserted(topic); } /// @@ -161,8 +161,10 @@ public virtual void UpdateTopic(Topic topic) _topicRepository.Update(topic); - //event notification - _eventPublisher.EntityUpdated(topic); + _cacheManager.RemoveByPattern(TOPICS_PATTERN_KEY); + + //event notification + _eventPublisher.EntityUpdated(topic); } #endregion diff --git a/src/Libraries/SmartStore.Services/app.config b/src/Libraries/SmartStore.Services/app.config index a376174d13..a9250b2fff 100644 --- a/src/Libraries/SmartStore.Services/app.config +++ b/src/Libraries/SmartStore.Services/app.config @@ -29,7 +29,7 @@ - + diff --git a/src/Libraries/SmartStore.Services/packages.config b/src/Libraries/SmartStore.Services/packages.config index 1d9b39f80e..d078b22719 100644 --- a/src/Libraries/SmartStore.Services/packages.config +++ b/src/Libraries/SmartStore.Services/packages.config @@ -1,18 +1,20 @@  - - + + + - - - + + + - + + - + \ No newline at end of file diff --git a/src/Plugins/SmartStore.AmazonPay/Description.txt b/src/Plugins/SmartStore.AmazonPay/Description.txt index bebde13958..8237c4a7ad 100644 --- a/src/Plugins/SmartStore.AmazonPay/Description.txt +++ b/src/Plugins/SmartStore.AmazonPay/Description.txt @@ -1,10 +1,10 @@ FriendlyName: Pay with Amazon SystemName: SmartStore.AmazonPay -Version: 2.2.0.1 +Version: 2.6.0 Group: Payment -MinAppVersion: 2.2.0 +MinAppVersion: 2.5.0 Author: SmartStore AG DisplayOrder: 1 FileName: SmartStore.AmazonPay.dll ResourceRootKey: Plugins.Payments.AmazonPay -Url: http://community.smartstore.com/index.php?/files/file/11-amazon-payments-plugin/ \ No newline at end of file +Url: http://community.smartstore.com/marketplace/file/11-amazon-payments-plugin/ \ No newline at end of file diff --git a/src/Plugins/SmartStore.AmazonPay/Events/MessageTokenEventConsumer.cs b/src/Plugins/SmartStore.AmazonPay/Events/MessageTokenEventConsumer.cs new file mode 100644 index 0000000000..78f10c09e2 --- /dev/null +++ b/src/Plugins/SmartStore.AmazonPay/Events/MessageTokenEventConsumer.cs @@ -0,0 +1,47 @@ +using System.Linq; +using SmartStore.AmazonPay.Services; +using SmartStore.Core.Domain.Messages; +using SmartStore.Core.Events; +using SmartStore.Core.Plugins; +using SmartStore.Services; +using SmartStore.Services.Messages; +using SmartStore.Services.Orders; +using SmartStore.Web.Framework; + +namespace SmartStore.AmazonPay.Events +{ + public class MessageTokenEventConsumer : IConsumer> + { + private readonly IPluginFinder _pluginFinder; + private readonly ICommonServices _services; + private readonly IOrderService _orderService; + + public MessageTokenEventConsumer( + IPluginFinder pluginFinder, + ICommonServices services, + IOrderService orderService) + { + _pluginFinder = pluginFinder; + _services = services; + _orderService = orderService; + } + + public void HandleEvent(MessageTokensAddedEvent messageTokenEvent) + { + if (!messageTokenEvent.Message.Name.IsCaseInsensitiveEqual("OrderPlaced.CustomerNotification")) + return; + + var storeId = _services.StoreContext.CurrentStore.Id; + + if (!_pluginFinder.IsPluginReady(_services.Settings, AmazonPayCore.SystemName, storeId)) + return; + + var order = _orderService.SearchOrders(storeId, _services.WorkContext.CurrentCustomer.Id, null, null, null, null, null, null, null, null, 0, 1).FirstOrDefault(); + + var isAmazonPayment = (order != null && order.PaymentMethodSystemName.IsCaseInsensitiveEqual(AmazonPayCore.SystemName)); + var tokenValue = (isAmazonPayment ? _services.Localization.GetResource("Plugins.Payments.AmazonPay.BillingAddressMessageNote") : ""); + + messageTokenEvent.Tokens.Add(new Token("SmartStore.AmazonPay.BillingAddressMessageNote", tokenValue)); + } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.AmazonPay/Localization/resources.de-de.xml b/src/Plugins/SmartStore.AmazonPay/Localization/resources.de-de.xml index 87a9e2e3a0..fbe79d7119 100644 --- a/src/Plugins/SmartStore.AmazonPay/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.AmazonPay/Localization/resources.de-de.xml @@ -8,15 +8,14 @@ - Registrieren Sie sich bei Amazon Payments an.

+ Registrieren Sie sich zunächst bei Amazon Payments.

So richten Sie "Bezahlen mit Amazon" ein:

  • Tragen Sie Ihre Amazon-Zugangsdaten unten in die dafür vorgesehenen Felder ein. Sie finden diese Daten in Ihrem Amazon Seller Central Konto.
  • -
  • Ihre Händlernummer finden Sie dort rechts oben unter Einstellungen - Integrationseinstellungen.
  • -
  • Die beiden Zugangsschlüssel finden Sie dort links oben unter Integration - MWS Access Key. In diesem Dokument finden Sie Bilder, wie Sie diese erstellen.
  • -
  • Falls Sie Sofortbenachrichtigungen (IPN) erhalten möchten (SSL zwingend erforderlich!), so tragen Sie die unten aufgeführte IPN URL unter Einstellungen - Integrationseinstellungen - Sofortbenachrichtigungs-Einstellungen - Händler-URL ein.
  • +
  • Ihre Händlernummer finden Sie dort rechts oben unter Einstellungen > Integrationseinstellungen.
  • +
  • Die beiden Zugangsschlüssel finden Sie dort links oben unter Integration > MWS Access Key. In diesem Dokument finden Sie Bilder, wie Sie diese erstellen.
  • +
  • Falls Sie Sofortbenachrichtigungen (IPN) erhalten möchten (SSL zwingend erforderlich!), so tragen Sie die unten aufgeführte IPN URL unter Einstellungen > Integrationseinstellungen > Sofortbenachrichtigungs-Einstellungen > Händler-URL ein.
-

Hinweis! Die Rechnungsadresse des Kunden erhalten Sie erst nach Bestätigung der Zahlungsautorisierung von Amazon, also i.d.R. erst kurz nach Versand der Bestellbestätigungs-Email. Um Missverständnisse zu vermeiden ist es ratsam, den Platzhalter für die Rechnungsadresse aus der Nachrichtenvorlage für die Bestellbestätigung zu entfernen.

-

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

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

  • Option 1: Bezahlen mit Amazon: Zahlen Sie jetzt mit den Zahl- und Lieferinformationen aus Ihrem Amazon-Konto.
  • Option 2: Sie sind Amazon-Kunde? Zahlen Sie jetzt mit den Zahl- und Lieferinformationen aus Ihrem Amazon-Konto.
  • @@ -24,17 +23,20 @@ Textvorschläge:

    ]]> + + Bitte beachten Sie, dass es sich bei dieser Rechnungsadresse unter Umständen nicht um die für diese Bestellung gültige handelt! + Es wurde keine Auftrags-Referenz-ID durch Amazon übermittelt! - Zahlungsmethode "Bezahlen mit Amazon" ist für Shop "{0}" nicht verfügbar. + Die Zahlungsart "Bezahlen mit Amazon" ist für Shop "{0}" nicht verfügbar. Fehlender Checkout-Sitzungsstatus für "Bezahlen mit Amazon". Ihre Zahlung kann leider nicht bearbeitet werden. Bitte gehen Sie zurück in den Warenkorb und Durchlaufen Sie den Checkout erneut. - Ein Auftrag mit der Zahlungsmethode "Bezahlen mit Amazon" und der Kennung {0} wurde nicht gefunden. + Ein Auftrag mit der Zahlungsart "Bezahlen mit Amazon" und der Kennung {0} wurde nicht gefunden. @@ -177,7 +179,7 @@ Textvorschläge:
      Button im Miniwarenkorb anzeigen - Legt fest, dass der "Bezahlen mit Amazon" Button auch im Miniwarenkorb angezeigt werden soll. + Legt fest, ob der "Bezahlen mit Amazon" Button auch im Miniwarenkorb angezeigt werden soll. Breite des Adressen-Widgets diff --git a/src/Plugins/SmartStore.AmazonPay/Localization/resources.en-us.xml b/src/Plugins/SmartStore.AmazonPay/Localization/resources.en-us.xml index 0505e89e6b..d5163d3ef3 100644 --- a/src/Plugins/SmartStore.AmazonPay/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.AmazonPay/Localization/resources.en-us.xml @@ -8,15 +8,14 @@ - Register now at Amazon Payments.

      + Register now at Amazon Payments.

      How to set up "Pay with Amazon":

      • Enter your Amazon credentials in the fields provided below. You can find these credentials in your Amazon Seller Central account.
      • -
      • You can find the Merchant ID in Seller Central at Settings - Integration Settings.
      • -
      • You can find both access keys in Seller Central at Integration - MWS Access Key.
      • -
      • If you would like to receive instant payment notifications (SSL required!) enter the IPN URL listed bewlow under Settings - Integration Settings - Instant Notification Settings - Merchant URL.
      • +
      • You can find the Merchant ID in Seller Central at Settings > Integration Settings.
      • +
      • You can find both access keys in Seller Central at Integration > MWS Access Key.
      • +
      • If you would like to receive instant payment notifications (SSL required!) enter the IPN URL listed bewlow under Settings > Integration Settings > Instant Notification Settings > Merchant URL.
      -

      Note! You get the billing address of the customer after the payment has been authorised by Amazon, so usually shortly after the order confirmation E-mail has been sent to the customer. To avoid misunderstandings, it is therefore advisable to remove the placeholder for the billing address from the message template for order confirmations.

      -

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

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

      • Option 1: Pay with Amazon: Pay now with the payment and shipping information from your Amazon account.
      • Option 2: Already Amazon customer? Pay now with the payment and shipping information from your Amazon account.
      • @@ -24,6 +23,9 @@ Text suggestions:

        ]]> + + Please note that this billing address is possibly not the valid billing address for this order! + There was no order reference ID transmitted by Amazon! @@ -66,7 +68,7 @@ Text suggestions:
          Use Sandbox - Check to use the sandbox (testing environment). + Check the box to use the sandbox (testing environment). Your Merchant ID @@ -129,7 +131,7 @@ Text suggestions:
            Updating the payment status - Determines the method used to update the payment status. + Specifies the method used to update the payment status. IPN (instant payment notification) requires valid SSL certificate to be installed on this server. Pay attention that the SSL certificate must be issued by a trusted Certificate Authority, self-signed certificates are not permittted. @@ -150,7 +152,7 @@ Text suggestions:
              Payment action - Determines when to debit the customer account. + Specifies when to debit the customer account. Immediately debit @@ -165,7 +167,7 @@ Text suggestions:
                Apply customer data - Determines whether and when the Amazon email address and telephone number of the customer should be saved. + Specifies whether and when the Amazon email address and telephone number of the customer should be saved. Only if empty @@ -177,7 +179,7 @@ Text suggestions:
                  Show button in mini shopping cart - Determines to show the "Pay with Amazon" button in the mini shopping cart too. + Specifies to show the "Pay with Amazon" button in the mini shopping cart too. Width of address widget @@ -216,36 +218,36 @@ Text suggestions:
                    Additional fee percentage - Determines whether to apply a percentage additional fee to the order total. If not enabled, a fixed value is used. + Specifies whether to apply a percentage additional fee to the order total. If not enabled, a fixed value is used. Create order notes - Determines that order notes should be created in context of the data exchange with Amazon Payments. + Specifies that order notes should be created in context of the data exchange with Amazon Payments. Frequency (in minutes) - Determines how often status of the different object shall be polled from Amazon Payments servers. + Specifies how often status of the different object shall be polled from Amazon Payments servers. Maximal order age (in days) - Determines that only orders which are not older than x days to be included in payment data updates. + Specifies that only orders which are not older than x days to be included in payment data updates. Inform about a refusal of an authorization - Determines to create order notes in case of a declination of an Amazon payment, which are visible for customers too. In addition the customer is informed by email about the case. + Specifies to create order notes in case of a declination of an Amazon payment, which are visible for customers too. In addition the customer is informed by email about the case. Append error message - Determines to append the error message to the order note and email. + Specifies to append the error message to the order note and email. \ No newline at end of file diff --git a/src/Plugins/SmartStore.AmazonPay/Models/AmazonPayViewModel.cs b/src/Plugins/SmartStore.AmazonPay/Models/AmazonPayViewModel.cs index 8fce222739..066e9dd9db 100644 --- a/src/Plugins/SmartStore.AmazonPay/Models/AmazonPayViewModel.cs +++ b/src/Plugins/SmartStore.AmazonPay/Models/AmazonPayViewModel.cs @@ -1,6 +1,6 @@ using System; using SmartStore.AmazonPay.Services; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.AmazonPay.Models { diff --git a/src/Plugins/SmartStore.AmazonPay/Models/ConfigurationModel.cs b/src/Plugins/SmartStore.AmazonPay/Models/ConfigurationModel.cs index fd1775b85a..aa40e32f0f 100644 --- a/src/Plugins/SmartStore.AmazonPay/Models/ConfigurationModel.cs +++ b/src/Plugins/SmartStore.AmazonPay/Models/ConfigurationModel.cs @@ -3,7 +3,7 @@ using SmartStore.AmazonPay.Services; using SmartStore.AmazonPay.Settings; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.AmazonPay.Models { diff --git a/src/Plugins/SmartStore.AmazonPay/RouteProvider.cs b/src/Plugins/SmartStore.AmazonPay/RouteProvider.cs index 5f5bbe5f90..a44b2d50f3 100644 --- a/src/Plugins/SmartStore.AmazonPay/RouteProvider.cs +++ b/src/Plugins/SmartStore.AmazonPay/RouteProvider.cs @@ -1,7 +1,7 @@ using System.Web.Mvc; using System.Web.Routing; using SmartStore.AmazonPay.Services; -using SmartStore.Web.Framework.Mvc.Routes; +using SmartStore.Web.Framework.Routing; namespace SmartStore.AmazonPay { diff --git a/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayApi.cs b/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayApi.cs index 64d21b0428..59e41a1e2b 100644 --- a/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayApi.cs +++ b/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayApi.cs @@ -1,26 +1,28 @@ using System; +using System.Collections.Generic; +using System.Globalization; using System.IO; -using System.Web; using System.Linq; using System.Text; -using System.Globalization; -using System.Collections.Generic; +using System.Web; using OffAmazonPaymentsService; using OffAmazonPaymentsService.Model; -using SmartStore.Utilities; -using SmartStore.AmazonPay.Services; using SmartStore.AmazonPay.Extensions; +using SmartStore.AmazonPay.Services; using SmartStore.AmazonPay.Settings; using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Directory; +using SmartStore.Core.Domain.Discounts; using SmartStore.Core.Domain.Orders; -using SmartStore.Services.Payments; -using SmartStore.Services.Directory; -using SmartStore.Services.Orders; -using SmartStore.Services.Localization; -using SmartStore.Services.Common; -using SmartStore.Services.Helpers; using SmartStore.Core.Infrastructure; using SmartStore.Core.Plugins; +using SmartStore.Services; +using SmartStore.Services.Common; +using SmartStore.Services.Directory; +using SmartStore.Services.Helpers; +using SmartStore.Services.Orders; +using SmartStore.Services.Payments; +using SmartStore.Utilities; namespace SmartStore.AmazonPay.Api { @@ -29,24 +31,30 @@ public class AmazonPayApi : IAmazonPayApi private readonly ICountryService _countryService; private readonly IStateProvinceService _stateProvinceService; private readonly IOrderService _orderService; - private readonly ILocalizationService _localizationService; private readonly IAddressService _addressService; private readonly IDateTimeHelper _dateTimeHelper; + private readonly CurrencySettings _currencySettings; + private readonly IOrderTotalCalculationService _orderTotalCalculationService; + private readonly ICommonServices _services; public AmazonPayApi( ICountryService countryService, IStateProvinceService stateProvinceService, IOrderService orderService, - ILocalizationService localizationService, IAddressService addressService, - IDateTimeHelper dateTimeHelper) + IDateTimeHelper dateTimeHelper, + CurrencySettings currencySettings, + IOrderTotalCalculationService orderTotalCalculationService, + ICommonServices services) { _countryService = countryService; _stateProvinceService = stateProvinceService; _orderService = orderService; - _localizationService = localizationService; _addressService = addressService; _dateTimeHelper = dateTimeHelper; + _currencySettings = currencySettings; + _orderTotalCalculationService = orderTotalCalculationService; + _services = services; } private string GetRandomId(string prefix) @@ -210,16 +218,16 @@ public OrderReferenceDetails SetOrderReferenceDetails(AmazonPayClient client, st if (orderTotalAmount.HasValue) { - attributes.OrderTotal = new OrderTotal() + attributes.OrderTotal = new OrderTotal { Amount = orderTotalAmount.Value.ToString("0.00", CultureInfo.InvariantCulture), - CurrencyCode = currencyCode + CurrencyCode = currencyCode ?? "EUR" }; } if (orderGuid.HasValue()) { - attributes.SellerOrderAttributes = new SellerOrderAttributes() + attributes.SellerOrderAttributes = new SellerOrderAttributes { SellerOrderId = orderGuid, StoreName = storeName @@ -240,6 +248,25 @@ public OrderReferenceDetails SetOrderReferenceDetails(AmazonPayClient client, st return null; } + public OrderReferenceDetails SetOrderReferenceDetails(AmazonPayClient client, string orderReferenceId, string currencyCode, List cart) + { + decimal orderTotalDiscountAmountBase = decimal.Zero; + Discount orderTotalAppliedDiscount = null; + List appliedGiftCards = null; + int redeemedRewardPoints = 0; + decimal redeemedRewardPointsAmount = decimal.Zero; + + decimal? shoppingCartTotalBase = _orderTotalCalculationService.GetShoppingCartTotal(cart, + out orderTotalDiscountAmountBase, out orderTotalAppliedDiscount, out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount); + + if (shoppingCartTotalBase.HasValue) + { + return SetOrderReferenceDetails(client, orderReferenceId, shoppingCartTotalBase, currencyCode); + } + + return null; + } + /// Confirm an order reference informs Amazon that the buyer has placed the order. public void ConfirmOrderReference(AmazonPayClient client, string orderReferenceId) { @@ -404,15 +431,17 @@ public void Capture(AmazonPayClient client, CapturePaymentRequest capture, Captu result.NewPaymentStatus = capture.Order.PaymentStatus; var request = new CaptureRequest(); + var store = _services.StoreService.GetStoreById(capture.Order.StoreId); + request.SellerId = client.Settings.SellerId; request.AmazonAuthorizationId = capture.Order.AuthorizationTransactionId; request.CaptureReferenceId = GetRandomId("Capture"); //request.SellerCaptureNote = client.Settings.SellerNoteCapture.Truncate(255); - request.CaptureAmount = new Price() + request.CaptureAmount = new Price { Amount = capture.Order.OrderTotal.ToString("0.00", CultureInfo.InvariantCulture), - CurrencyCode = capture.Order.CustomerCurrencyCode ?? "EUR" + CurrencyCode = store.PrimaryStoreCurrency.CurrencyCode }; var response = client.Service.Capture(request); @@ -499,16 +528,18 @@ public string Refund(AmazonPayClient client, RefundPaymentRequest refund, Refund result.NewPaymentStatus = refund.Order.PaymentStatus; string amazonRefundId = null; + var store = _services.StoreService.GetStoreById(refund.Order.StoreId); + var request = new RefundRequest(); request.SellerId = client.Settings.SellerId; request.AmazonCaptureId = refund.Order.CaptureTransactionId; request.RefundReferenceId = GetRandomId("Refund"); //request.SellerRefundNote = client.Settings.SellerNoteRefund.Truncate(255); - request.RefundAmount = new Price() + request.RefundAmount = new Price { Amount = refund.AmountToRefund.ToString("0.00", CultureInfo.InvariantCulture), - CurrencyCode = refund.Order.CustomerCurrencyCode ?? "EUR" + CurrencyCode = store.PrimaryStoreCurrency.CurrencyCode }; var response = client.Service.Refund(request); @@ -594,7 +625,7 @@ public string ToInfoString(AmazonPayApiData data) try { - string[] strings = _localizationService.GetResource("Plugins.Payments.AmazonPay.MessageStrings").SplitSafe(";"); + string[] strings = _services.Localization.GetResource("Plugins.Payments.AmazonPay.MessageStrings").SplitSafe(";"); string state = data.State.Grow(data.ReasonCode, " "); if (data.ReasonDescription.HasValue()) diff --git a/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayService.cs b/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayService.cs index 457844290e..12bbea250b 100644 --- a/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayService.cs +++ b/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayService.cs @@ -18,7 +18,7 @@ using SmartStore.Core.Data; using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Discounts; +using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Logging; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Payments; @@ -34,7 +34,6 @@ using SmartStore.Services.Messages; using SmartStore.Services.Orders; using SmartStore.Services.Payments; -using SmartStore.Services.Stores; using SmartStore.Services.Tasks; namespace SmartStore.AmazonPay.Services @@ -48,8 +47,8 @@ public class AmazonPayService : IAmazonPayService private readonly IGenericAttributeService _genericAttributeService; private readonly IOrderTotalCalculationService _orderTotalCalculationService; private readonly ICurrencyService _currencyService; + private readonly CurrencySettings _currencySettings; private readonly ICustomerService _customerService; - private readonly IStoreService _storeService; private readonly IPriceFormatter _priceFormatter; private readonly OrderSettings _orderSettings; private readonly RewardPointsSettings _rewardPointsSettings; @@ -67,8 +66,8 @@ public AmazonPayService( IGenericAttributeService genericAttributeService, IOrderTotalCalculationService orderTotalCalculationService, ICurrencyService currencyService, + CurrencySettings currencySettings, ICustomerService customerService, - IStoreService storeService, IPriceFormatter priceFormatter, OrderSettings orderSettings, RewardPointsSettings rewardPointsSettings, @@ -85,8 +84,8 @@ public AmazonPayService( _genericAttributeService = genericAttributeService; _orderTotalCalculationService = orderTotalCalculationService; _currencyService = currencyService; + _currencySettings = currencySettings; _customerService = customerService; - _storeService = storeService; _priceFormatter = priceFormatter; _orderSettings = orderSettings; _rewardPointsSettings = rewardPointsSettings; @@ -109,27 +108,27 @@ private string GetPluginUrl(string action, bool useSsl = false) return pluginUrl; } - private decimal? GetOrderTotal() - { - decimal orderTotalDiscountAmountBase = decimal.Zero; - Discount orderTotalAppliedDiscount = null; - List appliedGiftCards = null; - int redeemedRewardPoints = 0; - decimal redeemedRewardPointsAmount = decimal.Zero; + //private decimal? GetOrderTotal() + //{ + // decimal orderTotalDiscountAmountBase = decimal.Zero; + // Discount orderTotalAppliedDiscount = null; + // List appliedGiftCards = null; + // int redeemedRewardPoints = 0; + // decimal redeemedRewardPointsAmount = decimal.Zero; - var cart = _services.WorkContext.CurrentCustomer.GetCartItems(ShoppingCartType.ShoppingCart, _services.StoreContext.CurrentStore.Id); + // var cart = _services.WorkContext.CurrentCustomer.GetCartItems(ShoppingCartType.ShoppingCart, _services.StoreContext.CurrentStore.Id); - decimal? shoppingCartTotalBase = _orderTotalCalculationService.GetShoppingCartTotal(cart, - out orderTotalDiscountAmountBase, out orderTotalAppliedDiscount, out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount); + // decimal? shoppingCartTotalBase = _orderTotalCalculationService.GetShoppingCartTotal(cart, + // out orderTotalDiscountAmountBase, out orderTotalAppliedDiscount, out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount); - if (shoppingCartTotalBase.HasValue) // shipping method needs to be selected here! - { - decimal shoppingCartTotal = _currencyService.ConvertFromPrimaryStoreCurrency(shoppingCartTotalBase.Value, _services.WorkContext.WorkingCurrency); + // if (shoppingCartTotalBase.HasValue) // shipping method needs to be selected here! + // { + // decimal shoppingCartTotal = _currencyService.ConvertFromPrimaryStoreCurrency(shoppingCartTotalBase.Value, _services.WorkContext.WorkingCurrency); - return shoppingCartTotal; - } - return null; - } + // return shoppingCartTotal; + // } + // return null; + //} private void SerializeOrderAttribute(AmazonPayOrderAttribute attribute, Order order) { @@ -337,7 +336,7 @@ public void SetupConfiguration(ConfigurationModel model) if (task == null) model.PollingTaskMinutes = 30; else - model.PollingTaskMinutes = (task.Seconds / 60); + model.PollingTaskMinutes = 30; // (task.Seconds / 60); } public string GetWidgetUrl() @@ -449,7 +448,7 @@ public AmazonPayViewModel ProcessPluginRequest(AmazonPayRequestType type, TempDa //model.IsOrderConfirmed = state.IsOrderConfirmed; } - var currency = _services.WorkContext.WorkingCurrency; + var currency = store.PrimaryStoreCurrency; var settings = _services.Settings.LoadSetting(store.Id); model.SellerId = settings.SellerId; @@ -530,7 +529,7 @@ public AmazonPayViewModel ProcessPluginRequest(AmazonPayRequestType type, TempDa _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.SelectedPaymentMethod, AmazonPayCore.SystemName, store.Id); var client = new AmazonPayClient(settings); - var details = _api.SetOrderReferenceDetails(client, model.OrderReferenceId, GetOrderTotal(), currency.CurrencyCode); + var unused = _api.SetOrderReferenceDetails(client, model.OrderReferenceId, store.PrimaryStoreCurrency.CurrencyCode, cart); // this is ugly... var paymentRequest = _httpContext.Session["OrderPaymentInfo"] as ProcessPaymentRequest; @@ -897,9 +896,9 @@ public PreProcessPaymentResult PreProcessPayment(ProcessPaymentRequest request) try { var orderGuid = request.OrderGuid.ToString(); - var store = _storeService.GetStoreById(request.StoreId); + var store = _services.StoreService.GetStoreById(request.StoreId); var customer = _customerService.GetCustomerById(request.CustomerId); - var currency = _services.WorkContext.WorkingCurrency; + var currency = store.PrimaryStoreCurrency; var settings = _services.Settings.LoadSetting(store.Id); var state = _httpContext.GetAmazonPayState(_services.Localization); var client = new AmazonPayClient(settings); @@ -967,8 +966,8 @@ public ProcessPaymentResult ProcessPayment(ProcessPaymentRequest request) try { var orderGuid = request.OrderGuid.ToString(); - var store = _storeService.GetStoreById(request.StoreId); - var currency = _services.WorkContext.WorkingCurrency; + var store = _services.StoreService.GetStoreById(request.StoreId); + var currency = store.PrimaryStoreCurrency; var settings = _services.Settings.LoadSetting(store.Id); var state = _httpContext.GetAmazonPayState(_services.Localization); var client = new AmazonPayClient(settings); @@ -1288,7 +1287,7 @@ public void DataPollingTaskInit() _scheduleTaskService.InsertTask(new ScheduleTask { Name = "{0} data polling".FormatWith(AmazonPayCore.SystemName), - Seconds = 30 * 60, + CronExpression = "*/30 * * * *", // Every 30 minutes Type = AmazonPayCore.DataPollingTaskType, Enabled = false, StopOnError = false, @@ -1302,7 +1301,7 @@ public void DataPollingTaskUpdate(bool enabled, int seconds) if (task != null) { task.Enabled = enabled; - task.Seconds = seconds; + //task.Seconds = seconds; _scheduleTaskService.UpdateTask(task); } diff --git a/src/Plugins/SmartStore.AmazonPay/Services/IAmazonPayApi.cs b/src/Plugins/SmartStore.AmazonPay/Services/IAmazonPayApi.cs index 838e9187fe..70d72d9aa1 100644 --- a/src/Plugins/SmartStore.AmazonPay/Services/IAmazonPayApi.cs +++ b/src/Plugins/SmartStore.AmazonPay/Services/IAmazonPayApi.cs @@ -1,10 +1,10 @@ using System.Collections.Generic; using System.Web; using OffAmazonPaymentsService.Model; -using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Orders; using SmartStore.AmazonPay.Services; using SmartStore.AmazonPay.Settings; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Orders; using SmartStore.Services.Payments; namespace SmartStore.AmazonPay.Api @@ -21,6 +21,8 @@ public partial interface IAmazonPayApi OrderReferenceDetails SetOrderReferenceDetails(AmazonPayClient client, string orderReferenceId, decimal? orderTotalAmount, string currencyCode, string orderGuid = null, string storeName = null); + OrderReferenceDetails SetOrderReferenceDetails(AmazonPayClient client, string orderReferenceId, string currencyCode, List cart); + void ConfirmOrderReference(AmazonPayClient client, string orderReferenceId); void CancelOrderReference(AmazonPayClient client, string orderReferenceId); diff --git a/src/Plugins/SmartStore.AmazonPay/SmartStore.AmazonPay.csproj b/src/Plugins/SmartStore.AmazonPay/SmartStore.AmazonPay.csproj index ab7c222b62..3cbc8fcb83 100644 --- a/src/Plugins/SmartStore.AmazonPay/SmartStore.AmazonPay.csproj +++ b/src/Plugins/SmartStore.AmazonPay/SmartStore.AmazonPay.csproj @@ -36,6 +36,7 @@ + true @@ -73,12 +74,11 @@ 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 ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll @@ -139,6 +139,7 @@ + diff --git a/src/Plugins/SmartStore.AmazonPay/Views/AmazonPay/Configure.cshtml b/src/Plugins/SmartStore.AmazonPay/Views/AmazonPay/Configure.cshtml index f8193c3c87..3d77fb69e4 100644 --- a/src/Plugins/SmartStore.AmazonPay/Views/AmazonPay/Configure.cshtml +++ b/src/Plugins/SmartStore.AmazonPay/Views/AmazonPay/Configure.cshtml @@ -9,21 +9,19 @@ Html.AddScriptParts(true, Url.Content("~/Plugins/SmartStore.AmazonPay/Scripts/smartstore.amazonpay.js")); } - - - - - -
                    -
                    - - @Html.Raw(@T("Plugins.Payments.AmazonPay.AdminInstruction")) -
                    -
                    - - - -
                    +
                    +
                    +
                    + + @Html.Raw(@T("Plugins.Payments.AmazonPay.AdminInstruction")) +
                    +
                    +
                    + + + +
                    +
                    @Html.Action("StoreScopeConfiguration", "Setting", new { area = "Admin" }) diff --git a/src/Plugins/SmartStore.AmazonPay/Views/Web.config b/src/Plugins/SmartStore.AmazonPay/Views/Web.config index 3bd96ceca5..52f7e9b6f7 100644 --- a/src/Plugins/SmartStore.AmazonPay/Views/Web.config +++ b/src/Plugins/SmartStore.AmazonPay/Views/Web.config @@ -14,7 +14,7 @@ - + diff --git a/src/Plugins/SmartStore.AmazonPay/changelog.md b/src/Plugins/SmartStore.AmazonPay/changelog.md index 21aa9f7c0f..c22eca67c8 100644 --- a/src/Plugins/SmartStore.AmazonPay/changelog.md +++ b/src/Plugins/SmartStore.AmazonPay/changelog.md @@ -1,44 +1,52 @@ -#Release Notes# +#Release Notes + +##Pay with Amazon 2.2.0.3 +###Improvements +* Added message token %SmartStore.AmazonPay.BillingAddressMessageNote% for billing address note in order placed customer notification + +##Pay with Amazon 2.2.0.2 +###Bugfixes +* Send currency code of primary store currency (not of working currency) to payment gateway ##Pay with Amazon 2.2.0.1 ### New Features * Supports order list label for new incoming IPNs -##Pay with Amazon 1.20## -###Bugfixes### +##Pay with Amazon 1.20 +###Bugfixes * PlatformID must be .NET ID not merchant ID. PlatformID needs to be identical for all orders -##Pay with Amazon 1.19## -###Bugfixes### +##Pay with Amazon 1.19 +###Bugfixes * Declined authorization IPN did not void the payment status -##Pay with Amazon 1.18## -###Bugfixes### +##Pay with Amazon 1.18 +###Bugfixes * Order wasn't found if the capturing\refunding took place at Amazon Seller Central and the notification came through IPN -##Pay with Amazon 1.17## -###Improvements### +##Pay with Amazon 1.17 +###Improvements * Amazon payments review -##Pay with Amazon 1.16## -###Bugfixes### +##Pay with Amazon 1.16 +###Bugfixes * Reflect refunds made at amazon seller central when using data polling -##Pay with Amazon 1.15## -###Bugfixes### +##Pay with Amazon 1.15 +###Bugfixes * Multistore configuration might be lost if "All stores" are left empty -##Pay with Amazon 1.14## -###Bugfixes### +##Pay with Amazon 1.14 +###Bugfixes * Data polling did not reflect the transaction status correctly if the action took place at amazon seller central -##Pay with Amazon 1.13## -###Bugfixes### +##Pay with Amazon 1.13 +###Bugfixes * Sometimes shipping method restrictions were not applied * Shipping tab in checkout may show "Shipping address is not set" even if set -##Pay with Amazon 1.12## -###Improvements### +##Pay with Amazon 1.12 +###Improvements * Created this changelog * Changed string „Bezahlen über Amazon“ into „Bezahlen mit Amazon“ diff --git a/src/Plugins/SmartStore.AmazonPay/packages.config b/src/Plugins/SmartStore.AmazonPay/packages.config index 504ef05535..dd197b961a 100644 --- a/src/Plugins/SmartStore.AmazonPay/packages.config +++ b/src/Plugins/SmartStore.AmazonPay/packages.config @@ -1,7 +1,7 @@  - - + + diff --git a/src/Plugins/SmartStore.AmazonPay/web.config b/src/Plugins/SmartStore.AmazonPay/web.config index 1e50c947f6..3fd5b09a23 100644 --- a/src/Plugins/SmartStore.AmazonPay/web.config +++ b/src/Plugins/SmartStore.AmazonPay/web.config @@ -1,124 +1,132 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + - \ No newline at end of file + diff --git a/src/Plugins/SmartStore.Clickatell/AdminMenu.cs b/src/Plugins/SmartStore.Clickatell/AdminMenu.cs index f01a091812..27ea086787 100644 --- a/src/Plugins/SmartStore.Clickatell/AdminMenu.cs +++ b/src/Plugins/SmartStore.Clickatell/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.Clickatell { @@ -15,7 +9,7 @@ protected override void BuildMenuCore(TreeNode pluginsNode) { var menuItem = new MenuItem().ToBuilder() .Text("Clickatell SMS Provider") - .ResKey("Plugins.FriendlyName.Mobile.SMS.Clickatell") + .ResKey("Plugins.FriendlyName.SmartStore.Clickatell") .Icon("send-o") .Action("ConfigurePlugin", "Plugin", new { systemName = "SmartStore.Clickatell", area = "Admin" }) .ToItem(); diff --git a/src/Plugins/SmartStore.Clickatell/ClickatellSmsProvider.cs b/src/Plugins/SmartStore.Clickatell/ClickatellSmsProvider.cs index e5ac70a69e..cd7fca5a22 100644 --- a/src/Plugins/SmartStore.Clickatell/ClickatellSmsProvider.cs +++ b/src/Plugins/SmartStore.Clickatell/ClickatellSmsProvider.cs @@ -1,18 +1,16 @@ using System; using System.ServiceModel; using System.Web.Routing; -using SmartStore.Core; -using SmartStore.Core.Plugins; using SmartStore.Clickatell.Clickatell; -using SmartStore.Services.Common; -using SmartStore.Services.Localization; using SmartStore.Core.Logging; +using SmartStore.Core.Plugins; +using SmartStore.Services.Localization; namespace SmartStore.Clickatell { - /// - /// Represents the Clickatell SMS provider - /// + /// + /// Represents the Clickatell SMS provider + /// public class ClickatellSmsProvider : BasePlugin, IConfigurable { private readonly ILogger _logger; @@ -84,7 +82,6 @@ public void GetConfigurationRoute(out string actionName, out string controllerNa ///
public override void Install() { - //locales _localizationService.ImportPluginResourcesFromXml(this.PluginDescriptor); base.Install(); @@ -95,9 +92,7 @@ public override void Install() ///
public override void Uninstall() { - //locales _localizationService.DeleteLocaleStringResources(this.PluginDescriptor.ResourceRootKey); - _localizationService.DeleteLocaleStringResources("Plugins.FriendlyName.Mobile.SMS.Clickatell", false); base.Uninstall(); } diff --git a/src/Plugins/SmartStore.Clickatell/Controllers/SmsClickatellController.cs b/src/Plugins/SmartStore.Clickatell/Controllers/SmsClickatellController.cs index 4b77ca2c8b..82be6e7725 100644 --- a/src/Plugins/SmartStore.Clickatell/Controllers/SmsClickatellController.cs +++ b/src/Plugins/SmartStore.Clickatell/Controllers/SmsClickatellController.cs @@ -6,6 +6,8 @@ using SmartStore.Services.Configuration; using SmartStore.Services.Localization; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Filters; +using SmartStore.Web.Framework.Security; namespace SmartStore.Clickatell.Controllers { diff --git a/src/Plugins/SmartStore.Clickatell/Description.txt b/src/Plugins/SmartStore.Clickatell/Description.txt index 9b5152e1bc..177b509843 100644 --- a/src/Plugins/SmartStore.Clickatell/Description.txt +++ b/src/Plugins/SmartStore.Clickatell/Description.txt @@ -1,8 +1,8 @@ FriendlyName: Clickatell SMS Provider SystemName: SmartStore.Clickatell Group: Mobile -Version: 2.2.0 -MinAppVersion: 2.2.0 +Version: 2.6.0 +MinAppVersion: 2.5.0 DisplayOrder: 1 FileName: SmartStore.Clickatell.dll ResourceRootKey: Plugins.Sms.Clickatell \ No newline at end of file diff --git a/src/Plugins/SmartStore.Clickatell/Localization/resources.de-de.xml b/src/Plugins/SmartStore.Clickatell/Localization/resources.de-de.xml index 33f5697b26..6a88b543c5 100644 --- a/src/Plugins/SmartStore.Clickatell/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.Clickatell/Localization/resources.de-de.xml @@ -1,5 +1,5 @@  - + Clickatell SMS-Anbieter diff --git a/src/Plugins/SmartStore.Clickatell/Localization/resources.en-us.xml b/src/Plugins/SmartStore.Clickatell/Localization/resources.en-us.xml index ba454d5fcc..4a42186f01 100644 --- a/src/Plugins/SmartStore.Clickatell/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.Clickatell/Localization/resources.en-us.xml @@ -1,5 +1,5 @@  - + Clickatell SMS Provider @@ -27,7 +27,7 @@ Enabled - Check to enable SMS provider + Check the box to enable SMS provider API ID diff --git a/src/Plugins/SmartStore.Clickatell/OrderPlacedEventConsumer.cs b/src/Plugins/SmartStore.Clickatell/OrderPlacedEventConsumer.cs index 580460d574..3ba9e1742b 100644 --- a/src/Plugins/SmartStore.Clickatell/OrderPlacedEventConsumer.cs +++ b/src/Plugins/SmartStore.Clickatell/OrderPlacedEventConsumer.cs @@ -14,7 +14,7 @@ public class OrderPlacedEventConsumer : IConsumer private readonly IPluginFinder _pluginFinder; private readonly IOrderService _orderService; private readonly IStoreContext _storeContext; - private readonly ISettingService _settingService; // codehint: sm-add + private readonly ISettingService _settingService; public OrderPlacedEventConsumer(ClickatellSettings clickatellSettings, IPluginFinder pluginFinder, @@ -26,7 +26,7 @@ public OrderPlacedEventConsumer(ClickatellSettings clickatellSettings, this._pluginFinder = pluginFinder; this._orderService = orderService; this._storeContext = storeContext; - this._settingService = settingService; // codehint: sm-add + this._settingService = settingService; } /// diff --git a/src/Plugins/SmartStore.Clickatell/RouteProvider.cs b/src/Plugins/SmartStore.Clickatell/RouteProvider.cs index ef267ba987..956b559e91 100644 --- a/src/Plugins/SmartStore.Clickatell/RouteProvider.cs +++ b/src/Plugins/SmartStore.Clickatell/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.Clickatell { diff --git a/src/Plugins/SmartStore.Clickatell/Views/SmsClickatell/Configure.cshtml b/src/Plugins/SmartStore.Clickatell/Views/SmsClickatell/Configure.cshtml index adee89c127..03b16cb275 100644 --- a/src/Plugins/SmartStore.Clickatell/Views/SmsClickatell/Configure.cshtml +++ b/src/Plugins/SmartStore.Clickatell/Views/SmsClickatell/Configure.cshtml @@ -1,8 +1,8 @@ -@{ - Layout = ""; -} +@using SmartStore.Web.Framework; @model SmartStore.Clickatell.Models.SmsClickatellModel -@using SmartStore.Web.Framework; +@{ + Layout = ""; +}
@@ -69,8 +69,8 @@
-
-

@T("Plugins.Sms.Clickatell.SendTest.Hint")

+
+
@T("Plugins.Sms.Clickatell.SendTest.Hint")
+ @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 4f2a281983..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 63f26d3792..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 a8f07ab2e8..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: 2.2.0 -MinAppVersion: 2.2.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 236ce4de62..dc162de8da 100644 --- a/src/Plugins/SmartStore.DiscountRules/SmartStore.DiscountRules.csproj +++ b/src/Plugins/SmartStore.DiscountRules/SmartStore.DiscountRules.csproj @@ -42,6 +42,7 @@ + true @@ -84,8 +85,9 @@ ..\..\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 @@ -192,6 +194,7 @@ PreserveNewest + Designer PreserveNewest diff --git a/src/Plugins/SmartStore.DiscountRules/Views/Web.config b/src/Plugins/SmartStore.DiscountRules/Views/Web.config index 31dc8df754..e9d36a3c51 100644 --- a/src/Plugins/SmartStore.DiscountRules/Views/Web.config +++ b/src/Plugins/SmartStore.DiscountRules/Views/Web.config @@ -14,7 +14,7 @@ - + diff --git a/src/Plugins/SmartStore.DiscountRules/packages.config b/src/Plugins/SmartStore.DiscountRules/packages.config index 669284b1aa..0325f33297 100644 --- a/src/Plugins/SmartStore.DiscountRules/packages.config +++ b/src/Plugins/SmartStore.DiscountRules/packages.config @@ -4,5 +4,5 @@ - + \ No newline at end of file diff --git a/src/Plugins/SmartStore.DiscountRules/web.config b/src/Plugins/SmartStore.DiscountRules/web.config index 46b8ba77d4..ba87d0f098 100644 --- a/src/Plugins/SmartStore.DiscountRules/web.config +++ b/src/Plugins/SmartStore.DiscountRules/web.config @@ -1,117 +1,117 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - \ No newline at end of file + diff --git a/src/Plugins/SmartStore.FacebookAuth/Controllers/ExternalAuthFacebookController.cs b/src/Plugins/SmartStore.FacebookAuth/Controllers/ExternalAuthFacebookController.cs index 386027e411..5a99884991 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Controllers/ExternalAuthFacebookController.cs +++ b/src/Plugins/SmartStore.FacebookAuth/Controllers/ExternalAuthFacebookController.cs @@ -1,75 +1,88 @@ using System.Web.Mvc; -using SmartStore.Core; using SmartStore.Core.Domain.Customers; using SmartStore.FacebookAuth.Core; using SmartStore.FacebookAuth.Models; +using SmartStore.Services; using SmartStore.Services.Authentication.External; -using SmartStore.Services.Configuration; using SmartStore.Services.Security; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Security; +using SmartStore.Web.Framework.Settings; namespace SmartStore.FacebookAuth.Controllers { //[UnitOfWork] public class ExternalAuthFacebookController : PluginControllerBase { - private readonly ISettingService _settingService; - private readonly FacebookExternalAuthSettings _facebookExternalAuthSettings; private readonly IOAuthProviderFacebookAuthorizer _oAuthProviderFacebookAuthorizer; private readonly IOpenAuthenticationService _openAuthenticationService; private readonly ExternalAuthenticationSettings _externalAuthenticationSettings; - private readonly IStoreContext _storeContext; - private readonly IPermissionService _permissionService; + private readonly ICommonServices _services; - public ExternalAuthFacebookController(ISettingService settingService, - FacebookExternalAuthSettings facebookExternalAuthSettings, + public ExternalAuthFacebookController( IOAuthProviderFacebookAuthorizer oAuthProviderFacebookAuthorizer, IOpenAuthenticationService openAuthenticationService, ExternalAuthenticationSettings externalAuthenticationSettings, - IStoreContext storeContext, - IPermissionService permissionService) + ICommonServices services) { - this._settingService = settingService; - this._facebookExternalAuthSettings = facebookExternalAuthSettings; this._oAuthProviderFacebookAuthorizer = oAuthProviderFacebookAuthorizer; this._openAuthenticationService = openAuthenticationService; this._externalAuthenticationSettings = externalAuthenticationSettings; - this._storeContext = storeContext; - this._permissionService = permissionService; + this._services = services; } + + private bool HasPermission(bool notify = true) + { + bool hasPermission = _services.Permissions.Authorize(StandardPermissionProvider.ManageExternalAuthenticationMethods); + + if (notify && !hasPermission) + NotifyError(_services.Localization.GetResource("Admin.AccessDenied.Description")); + + return hasPermission; + } - [AdminAuthorize] - [ChildActionOnly] + [AdminAuthorize, ChildActionOnly] public ActionResult Configure() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageExternalAuthenticationMethods)) - return Content("Access denied"); + if (!HasPermission(false)) + return AccessDeniedPartialView(); var model = new ConfigurationModel(); - model.ClientKeyIdentifier = _facebookExternalAuthSettings.ClientKeyIdentifier; - model.ClientSecret = _facebookExternalAuthSettings.ClientSecret; + int storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var settings = _services.Settings.LoadSetting(storeScope); + + model.ClientKeyIdentifier = settings.ClientKeyIdentifier; + model.ClientSecret = settings.ClientSecret; + + var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); + storeDependingSettingHelper.GetOverrideKeys(settings, model, storeScope, _services.Settings); return View(model); } - [HttpPost] - [AdminAuthorize] - [ChildActionOnly] - public ActionResult Configure(ConfigurationModel model) + [HttpPost, AdminAuthorize, ChildActionOnly] + public ActionResult Configure(ConfigurationModel model, FormCollection form) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageExternalAuthenticationMethods)) - return Content("Access denied"); + if (!HasPermission(false)) + return Configure(); if (!ModelState.IsValid) return Configure(); - - //save settings - _facebookExternalAuthSettings.ClientKeyIdentifier = model.ClientKeyIdentifier; - _facebookExternalAuthSettings.ClientSecret = model.ClientSecret; - _settingService.SaveSetting(_facebookExternalAuthSettings); - - return View(model); + + var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); + int storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var settings = _services.Settings.LoadSetting(storeScope); + + settings.ClientKeyIdentifier = model.ClientKeyIdentifier; + settings.ClientSecret = model.ClientSecret; + + storeDependingSettingHelper.UpdateSettings(settings, form, storeScope, _services.Settings); + _services.Settings.ClearCache(); + + NotifySuccess(_services.Localization.GetResource("Admin.Common.DataSuccessfullySaved")); + + return Configure(); } [ChildActionOnly] @@ -81,7 +94,7 @@ public ActionResult PublicInfo() [NonAction] private ActionResult LoginInternal(string returnUrl, bool verifyResponse) { - var processor = _openAuthenticationService.LoadExternalAuthenticationMethodBySystemName("SmartStore.FacebookAuth", _storeContext.CurrentStore.Id); + var processor = _openAuthenticationService.LoadExternalAuthenticationMethodBySystemName(Provider.SystemName, _services.StoreContext.CurrentStore.Id); if (processor == null || !processor.IsMethodActive(_externalAuthenticationSettings)) { throw new SmartException("Facebook module cannot be loaded"); @@ -122,9 +135,13 @@ private ActionResult LoginInternal(string returnUrl, bool verifyResponse) break; } - if (result.Result != null) return result.Result; - return HttpContext.Request.IsAuthenticated ? new RedirectResult(!string.IsNullOrEmpty(returnUrl) ? returnUrl : "~/") : new RedirectResult(Url.LogOn(returnUrl)); - } + if (result.Result != null) + return result.Result; + + return HttpContext.Request.IsAuthenticated ? + RedirectToReferrer(returnUrl, "~/") : + new RedirectResult(Url.LogOn(returnUrl)); + } public ActionResult Login(string returnUrl) { diff --git a/src/Plugins/SmartStore.FacebookAuth/Core/FacebookOAuth2Client.cs b/src/Plugins/SmartStore.FacebookAuth/Core/FacebookOAuth2Client.cs new file mode 100644 index 0000000000..5f0960101b --- /dev/null +++ b/src/Plugins/SmartStore.FacebookAuth/Core/FacebookOAuth2Client.cs @@ -0,0 +1,231 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Data; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Web; +using System.Web.Script.Serialization; +using DotNetOpenAuth.AspNet; +using DotNetOpenAuth.AspNet.Clients; +using Newtonsoft.Json; + +namespace SmartStore.FacebookAuth.Core +{ + /// + /// + internal class FacebookOAuth2Client : OAuth2Client + { + /// + /// The authorization endpoint. + /// + private const string AuthorizationEndpoint = "https://www.facebook.com/v2.8/dialog/oauth"; + /// + /// The token endpoint. + /// + private const string TokenEndpoint = "https://graph.facebook.com/v2.8/oauth/access_token"; + /// + /// The user info endpoint. + /// + private const string UserInfoEndpoint = "https://graph.facebook.com/v2.8/me"; + /// + /// The app id. + /// + private readonly string _appId; + /// + /// The app secret. + /// + private readonly string _appSecret; + + /// + /// The requested scopes. + /// + private readonly string[] _requestedScopes; + + + /// + /// Creates a new Facebook OAuth2 client, requesting the default "email" scope. + /// + /// The Facebook App Id + /// The Facebook App Secret + public FacebookOAuth2Client(string appId, string appSecret) + : this(appId, appSecret, new[] { "email" }) { } + + /// + /// Creates a new Facebook OAuth2 client. + /// + /// The Facebook App Id + /// The Facebook App Secret + /// One or more requested scopes, passed without the base URI. + public FacebookOAuth2Client(string appId, string appSecret, params string[] requestedScopes) + : base("facebook") + { + if (string.IsNullOrWhiteSpace(appId)) + throw new ArgumentNullException("appId"); + + if (string.IsNullOrWhiteSpace(appSecret)) + throw new ArgumentNullException("appSecret"); + + if (requestedScopes == null) + throw new ArgumentNullException("requestedScopes"); + + if (requestedScopes.Length == 0) + throw new ArgumentException("One or more scopes must be requested.", "requestedScopes"); + + _appId = appId; + _appSecret = appSecret; + _requestedScopes = requestedScopes; + } + + public override void RequestAuthentication(HttpContextBase context, Uri returnUrl) + { + string redirectUrl = this.GetServiceLoginUrl(returnUrl).AbsoluteUri; + context.Response.Redirect(redirectUrl, endResponse: true); + } + + public new AuthenticationResult VerifyAuthentication(HttpContextBase context) + { + throw new NoNullAllowedException(); + } + + public override AuthenticationResult VerifyAuthentication(HttpContextBase context, Uri returnPageUrl) + { + string code = context.Request.QueryString["code"]; + if (string.IsNullOrEmpty(code)) + { + return AuthenticationResult.Failed; + } + + string accessToken = this.QueryAccessToken(returnPageUrl, code); + if (accessToken == null) + { + return AuthenticationResult.Failed; + } + + IDictionary userData = this.GetUserData(accessToken); + if (userData == null) + { + return AuthenticationResult.Failed; + } + + string id = userData["id"]; + string name; + + // Some oAuth providers do not return value for the 'username' attribute. + // In that case, try the 'name' attribute. If it's still unavailable, fall back to 'id' + if (!userData.TryGetValue("username", out name) && !userData.TryGetValue("name", out name)) + { + name = id; + } + + // add the access token to the user data dictionary just in case page developers want to use it + userData["accesstoken"] = accessToken; + + return new AuthenticationResult( + isSuccessful: true, provider: this.ProviderName, providerUserId: id, userName: name, extraData: userData); + } + + protected override Uri GetServiceLoginUrl(Uri returnUrl) + { + var state = string.IsNullOrEmpty(returnUrl.Query) ? string.Empty : returnUrl.Query.Substring(1); + + return BuildUri(AuthorizationEndpoint, new NameValueCollection + { + { "client_id", _appId }, + { "scope", string.Join(" ", _requestedScopes) }, + { "redirect_uri", returnUrl.GetLeftPart(UriPartial.Path) }, + { "state", state }, + }); + } + + protected override IDictionary GetUserData(string accessToken) + { + var uri = BuildUri(UserInfoEndpoint, new NameValueCollection { { "access_token", accessToken } }); + + var webRequest = (HttpWebRequest)WebRequest.Create(uri); + + using (var webResponse = webRequest.GetResponse()) + using (var stream = webResponse.GetResponseStream()) + { + if (stream == null) + return null; + + using (var textReader = new StreamReader(stream)) + { + var json = textReader.ReadToEnd(); + var extraData = JsonConvert.DeserializeObject>(json); + var data = extraData.ToDictionary(x => x.Key, x => x.Value.ToString()); + + data.Add("picture", string.Format("https://graph.facebook.com/{0}/picture", data["id"])); + + return data; + } + } + } + + public string QueryAccessTokenByCode(Uri returnUrl, string authorizationCode) + { + return this.QueryAccessToken(returnUrl, authorizationCode); + } + + protected override string QueryAccessToken(Uri returnUrl, string authorizationCode) + { + var uri = BuildUri(TokenEndpoint, new NameValueCollection + { + { "code", authorizationCode }, + { "client_id", _appId }, + { "client_secret", _appSecret }, + { "redirect_uri", returnUrl.GetLeftPart(UriPartial.Path) }, + }); + + var webRequest = (HttpWebRequest)WebRequest.Create(uri); + string accessToken = null; + HttpWebResponse response = (HttpWebResponse)webRequest.GetResponse(); + + // handle response from FB + // this will not be a url with params like the first request to get the 'code' + Encoding rEncoding = Encoding.GetEncoding(response.CharacterSet); + + using (StreamReader sr = new StreamReader(response.GetResponseStream(), rEncoding)) + { + var serializer = new JavaScriptSerializer(); + var jsonObject = serializer.DeserializeObject(sr.ReadToEnd()); + var jConvert = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(jsonObject)); + + Dictionary desirializedJsonObject = JsonConvert.DeserializeObject>(jConvert.ToString()); + accessToken = desirializedJsonObject["access_token"].ToString(); + } + return accessToken; + } + + private static Uri BuildUri(string baseUri, NameValueCollection queryParameters) + { + var keyValuePairs = queryParameters.AllKeys.Select(k => HttpUtility.UrlEncode(k) + "=" + HttpUtility.UrlEncode(queryParameters[k])); + var qs = String.Join("&", keyValuePairs); + + var builder = new UriBuilder(baseUri) { Query = qs }; + return builder.Uri; + } + + /// + /// Facebook works best when return data be packed into a "state" parameter. + /// This should be called before verifying the request, so that the url is rewritten to support this. + /// + public static void RewriteRequest() + { + var ctx = HttpContext.Current; + + var stateString = HttpUtility.UrlDecode(ctx.Request.QueryString["state"]); + if (stateString == null || !stateString.Contains("__provider__=facebook")) + return; + + var q = HttpUtility.ParseQueryString(stateString); + q.Add(ctx.Request.QueryString); + q.Remove("state"); + + ctx.RewritePath(ctx.Request.Path + "?" + q); + } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.FacebookAuth/Core/FacebookProviderAuthorizer.cs b/src/Plugins/SmartStore.FacebookAuth/Core/FacebookProviderAuthorizer.cs index 6da9e37086..378599e6d5 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Core/FacebookProviderAuthorizer.cs +++ b/src/Plugins/SmartStore.FacebookAuth/Core/FacebookProviderAuthorizer.cs @@ -2,29 +2,32 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Net; using System.Text; using System.Web; using System.Web.Mvc; using DotNetOpenAuth.AspNet; using DotNetOpenAuth.AspNet.Clients; -using SmartStore.Core; +using Newtonsoft.Json.Linq; using SmartStore.Core.Domain.Customers; +using SmartStore.Services; using SmartStore.Services.Authentication.External; namespace SmartStore.FacebookAuth.Core { - public class FacebookProviderAuthorizer : IOAuthProviderFacebookAuthorizer + public class FacebookProviderAuthorizer : IOAuthProviderFacebookAuthorizer { #region Fields private readonly IExternalAuthorizer _authorizer; private readonly IOpenAuthenticationService _openAuthenticationService; private readonly ExternalAuthenticationSettings _externalAuthenticationSettings; - private readonly FacebookExternalAuthSettings _facebookExternalAuthSettings; private readonly HttpContextBase _httpContext; - private readonly IWebHelper _webHelper; - private FacebookClient _facebookApplication; + private readonly ICommonServices _services; + + private FacebookOAuth2Client _facebookApplication; #endregion @@ -33,25 +36,33 @@ public class FacebookProviderAuthorizer : IOAuthProviderFacebookAuthorizer public FacebookProviderAuthorizer(IExternalAuthorizer authorizer, IOpenAuthenticationService openAuthenticationService, ExternalAuthenticationSettings externalAuthenticationSettings, - FacebookExternalAuthSettings facebookExternalAuthSettings, HttpContextBase httpContext, - IWebHelper webHelper) + ICommonServices services) { this._authorizer = authorizer; this._openAuthenticationService = openAuthenticationService; this._externalAuthenticationSettings = externalAuthenticationSettings; - this._facebookExternalAuthSettings = facebookExternalAuthSettings; this._httpContext = httpContext; - this._webHelper = webHelper; + this._services = services; } #endregion #region Utilities - private FacebookClient FacebookApplication + private FacebookOAuth2Client FacebookApplication { - get { return _facebookApplication ?? (_facebookApplication = new FacebookClient(_facebookExternalAuthSettings.ClientKeyIdentifier, _facebookExternalAuthSettings.ClientSecret)); } + get + { + if (_facebookApplication == null) + { + var settings = _services.Settings.LoadSetting(_services.StoreContext.CurrentStore.Id); + + _facebookApplication = new FacebookOAuth2Client(settings.ClientKeyIdentifier, settings.ClientSecret); + } + + return _facebookApplication; + } } private AuthorizeState VerifyAuthentication(string returnUrl) @@ -82,18 +93,50 @@ private AuthorizeState VerifyAuthentication(string returnUrl) } var state = new AuthorizeState(returnUrl, OpenAuthenticationStatus.Error); - var error = authResult.Error != null ? authResult.Error.Message : "Unknown error"; - state.AddError(error); - return state; + + state.AddError(authResult.Error != null + ? authResult.Error.Message + : _services.Localization.GetResource("Admin.Common.UnknownError")); + + return state; } + private string GetEmailFromFacebook(string accessToken) + { + var result = ""; + var webRequest = WebRequest.Create("https://graph.facebook.com/me?fields=email&access_token=" + EscapeUriDataStringRfc3986(accessToken)); + + using (var webResponse = webRequest.GetResponse()) + using (var stream = webResponse.GetResponseStream()) + using (var reader = new StreamReader(stream)) + { + var strResponse = reader.ReadToEnd(); + var info = JObject.Parse(strResponse); + + if (info["email"] != null) + { + result = info["email"].ToString(); + } + } + return result; + } + private void ParseClaims(AuthenticationResult authenticationResult, OAuthAuthenticationParameters parameters) { var claims = new UserClaims(); claims.Contact = new ContactClaims(); + if (authenticationResult.ExtraData.ContainsKey("username")) + { claims.Contact.Email = authenticationResult.ExtraData["username"]; + } + else + { + claims.Contact.Email = GetEmailFromFacebook(authenticationResult.ExtraData["accesstoken"]); + } + claims.Name = new NameClaims(); + if (authenticationResult.ExtraData.ContainsKey("name")) { var name = authenticationResult.ExtraData["name"]; @@ -122,7 +165,7 @@ private AuthorizeState RequestAuthentication(string returnUrl) private Uri GenerateLocalCallbackUri() { - string url = string.Format("{0}Plugins/SmartStore.FacebookAuth/logincallback/", _webHelper.GetStoreLocation()); + string url = string.Format("{0}Plugins/SmartStore.FacebookAuth/logincallback/", _services.WebHelper.GetStoreLocation()); return new Uri(url); } @@ -131,10 +174,14 @@ private Uri GenerateServiceLoginUrl() //code copied from DotNetOpenAuth.AspNet.Clients.FacebookClient file var builder = new UriBuilder("https://www.facebook.com/dialog/oauth"); var args = new Dictionary(); - args.Add("client_id", _facebookExternalAuthSettings.ClientKeyIdentifier); + var settings = _services.Settings.LoadSetting(_services.StoreContext.CurrentStore.Id); + + args.Add("client_id", settings.ClientKeyIdentifier); args.Add("redirect_uri", GenerateLocalCallbackUri().AbsoluteUri); args.Add("scope", "email"); + AppendQueryArgs(builder, args); + return builder.Uri; } @@ -152,6 +199,7 @@ private void AppendQueryArgs(UriBuilder builder, IEnumerable> args) { if (!args.Any>()) @@ -169,7 +217,9 @@ private string CreateQueryString(IEnumerable> args) builder.Length--; return builder.ToString(); } + private readonly string[] UriRfc3986CharsToEscape = new string[] { "!", "*", "'", "(", ")" }; + private string EscapeUriDataStringRfc3986(string value) { StringBuilder builder = new StringBuilder(Uri.EscapeDataString(value)); diff --git a/src/Plugins/SmartStore.FacebookAuth/DependencyRegistrar.cs b/src/Plugins/SmartStore.FacebookAuth/DependencyRegistrar.cs index 69f5a94064..ef4c32cf34 100644 --- a/src/Plugins/SmartStore.FacebookAuth/DependencyRegistrar.cs +++ b/src/Plugins/SmartStore.FacebookAuth/DependencyRegistrar.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq.Expressions; using Autofac; -using Autofac.Integration.Mvc; using SmartStore.Core.Infrastructure; using SmartStore.Core.Infrastructure.DependencyManagement; using SmartStore.FacebookAuth.Core; diff --git a/src/Plugins/SmartStore.FacebookAuth/Description.txt b/src/Plugins/SmartStore.FacebookAuth/Description.txt index 081bf95dd1..d60a7a363b 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Description.txt +++ b/src/Plugins/SmartStore.FacebookAuth/Description.txt @@ -1,8 +1,8 @@ FriendlyName: Facebook SystemName: SmartStore.FacebookAuth Group: Security -Version: 2.2.0 -MinAppVersion: 2.2.0 +Version: 2.6.0.1 +MinAppVersion: 2.5.0 DisplayOrder: 5 FileName: SmartStore.FacebookAuth.dll ResourceRootKey: Plugins.ExternalAuth.Facebook \ No newline at end of file diff --git a/src/Plugins/SmartStore.FacebookAuth/FacebookExternalAuthMethod.cs b/src/Plugins/SmartStore.FacebookAuth/FacebookExternalAuthMethod.cs index 2c4c183c76..d3a0ea7b26 100644 --- a/src/Plugins/SmartStore.FacebookAuth/FacebookExternalAuthMethod.cs +++ b/src/Plugins/SmartStore.FacebookAuth/FacebookExternalAuthMethod.cs @@ -1,5 +1,6 @@ using System.Web.Routing; using SmartStore.Core.Plugins; +using SmartStore.FacebookAuth.Core; using SmartStore.Services.Authentication.External; using SmartStore.Services.Localization; @@ -11,8 +12,10 @@ namespace SmartStore.FacebookAuth public class FacebookExternalAuthMethod : BasePlugin, IExternalAuthenticationMethod, IConfigurable { #region Fields + private readonly FacebookExternalAuthSettings _facebookExternalAuthSettings; private readonly ILocalizationService _localizationService; + #endregion #region Ctor @@ -37,7 +40,7 @@ public void GetConfigurationRoute(out string actionName, out string controllerNa { actionName = "Configure"; controllerName = "ExternalAuthFacebook"; - routeValues = new RouteValueDictionary(new { Namespaces = "SmartStore.FacebookAuth.Controllers", area = "SmartStore.FacebookAuth" }); + routeValues = new RouteValueDictionary(new { Namespaces = "SmartStore.FacebookAuth.Controllers", area = Provider.SystemName }); } /// @@ -50,7 +53,7 @@ public void GetPublicInfoRoute(out string actionName, out string controllerName, { actionName = "PublicInfo"; controllerName = "ExternalAuthFacebook"; - routeValues = new RouteValueDictionary(new { Namespaces = "SmartStore.FacebookAuth.Controllers", area = "SmartStore.FacebookAuth" }); + routeValues = new RouteValueDictionary(new { Namespaces = "SmartStore.FacebookAuth.Controllers", area = Provider.SystemName }); } /// @@ -68,7 +71,6 @@ public override void Uninstall() { //locales _localizationService.DeleteLocaleStringResources(this.PluginDescriptor.ResourceRootKey); - _localizationService.DeleteLocaleStringResources("Plugins.FriendlyName.ExternalAuth.Facebook", false); base.Uninstall(); } diff --git a/src/Plugins/SmartStore.FacebookAuth/Localization/resources.de-de.xml b/src/Plugins/SmartStore.FacebookAuth/Localization/resources.de-de.xml index 5fa1ebdcdb..34d1ab5e11 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.FacebookAuth/Localization/resources.de-de.xml @@ -1,5 +1,5 @@  - + Facebook diff --git a/src/Plugins/SmartStore.FacebookAuth/Localization/resources.en-us.xml b/src/Plugins/SmartStore.FacebookAuth/Localization/resources.en-us.xml index b8bb50d689..e1594d213b 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.FacebookAuth/Localization/resources.en-us.xml @@ -1,5 +1,5 @@  - + Facebook diff --git a/src/Plugins/SmartStore.FacebookAuth/Models/ConfigurationModel.cs b/src/Plugins/SmartStore.FacebookAuth/Models/ConfigurationModel.cs index 5c78e61896..928feab5b2 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Models/ConfigurationModel.cs +++ b/src/Plugins/SmartStore.FacebookAuth/Models/ConfigurationModel.cs @@ -1,5 +1,5 @@ using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.FacebookAuth.Models { @@ -7,6 +7,7 @@ public class ConfigurationModel : ModelBase { [SmartResourceDisplayName("Plugins.ExternalAuth.Facebook.ClientKeyIdentifier")] public string ClientKeyIdentifier { get; set; } + [SmartResourceDisplayName("Plugins.ExternalAuth.Facebook.ClientSecret")] public string ClientSecret { get; set; } } diff --git a/src/Plugins/SmartStore.FacebookAuth/RouteProvider.cs b/src/Plugins/SmartStore.FacebookAuth/RouteProvider.cs index 1326663260..90465eb41a 100644 --- a/src/Plugins/SmartStore.FacebookAuth/RouteProvider.cs +++ b/src/Plugins/SmartStore.FacebookAuth/RouteProvider.cs @@ -1,6 +1,7 @@ using System.Web.Mvc; using System.Web.Routing; -using SmartStore.Web.Framework.Mvc.Routes; +using SmartStore.FacebookAuth.Core; +using SmartStore.Web.Framework.Routing; namespace SmartStore.FacebookAuth { @@ -13,7 +14,7 @@ public void RegisterRoutes(RouteCollection routes) new { controller = "ExternalAuthFacebook" }, new[] { "SmartStore.FacebookAuth.Controllers" } ) - .DataTokens["area"] = "SmartStore.FacebookAuth"; + .DataTokens["area"] = Provider.SystemName; } public int Priority { diff --git a/src/Plugins/SmartStore.FacebookAuth/SmartStore.FacebookAuth.csproj b/src/Plugins/SmartStore.FacebookAuth/SmartStore.FacebookAuth.csproj index 9037add387..85992eb451 100644 --- a/src/Plugins/SmartStore.FacebookAuth/SmartStore.FacebookAuth.csproj +++ b/src/Plugins/SmartStore.FacebookAuth/SmartStore.FacebookAuth.csproj @@ -42,6 +42,7 @@ + true @@ -81,11 +82,11 @@ 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 @@ -120,8 +121,9 @@ ..\..\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 @@ -178,6 +180,7 @@ Properties\AssemblyVersionInfo.cs + diff --git a/src/Plugins/SmartStore.FacebookAuth/Views/ExternalAuthFacebook/Configure.cshtml b/src/Plugins/SmartStore.FacebookAuth/Views/ExternalAuthFacebook/Configure.cshtml index 94d69f5d8a..60dc28cea5 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Views/ExternalAuthFacebook/Configure.cshtml +++ b/src/Plugins/SmartStore.FacebookAuth/Views/ExternalAuthFacebook/Configure.cshtml @@ -3,23 +3,29 @@ } @model SmartStore.FacebookAuth.Models.ConfigurationModel @using SmartStore.Web.Framework; + +
+ + @Html.Raw(T("Plugins.ExternalAuth.Facebook.AdminInstruction")) +
+ +
+ +@Html.Action("StoreScopeConfiguration", "Setting", new { area = "Admin" }) + +@Html.ValidationSummary(false) + +
+ @using (Html.BeginForm()) { - - - @@ -28,7 +34,7 @@ @Html.SmartLabelFor(model => model.ClientSecret) @@ -43,5 +49,4 @@
-
- - @Html.Raw(T("Plugins.ExternalAuth.Facebook.AdminInstruction")) -
-
@Html.SmartLabelFor(model => model.ClientKeyIdentifier) - @Html.EditorFor(model => model.ClientKeyIdentifier) + @Html.SettingEditorFor(model => model.ClientKeyIdentifier) @Html.ValidationMessageFor(model => model.ClientKeyIdentifier)
- @Html.EditorFor(model => model.ClientSecret) + @Html.SettingEditorFor(model => model.ClientSecret) @Html.ValidationMessageFor(model => model.ClientSecret)
- } \ No newline at end of file diff --git a/src/Plugins/SmartStore.FacebookAuth/Views/Web.config b/src/Plugins/SmartStore.FacebookAuth/Views/Web.config index 31dc8df754..e9d36a3c51 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Views/Web.config +++ b/src/Plugins/SmartStore.FacebookAuth/Views/Web.config @@ -14,7 +14,7 @@ - + diff --git a/src/Plugins/SmartStore.FacebookAuth/packages.config b/src/Plugins/SmartStore.FacebookAuth/packages.config index 894e492c07..2cd39833bf 100644 --- a/src/Plugins/SmartStore.FacebookAuth/packages.config +++ b/src/Plugins/SmartStore.FacebookAuth/packages.config @@ -1,7 +1,7 @@  - - + + @@ -15,5 +15,5 @@ - + \ No newline at end of file diff --git a/src/Plugins/SmartStore.FacebookAuth/web.config b/src/Plugins/SmartStore.FacebookAuth/web.config index 10ad5c862a..68523a15bc 100644 --- a/src/Plugins/SmartStore.FacebookAuth/web.config +++ b/src/Plugins/SmartStore.FacebookAuth/web.config @@ -1,121 +1,121 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - \ No newline at end of file + diff --git a/src/Plugins/SmartStore.GoogleAnalytics/Controllers/WidgetsGoogleAnalyticsController.cs b/src/Plugins/SmartStore.GoogleAnalytics/Controllers/WidgetsGoogleAnalyticsController.cs index 3ed283c958..6e14ec3d1b 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/Controllers/WidgetsGoogleAnalyticsController.cs +++ b/src/Plugins/SmartStore.GoogleAnalytics/Controllers/WidgetsGoogleAnalyticsController.cs @@ -14,6 +14,7 @@ using SmartStore.Services.Orders; using SmartStore.Services.Stores; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Security; using SmartStore.Web.Framework.Settings; namespace SmartStore.GoogleAnalytics.Controllers @@ -126,19 +127,6 @@ private Order GetLastOrder() return order; } - // 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 4a7c23c2ff..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: 2.2.0 -MinAppVersion: 2.2.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/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/web.config b/src/Plugins/SmartStore.GoogleAnalytics/web.config index ad003b3e26..c0f82db177 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/web.config +++ b/src/Plugins/SmartStore.GoogleAnalytics/web.config @@ -1,116 +1,116 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - \ No newline at end of file + 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 312ff89ff7..0000000000 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedFroogleController.cs +++ /dev/null @@ -1,180 +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 SmartStore.Web.Framework.Plugins; -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; - model.Exporting = entity.Export; - } - - 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() && _settings.Gender != PluginHelper.NotSpecified) - { - ViewBag.DefaultGender = T("Plugins.Feed.Froogle.Gender" + ci.TextInfo.ToTitleCase(_settings.Gender)); - } - - if (_settings.AgeGroup.HasValue() && _settings.AgeGroup != PluginHelper.NotSpecified) - { - 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/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/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 e2a36fb2e7..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.2.0.1 -MinAppVersion: 2.2.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 cca0f73827..7064e84880 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Domain/GoogleProductRecord.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Domain/GoogleProductRecord.cs @@ -29,5 +29,16 @@ public GoogleProductRecord() 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 5ca8aeb5dc..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) @@ -63,8 +64,17 @@ public void HandleEvent(ModelBoundEvent eventMessage) entity.Taxonomy = model.Taxonomy; entity.Material = model.Material; entity.Pattern = model.Pattern; - entity.Export = model.Exporting; + 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 6ab4f71335..5a8dd0168d 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Extensions/MiscExtensions.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Extensions/MiscExtensions.cs @@ -9,26 +9,28 @@ public static string XEditableLink(this HtmlHelper hlp, string fieldName, string { string displayText = null; - if (fieldName == "Gender" || fieldName == "AgeGroup" || fieldName == "Exporting") - 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() || - !product.Export; + 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 60ba0d7094..0000000000 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Models/FeedFroogleModel.cs +++ /dev/null @@ -1,268 +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; } - - [SmartResourceDisplayName("Common.Export")] - public int Export { get; set; } - [SmartResourceDisplayName("Common.Export")] - public bool Exporting - { - get { return Export != 0; } - set { Export = (value ? 1 : 0); } - } - - public string GenderLocalize { get; set; } - public string AgeGroupLocalize { get; set; } - public string ExportingLocalize { 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 c0e67eff5a..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,390 +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.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 void WriteItem(FeedFileCreationContext fileCreation, XmlWriter writer, Product product, Currency currency, string measureWeightSystemKey) - { - GoogleProductRecord googleProduct = null; - - try - { - googleProduct = GetGoogleProductRecord(product.Id); - - if (googleProduct != null && !googleProduct.Export) - return; - } - catch (Exception exc) - { - fileCreation.Logger.Error(exc.Message, exc); - } - - writer.WriteStartElement("item"); - - try - { - var manu = _manufacturerService.GetProductManufacturersByProductId(product.Id).FirstOrDefault(); - var mainImageUrl = Helper.GetMainProductImageUrl(fileCreation.Store, product); - 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; @@ -527,9 +125,36 @@ public void UpdateInsert(int pk, string name, string value) case "Pattern": product.Pattern = value; break; - case "Exporting": + 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; @@ -550,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 old 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)"); @@ -573,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], [t3].[value8] AS [Export]" + -" 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]" + -" 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], [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(); @@ -593,201 +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; - x.ExportingLocalize = Helper.GetResource(x.Export == 0 ? "Admin.Common.No" : "Admin.Common.Yes"); + 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; - - // Perf - _dbContext.DetachAll(); - - 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 || taskContext.CancellationToken.IsCancellationRequested) - 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; - }); - } + var descriptor = _pluginFinder.GetPluginDescriptorBySystemName(GoogleMerchantCenterFeedPlugin.SystemName); - public void SetupModel(FeedFroogleModel model) - { - Helper.SetupConfigModel(model, "FeedFroogle"); + 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.GenerateStaticFileEachMinutes = Helper.ScheduleTask.Seconds / 60; - model.TaskEnabled = Helper.ScheduleTask.Enabled; + if (!File.Exists(path)) + path = Path.Combine(fileDir, "taxonomy.en-US.txt"); - model.AvailableCurrencies = Helper.AvailableCurrencies(); - model.AvailableGoogleCategories = GetTaxonomyList(); + string[] lines = File.ReadAllLines(path, Encoding.UTF8); - var urlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext); - model.GridEditUrl = urlHelper.Action("GoogleProductEdit", "FeedFroogle", - new { Namespaces = "SmartStore.GoogleMerchantCenter.Controllers", area = "SmartStore.GoogleMerchantCenter" }); + return lines; + } + catch (Exception exc) + { + exc.Dump(); + } - model.GridPageSize = _adminAreaSettings.GridPageSize; + 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 8ad761ecef..f743627997 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj @@ -42,6 +42,7 @@ + true @@ -81,28 +82,29 @@ 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 @@ -153,7 +155,7 @@ Properties\AssemblyVersionInfo.cs - + @@ -168,20 +170,24 @@ 201504211854125_IsActive.cs + + + 201601061649324_IsBundle.cs + - - - - + + + + - + @@ -205,7 +211,10 @@ 201403112356126_Initial.cs - + + PreserveNewest + + PreserveNewest @@ -214,22 +223,22 @@ 201504211854125_IsActive.cs + + 201601061649324_IsBundle.cs + - - Always - - - Always + + PreserveNewest PreserveNewest - Always + PreserveNewest - Always + PreserveNewest PreserveNewest @@ -257,7 +266,7 @@ - + PreserveNewest @@ -266,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 3720c9df24..0000000000 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedFroogle/Configure.cshtml +++ /dev/null @@ -1,620 +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.Exporting).ClientTemplate(Html.XEditableLink("Exporting", "select2")); - 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)) - .PreserveGridState() - .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 fa3273da09..0000000000 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedFroogle/ProductEditTab.cshtml +++ /dev/null @@ -1,111 +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.Exporting) - - @Html.EditorFor(m => m.Exporting) - @Html.ValidationMessageFor(m => m.Exporting) -
    - @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 5b2fb699b8..2c1f7000cf 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md @@ -1,9 +1,39 @@ -#Release Notes# +#Release Notes -##Google Merchant Center (GMC) 2.2.0.1## +##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## +##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 098fe232fa..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 46b8ba77d4..ba87d0f098 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/web.config +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/web.config @@ -1,117 +1,117 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - \ No newline at end of file + 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 869d27cb75..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: 2.2.0 -MinAppVersion: 2.2.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 015c3b66c5..9083d62e99 100644 --- a/src/Plugins/SmartStore.OfflinePayment/SmartStore.OfflinePayment.csproj +++ b/src/Plugins/SmartStore.OfflinePayment/SmartStore.OfflinePayment.csproj @@ -42,6 +42,7 @@ + true @@ -81,19 +82,18 @@ 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 @@ -145,6 +145,7 @@ + @@ -214,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 d24ebf5f1f..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 46b8ba77d4..ba87d0f098 100644 --- a/src/Plugins/SmartStore.OfflinePayment/web.config +++ b/src/Plugins/SmartStore.OfflinePayment/web.config @@ -1,117 +1,117 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - \ No newline at end of file + 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 d0a5d8434d..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,221 +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.HasNewPaymentNotification = true; - - 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 a1e1b10db2..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,264 +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.HasNewPaymentNotification = true; - - 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)) @@ -387,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); @@ -429,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); @@ -451,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; } @@ -471,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); @@ -480,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); @@ -491,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"); @@ -503,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 = "" }); @@ -517,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 = "" }); @@ -532,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); @@ -552,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 10aa50046e..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,289 +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.HasNewPaymentNotification = true; - - 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 84e4594446..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: 2.2.0.1 -MinAppVersion: 2.2.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/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 1efcc29c03..e82c4a4097 100644 --- a/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj +++ b/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj @@ -34,6 +34,7 @@ + true @@ -55,19 +56,24 @@ 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 + + @@ -112,23 +118,33 @@ Properties\AssemblyVersionInfo.cs + + + + + + + + + + - - + + + - @@ -137,6 +153,7 @@ Settings.settings + @@ -168,6 +185,9 @@ + + PreserveNewest + PreserveNewest @@ -179,9 +199,6 @@ PreserveNewest - - Always - Always @@ -264,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) - + + +
    + @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/changelog.md b/src/Plugins/SmartStore.PayPal/changelog.md index cf618f7d59..e074479a1d 100644 --- a/src/Plugins/SmartStore.PayPal/changelog.md +++ b/src/Plugins/SmartStore.PayPal/changelog.md @@ -1,13 +1,56 @@ -#Release Notes# +#Release Notes + +##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 +###New Features * Supports order list label for new incoming IPNs -##Paypal 1.22## -###Bugfixes### +##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 6025774f20..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 009e149619..f6daa0fb4b 100644 --- a/src/Plugins/SmartStore.PayPal/web.config +++ b/src/Plugins/SmartStore.PayPal/web.config @@ -50,7 +50,7 @@ - + @@ -66,7 +66,7 @@ - + @@ -82,7 +82,7 @@ - + @@ -90,11 +90,11 @@ - + - + @@ -132,6 +132,14 @@ + + + + + + + + - \ No newline at end of file + 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 e4b70d8120..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: 2.2.0 -MinAppVersion: 2.2.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 a4eb6d3def..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; diff --git a/src/Plugins/SmartStore.Shipping/Providers/FixedRateProvider.cs b/src/Plugins/SmartStore.Shipping/Providers/FixedRateProvider.cs index fc73a0c460..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) 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 59f1bdcdf0..3a9b8863dd 100644 --- a/src/Plugins/SmartStore.Shipping/SmartStore.Shipping.csproj +++ b/src/Plugins/SmartStore.Shipping/SmartStore.Shipping.csproj @@ -42,6 +42,7 @@ + true @@ -81,19 +82,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 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 diff --git a/src/Plugins/SmartStore.Shipping/Views/ByTotal/Configure.cshtml b/src/Plugins/SmartStore.Shipping/Views/ByTotal/Configure.cshtml index 779738d741..ae05d59b64 100644 --- a/src/Plugins/SmartStore.Shipping/Views/ByTotal/Configure.cshtml +++ b/src/Plugins/SmartStore.Shipping/Views/ByTotal/Configure.cshtml @@ -146,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 7605330a41..1a1ea4e95b 100644 --- a/src/Plugins/SmartStore.Shipping/packages.config +++ b/src/Plugins/SmartStore.Shipping/packages.config @@ -1,8 +1,8 @@  - - - + + + diff --git a/src/Plugins/SmartStore.Shipping/web.config b/src/Plugins/SmartStore.Shipping/web.config index ad003b3e26..c0f82db177 100644 --- a/src/Plugins/SmartStore.Shipping/web.config +++ b/src/Plugins/SmartStore.Shipping/web.config @@ -1,116 +1,116 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - \ No newline at end of file + diff --git a/src/Plugins/SmartStore.ShippingByWeight/ByWeightShippingComputationMethod.cs b/src/Plugins/SmartStore.ShippingByWeight/ByWeightShippingComputationMethod.cs index 48d3ef8851..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; } - #region Utilities + #endregion - private decimal? GetRate(decimal subTotal, decimal weight, int shippingMethodId, int storeId, int countryId) + #region Utilities + + 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) 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 9dd67c0819..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: 2.2.0 -MinAppVersion: 2.2.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 561149e08f..990c0f2f61 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/SmartStore.ShippingByWeight.csproj +++ b/src/Plugins/SmartStore.ShippingByWeight/SmartStore.ShippingByWeight.csproj @@ -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 @@ -158,6 +156,10 @@ 201406041606037_SmallQuantitySurcharge.cs + + + 201506290747117_ZipCode.cs + @@ -219,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 3433b37a24..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 = ""; +} @@ -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}"); @@ -61,13 +61,11 @@

    @using (Html.BeginForm()) -{ - +{ -
    - @T("Plugins.Shipping.ByWeight.AddNewRecordTitle") -
    +
    + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + - + - -
    +
    +
    @T("Plugins.Shipping.ByWeight.AddNewRecordTitle")
    +
    +
    @Html.SmartLabelFor(model => model.AddStoreId) @@ -95,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 7605330a41..1a1ea4e95b 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/packages.config +++ b/src/Plugins/SmartStore.ShippingByWeight/packages.config @@ -1,8 +1,8 @@  - - - + + + diff --git a/src/Plugins/SmartStore.ShippingByWeight/web.config b/src/Plugins/SmartStore.ShippingByWeight/web.config index ad003b3e26..c0f82db177 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/web.config +++ b/src/Plugins/SmartStore.ShippingByWeight/web.config @@ -1,116 +1,116 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - \ No newline at end of file + 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 245ae96884..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: 2.2.0 -MinAppVersion: 2.2.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 4c9be02213..3f4f325f97 100644 --- a/src/Plugins/SmartStore.Tax/SmartStore.Tax.csproj +++ b/src/Plugins/SmartStore.Tax/SmartStore.Tax.csproj @@ -42,6 +42,7 @@ + true @@ -81,23 +82,23 @@ 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 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 fa6b3ec07d..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 46b8ba77d4..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 index 232860f3c8..d2b92900b9 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/Api/UploadsController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/Api/UploadsController.cs @@ -1,16 +1,24 @@ 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; @@ -18,29 +26,56 @@ 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> PostProductImages() + public async Task> ProductImages() { if (!Request.Content.IsMimeMultipartContent()) { @@ -56,10 +91,10 @@ public async Task> PostProductImages() { await Request.Content.ReadAsMultipartAsync(provider); } - catch (Exception exc) + catch (Exception exception) { provider.DeleteLocalFiles(); - throw this.ExceptionInternalServerError(exc); + throw this.ExceptionInternalServerError(exception); } // find product entity @@ -82,7 +117,7 @@ public async Task> PostProductImages() if (entity == null) { provider.DeleteLocalFiles(); - throw this.ExceptionNotFound(WebApiGlobal.Error.EntityNotFound.FormatWith(identifier.NaIfEmpty())); + throw this.ExceptionNotFound(WebApiGlobal.Error.EntityNotFound.FormatInvariant(identifier.NaIfEmpty())); } // process images @@ -97,13 +132,7 @@ public async Task> PostProductImages() foreach (var file in provider.FileData) { - var image = new UploadImage - { - FileName = file.Headers.ContentDisposition.FileName.ToUnquoted(), - Name = file.Headers.ContentDisposition.Name.ToUnquoted(), - MediaType = file.Headers.ContentType.MediaType.ToUnquoted(), - ContentDisposition = file.Headers.ContentDisposition.Parameters - }; + var image = new UploadImage(file.Headers); if (image.FileName.IsEmpty()) image.FileName = entity.Name; @@ -113,14 +142,13 @@ public async Task> PostProductImages() 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); + var newPicture = _pictureService.Value.InsertPicture(pictureBinary, image.MediaType, seoName, true, false, false); if (newPicture != null) { @@ -155,5 +183,121 @@ public async Task> PostProductImages() 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 e0a6d49da2..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.GetPreselectedPrice(entity); + 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 30c9c98295..4fb254bbff 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/WebApiController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/WebApiController.cs @@ -4,6 +4,8 @@ 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; @@ -19,36 +21,36 @@ public class WebApiController : PluginControllerBase private readonly WebApiSettings _webApiSettings; private readonly IWebApiPluginService _webApiPluginService; private readonly AdminAreaSettings _adminAreaSettings; - private readonly ICommonServices _commonServices; + private readonly ICommonServices _services; public WebApiController( WebApiSettings settings, IWebApiPluginService webApiPluginService, AdminAreaSettings adminAreaSettings, - ICommonServices commonServices) + ICommonServices services) { _webApiSettings = settings; _webApiPluginService = webApiPluginService; _adminAreaSettings = adminAreaSettings; - _commonServices = commonServices; + _services = services; } private bool HasPermission(bool notify = true) { - bool hasPermission = _commonServices.Permissions.Authorize(WebApiPermissionProvider.ManageWebApi); + bool hasPermission = _services.Permissions.Authorize(WebApiPermissionProvider.ManageWebApi); if (notify && !hasPermission) - NotifyError(_commonServices.Localization.GetResource("Admin.AccessDenied.Description")); + NotifyError(_services.Localization.GetResource("Admin.AccessDenied.Description")); return hasPermission; } private void AddButtonText() { - ViewData["ButtonTextEnable"] = _commonServices.Localization.GetResource("Plugins.Api.WebApi.Activate"); - ViewData["ButtonTextDisable"] = _commonServices.Localization.GetResource("Plugins.Api.WebApi.Deactivate"); - ViewData["ButtonTextRemoveKeys"] = _commonServices.Localization.GetResource("Plugins.Api.WebApi.RemoveKeys"); - ViewData["ButtonTextCreateKeys"] = _commonServices.Localization.GetResource("Plugins.Api.WebApi.CreateKeys"); + ViewData["ButtonTextEnable"] = _services.Localization.GetResource("Plugins.Api.WebApi.Activate"); + ViewData["ButtonTextDisable"] = _services.Localization.GetResource("Plugins.Api.WebApi.Deactivate"); + ViewData["ButtonTextRemoveKeys"] = _services.Localization.GetResource("Plugins.Api.WebApi.RemoveKeys"); + ViewData["ButtonTextCreateKeys"] = _services.Localization.GetResource("Plugins.Api.WebApi.CreateKeys"); } public ActionResult Configure() @@ -84,7 +86,7 @@ public ActionResult SaveGeneralSettings(WebApiConfigModel model) return AccessDeniedPartialView(); model.Copy(_webApiSettings, false); - _commonServices.Settings.SaveSetting(_webApiSettings); + _services.Settings.SaveSetting(_webApiSettings); WebApiCachingControllingData.Remove(); @@ -95,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 53b6ca55d7..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: 2.2.0.2 +Version: 2.6.0 Group: Api -MinAppVersion: 2.2.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/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 index 5ad5933fc9..cca701246b 100644 --- a/src/Plugins/SmartStore.WebApi/Models/Api/UploadImage.cs +++ b/src/Plugins/SmartStore.WebApi/Models/Api/UploadImage.cs @@ -1,42 +1,19 @@ -using System.Collections.Generic; -using System.Net.Http.Headers; +using System.Net.Http.Headers; using System.Runtime.Serialization; using SmartStore.Core.Domain.Media; namespace SmartStore.WebApi.Models.Api { [DataContract] - public partial class UploadImage + public partial class UploadImage : UploadFileBase { - /// - /// 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; } - - /// - /// Media (mime) type of content-type multipart header - /// - [DataMember] - public string MediaType { get; set; } + public UploadImage() + { + } - /// - /// Indicates whether the uploaded image already exist and therefore has been skipped - /// - [DataMember] - public bool Exists { get; set; } - - /// - /// Indicates whether the uploaded image has been inserted - /// - [DataMember] - public bool Inserted { get; set; } + public UploadImage(HttpContentHeaders headers) : base(headers) + { + } /// /// Url of the default size image @@ -56,12 +33,6 @@ public partial class UploadImage [DataMember] public string FullSizeImageUrl { get; set; } - /// - /// Raw custom parameters of the content-disposition multipart header - /// - [DataMember] - public ICollection ContentDisposition { get; set; } - /// /// The picture entity. Can be null. /// 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/SmartStore.WebApi.csproj b/src/Plugins/SmartStore.WebApi/SmartStore.WebApi.csproj index 114a7f15ce..73e315200d 100644 --- a/src/Plugins/SmartStore.WebApi/SmartStore.WebApi.csproj +++ b/src/Plugins/SmartStore.WebApi/SmartStore.WebApi.csproj @@ -49,6 +49,7 @@ + true @@ -77,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 @@ -103,11 +107,14 @@ ..\..\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 @@ -204,6 +211,9 @@ + + + @@ -221,7 +231,9 @@ + + @@ -311,7 +323,6 @@ - +
    +
    + @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 b5ed46eae0..2ad6b7fb2e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/AllSettings.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/AllSettings.cshtml @@ -63,7 +63,6 @@ } function grid_onStoreEdit(e) { - @* codehint: sm-edit *@ $(e.form).find('#Store').select2('val', e.dataItem['StoreId']); } 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 a55ae23614..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(); @@ -144,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") @@ -163,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()); })) @@ -244,8 +249,7 @@ @Html.ValidationMessageFor(model => model.IgnoreFeaturedProducts) - - + @Html.SmartLabelFor(model => model.CompareProductsEnabled) @@ -290,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) @@ -322,237 +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.SubCategoryDisplayType) - - @Html.SettingOverrideCheckbox(model => Model.SubCategoryDisplayType) - @Html.DropDownListFor(model => model.SubCategoryDisplayType, Model.AvailableSubCategoryDisplayTypes) - @Html.ValidationMessageFor(model => model.SubCategoryDisplayType) -
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    @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() { - +
    + + + + + + + + + + +
    @Html.SmartLabelFor(model => model.ShowProductReviewsInProductLists) @@ -630,7 +742,7 @@ @helper TabProductDetailSettings() { - +
    + + + + - - + @@ -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.RecentlyViewedProductsEnabled) @@ -703,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) @@ -771,8 +892,7 @@ } @helper TabProductSearchSettings() { - - +
    - + + + + 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.SearchPageProductsPerPage) @@ -810,7 +930,16 @@
    + + @Html.SmartLabelFor(model => model.SearchDescriptions) + + @Html.SettingEditorFor(model => model.SearchDescriptions) + @Html.ValidationMessageFor(model => model.SearchDescriptions) +

    + @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/CurrentWishlists.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ShoppingCart/CurrentWishlists.cshtml index 57b381adaa..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")
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Store/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Store/List.cshtml index ff0728c169..b3832779c4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Store/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Store/List.cshtml @@ -23,13 +23,17 @@ .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")) - .Centered(); + .Centered() + .Width(80); columns.Bound(x => x.DisplayOrder) .Centered(); }) 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 ffcf8688ca..15e975c4ce 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Theme/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Theme/List.cshtml @@ -5,7 +5,6 @@ @using SmartStore.Core.Themes; @{ - //page title ViewBag.Title = T("Admin.Configuration.Themes").Text; } @@ -17,6 +16,8 @@ @T("Admin.Configuration.Themes")
    + @Html.Widget("admin_button_toolbar_before") +
    @@ -227,7 +230,7 @@ - +
    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.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 7d3a217dd0..7f12a993a8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Web.config +++ b/src/Presentation/SmartStore.Web/Administration/Web.config @@ -44,7 +44,7 @@ - + @@ -64,7 +64,7 @@ - + @@ -92,7 +92,7 @@ - + @@ -100,12 +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 a9d0066006..7e1b1648ae 100644 --- a/src/Presentation/SmartStore.Web/Administration/packages.config +++ b/src/Presentation/SmartStore.Web/Administration/packages.config @@ -1,10 +1,11 @@  - - - - + + + + + @@ -14,6 +15,6 @@ - + \ 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/Localization/App/de/all.smres.xml b/src/Presentation/SmartStore.Web/App_Data/Localization/App/de/all.smres.xml index b492367ecf..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 @@ -783,6 +801,9 @@ Telefonnummer wird benötigt + + * Eingabefelder mit Sternchen sind Pflichfelder und müssen ausgefüllt werden. + Bundesland/Region @@ -1359,6 +1380,9 @@ Die Warengruppe wurde gelöscht. + + Andere Beschreibung anzeigen + Rabatte @@ -1405,10 +1429,10 @@ Gelöscht - Beschreibung + Obere Beschreibung - Beschreibung der Warengruppe + Beschreibung der Warengruppe, die auf der Warengruppenseite oberhalb der Produkte angezeigt wird. Reihenfolge @@ -1467,6 +1491,9 @@ Legt die Vater-Warengruppe fest. Lassen Sie das Feld leer, um eine Warengruppe erster Ebene zu erzeugen. + + Übergeordnete Warengruppe + Bild @@ -1924,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. @@ -1970,7 +2000,7 @@ Checkout-Selling - Hinzufügen + Checkout-Selling-Produkt hinzufügen Produkt @@ -2026,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 @@ -2090,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. @@ -2197,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 @@ -2215,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 @@ -2269,6 +2317,9 @@ Die Länge des Produktes. + + Niedrigster Attributkombinationspreis + Aktion bei Erreichen des Meldebestandes @@ -2338,6 +2389,12 @@ Produktname ist erforderlich + + Summe nicht genehmigter Bewertungen + + + Summe nicht genehmigter Rezensionen + Benachrichtigt den Administrator, wenn die Mindestmenge unterschritten wird. @@ -2560,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 @@ -2825,7 +2894,7 @@ Cross-Selling - Hinzufügen + Cross-Selling-Produkt hinzufügen Reihenfolge @@ -2834,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 @@ -2932,6 +3001,15 @@ 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 @@ -2947,23 +3025,14 @@ Update verfügbar - - Ihre Version - Jetzt aktualisieren - - AutoUpdate möglich - - - Unbekannter Fehler beim Paket-Download. Bitte versuchen Sie es später erneut. - - - <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> + + Ihre Version - Über + Über SmartStore.NET Aktionen @@ -3028,12 +3097,15 @@ Es wurden {0} gegenseitige Zuordnung(en) erstellt. - - CSV-Datei + + CSV Konfiguration Die Daten wurden erfolgreich geändert. + + Datenaustausch + Die Daten wurden erfolgreich gespeichert. @@ -3046,35 +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 @@ -3091,20 +3187,29 @@ Nur Ausgewählte nach PDF exportieren - - Inhaltsverzeichnis - 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 - - Nur Ausgewählte als XML exportieren + + {0} Dateien wurden gelöscht + + + Die Datei muss vom Typ {0} sein. + + + {0} Verzeichnisse wurden gelöscht + + + FTP-Status {0} ({1}). Allgemein @@ -3130,41 +3235,17 @@ Verbergen - - Von CSV-Datei importieren - - - Von Excel importieren - - - Aktiv seit: {0}. - - - Import abbrechen - - - Import wurde abgebrochen + + HTTP-Status {0} ({1}). - - Soll der aktive Importvorgang wirklich abgebrochen werden? Bislang importierte Produkte werden nicht gelöscht. + + Ignorieren - - Vollständigen Bericht runterladen... + + Importdatei - - Der Import läuft jetzt im Hintergrund . Sie können den Fortschritt bzw. das Ergebnis des letzten Importvorganges jederzeit im Import-Dialog einsehen. - - - <b>Letzter Import</b>: {0}{1}. - - - Kein Bericht verfügbar - - - {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 @@ -3172,6 +3253,9 @@ Info + + Letzte Ausführung + Lizenzieren @@ -3184,18 +3268,51 @@ 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 @@ -3205,6 +3322,9 @@ Sie müssen die Anwendung neu starten, damit die Änderungen wirksam werden. + + Einschränkungen + Speichern @@ -3214,6 +3334,12 @@ Suchen + + Ausgewählte + + + Jetzt senden + Suchmaschinen (SEO) @@ -3223,6 +3349,12 @@ Anzeigen + + Werte für Überspringen und Begrenzen müssen größer oder gleich 0 sein. + + + Ausgelassen + Standard @@ -3253,18 +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. @@ -3274,6 +3418,9 @@ Bitte warten, die Verarbeitung läuft... + + Warnungen + Falsche E-Mail @@ -3286,6 +3433,9 @@ Zugriffsrechte + + Es sind keine Kundengruppen definiert + Zugriffsrecht @@ -3304,9 +3454,6 @@ Filtert Ereignisse nach Ereignistypen. - - Ereignistyp - Nachricht @@ -3331,6 +3478,15 @@ Kunden-E-Mail + + Filtert Ergebnisse nach E-Mail-Adresse der Kunden. + + + Kundensystemkonto + + + Filtert Ergebnisse nach Kundenystemkonten. + Aktivitätstypen @@ -3346,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 @@ -3490,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. @@ -3578,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 @@ -3631,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 @@ -3698,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 @@ -3721,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 @@ -3889,6 +4069,9 @@ Die E-Mail wurde erfolgreich versandt. + + Test der E-Mail-Funktion. + Das E-Mail-Konto wurde erfolgreich bearbeitet. @@ -3929,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 @@ -3974,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 @@ -4115,7 +4298,7 @@ Zahlung - Das Plugin erlaubt keine Aktivierung dieser Zahlungsmethode. + Das Plugin erlaubt keine Aktivierung dieser Zahlungsart. Zahlungsarten @@ -4127,10 +4310,10 @@ Konfiguration - Wiederkehrender Zahlungstyp + Wiederkehrende Zahlungen - Unterstützt Capture + Buchung möglich Teilerstattung möglich @@ -4139,7 +4322,22 @@ 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 @@ -4270,6 +4468,9 @@ Das Plugin wurde deinstalliert. + + Beim Aufruf eines Plugins ist ein unbekannter Fehler aufgetreten. Details entnehmen Sie bitte der folgenden Meldung. + Verpackungseinheiten @@ -4300,6 +4501,9 @@ Regionale Einstellungen + + Sie müssen zunächst speichern, bevor Sie Einschränkungen festlegen können. + Einstellungen @@ -4339,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 @@ -4361,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 @@ -4420,6 +4630,12 @@ Legt die Anzahl der dargestellten Produkte pro Seite fest. + + Standardsortierreihenfolge für Produkte + + + Legt die Standardsortierreihenfolge für Produkte fest. + Standard Listendarstellung @@ -4474,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 @@ -4549,6 +4783,12 @@ Anzahl der angezeigten Produkt-Tags + + Preisanzeige + + + Legt fest, ob bzw. welcher Typ von Preis in Produktlisten angezeigt werden soll. + Produktdetail @@ -4666,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 @@ -4756,6 +5002,24 @@ 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 @@ -4804,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 @@ -4978,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 @@ -4996,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 @@ -5026,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 @@ -5095,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 @@ -5173,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 @@ -5308,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 @@ -5650,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 @@ -5704,6 +6064,12 @@ Lokalisierung + + Meta Robots + + + Legt fest, ob und wie Suchmaschinen die Seiten Ihres Shops indexieren. + Seiten-Titel-Anpassung @@ -5758,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 @@ -5812,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 @@ -5872,6 +6250,12 @@ 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 @@ -5950,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 @@ -5992,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... @@ -6002,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 … @@ -6046,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 @@ -6151,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 @@ -6265,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 @@ -6278,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 @@ -6301,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 @@ -6355,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 @@ -6565,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. @@ -6580,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 @@ -6661,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 @@ -6668,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 @@ -6766,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. @@ -7039,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 @@ -7084,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. @@ -7111,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 @@ -7144,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 @@ -7585,6 +8068,9 @@ Zurück zur Gesamtliste + + Die Kundengruppe "{0}" wurde nicht gefunden. + Die Kundengruppe wurde gelöscht. @@ -7768,6 +8254,9 @@ Erstellt am + + Kunden GUID + Kundengruppen @@ -7825,6 +8314,9 @@ IP-Adresse, mit der sich der Kunde zuletzt im Shop aktiv war. + + Ist Systemkonto + Mehrwertsteuerfrei @@ -7837,6 +8329,9 @@ Zeigt an, wann der Kunde zuletzt im Shop aktiv war. + + Letztes Login-Datum + Nachname @@ -7858,6 +8353,9 @@ Passwort des Kunden. + + Passwort Salt + Telefon @@ -8212,25 +8710,748 @@ Dashboard - - Herunterladen der hochgeladenen Datei + + Neues Profil - - Download URL + + Standardwert - - Das Download-Objekt wurde gespeichert + + Eigenschaft des Objektes - - Download entfernen + + Importfeld - - Download speichern + + 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. - - Upload Datei + + 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 + + + Download URL + + + Das Download-Objekt wurde gespeichert + + + Download entfernen + + + Download speichern + + + Upload Datei + + Benutze Download-URL @@ -8386,8 +9607,11 @@ Hilfe + + Dokumentation + - Community-Forum + Community SmartStore.NET ist ein Fork der ASP.NET Open-Source E-Commerce-Lösung {0}. @@ -8434,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 @@ -8441,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 @@ -8465,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 @@ -8474,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} @@ -8483,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 @@ -8548,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 @@ -8555,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 @@ -8585,10 +9821,10 @@ 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 @@ -8759,10 +9995,10 @@ 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 @@ -8858,10 +10094,10 @@ 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 @@ -9100,6 +10336,12 @@ Notiz + + Neue IPN + + + In den Auftragsnotizen ist eine neue Benachrichtigung vom Zahlungsanbieter eingetroffen. + Auftrag als PDF @@ -9502,6 +10744,9 @@ 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 @@ -9751,8 +10996,8 @@ E-Mail-Adresse erforderlich - - Es wurden {0} E-Mail(s) importiert und {1} aktualisiert. + + Abonnement GUID Bereitstellendes Plugin @@ -10279,6 +11524,9 @@ Fügen Sie die SQL-Abfrage hier ein: + + Die SQL Anweisung wurde erfolgreich ausgeführt. + Ausführen @@ -10291,6 +11539,9 @@ Zurück zur Übersicht + + E-Mail Anhang konnte nicht herunterladen: Daten nicht verfügbar. + Die E-Mail wurde gelöscht @@ -10300,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 @@ -10360,6 +11623,12 @@ Priorität eingeben + + Nur manuell senden + + + Legt fest, ob die E-Mail ausschließlich manuell gesendet werden soll. + Gesendet um @@ -10399,6 +11668,9 @@ Der Name des Empfängers. + + Anzahl Anhänge + Enddatum @@ -10421,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 @@ -10429,6 +11701,12 @@ Gibt die Anzahl der Zustellversuche an. + + Lade nur manuell zu sendende E-Mails + + + Lade nur manuell zu sendende E-Mails. + Anfangsdatum @@ -10445,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 - - Standard + + Legt die Sprache des SEO Namens fest. + + + Standard + + + SEO Name + + + Legt den SEO Namen fest. + + + Namen pro Objekt + + + Die Anzahl der SEO Namen pro Objekt. + + + Geplante Aufgabe - - Name + + PDF-Konvertierer - - Der zu findende Name. + + Suchmaschine Systeminformation @@ -10555,6 +11920,12 @@ Der Name des Daten-Providers. + + Aufräumen + + + Der Arbeitsspeicher wurde erfolgreich aufgeräumt. + HTTP_HOST @@ -10591,6 +11962,9 @@ Zeitzone des Servers + + Benutzter Speicher (RAM) + GMT/UTC @@ -10600,6 +11974,12 @@ Warnungen + + Zugriffsverweigerung durch anonyme Anfrage bei {0}. + + + Zugriffsverweigerung durch Kunde #{0} '{1}' bei {2}. + Die Standard-Maßeinheit wurde nicht festgelegt. @@ -10618,6 +11998,9 @@ Die Standard-Gewichtseinheit wurde festgelegt. + + Bitte nur Ziffern eingeben. + Alle Verzeichnisberechtigungen sind OK. @@ -10642,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. @@ -10657,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. @@ -10942,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. @@ -11104,6 +12520,9 @@ Zur Kasse + + Ein anonymer Checkout ist nicht zulässig. + Rechnungsanschrift @@ -11128,12 +12547,21 @@ Weiter einkaufen + + Bitte stimmen Sie der Nutzungsvereinbarung für herunterladbare Produkte zu. + Rechnungsanschrift eingeben 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. @@ -11150,7 +12578,7 @@ Weiter - Keine Zahlungsmethoden verfügbar. + Es sind keine Zahlungsarten verfügbar. Ihre Bestellung ist angekommen @@ -11168,7 +12596,7 @@ Zahlungsinformation - Zahlungsmethode + Zahlungsarten Bestelldetails @@ -11227,6 +12655,9 @@ Übermittele Auftragsinformationen + + Newsletter abonnieren + Geschäftsbedingungen (AGB) @@ -11311,6 +12742,9 @@ Abgebrochen + + Der API-Aufruf zur Prüfung eines CAPTCHAs ist fehlgeschlagen. + Produkt bearbeiten @@ -11332,6 +12766,15 @@ Fortsetzen + + In die Zwischenablage kopieren + + + Kopieren ist fehlgeschlagen. + + + Kopiert! + Anzahl @@ -11344,6 +12787,9 @@ Querverweise + + Die von Ihnen gewählte Kundennummer existiert bereits. Bitte geben Sie eine andere Kundennummer an. + Datum @@ -11362,33 +12808,84 @@ 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 @@ -11416,12 +12913,21 @@ Dateien für den Upload hier ablegen + + URL eingeben + Fehlgeschlagen Datei hochladen + + Filter + + + Versandkostenfrei + Anzeigename @@ -11437,6 +12943,9 @@ Startseite + + Bild + Importieren @@ -11446,12 +12955,21 @@ Aktiv + + Sprache + Liste + + Lade + Lade nächsten Schritt… + + Diese Funktion steht für Gäste nicht zur Verfügung. + Verschiedenes @@ -11461,6 +12979,9 @@ Mehr Info + + Mein + Navigation @@ -11476,6 +12997,12 @@ Nein, abbrechen + + Es sind keine Dateien vorhanden. + + + Es wurde keine Datei hochgeladen. + Der Vorgang wurde aus Sicherheitsgründen nicht ausgeführt. @@ -11485,6 +13012,9 @@ Benachrichtigung + + Nicht auswählbar + Aus @@ -11500,6 +13030,12 @@ Optional + + Optionen + + + Aufteilung + Capture wird nicht unterstützt @@ -11524,6 +13060,24 @@ Artikel + + Profil + + + Projektion + + + Provider + + + öffentlich + + + Veröffentlicht + + + Veröffentlichung + Frage @@ -11536,9 +13090,21 @@ Entfernen + + Ersetzen + + + Die Anfrage konnte nicht ausgeführt werden.<br />Controller: {0}, Action: {1}, Grund: {2}. + + + Regel + Speichern + + Geplant + Suchen @@ -11572,6 +13138,12 @@ Anzeigen + + Alle anzeigen + + + Mehr anzeigen + Verkleinern @@ -11584,11 +13156,23 @@ Systemname + + Nicht verfügbar + undefiniert + + Unbekannt + + + Unveröffentlicht + + + Ungeplant + - nicht spezifiziert + Nicht spezifiziert Geändert am @@ -11605,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 @@ -11659,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. @@ -11668,6 +13267,9 @@ Währungen + + Der Kunde existiert nicht. + Gast @@ -11689,6 +13291,9 @@ Produkt + + Das Produkt besitzt keine Nutzungsvereinbarung. + Ich bin einverstanden. @@ -11698,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 @@ -11764,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 @@ -11815,6 +13435,15 @@ Jahre + + Über der Produktliste + + + Am Seitenende + + + Nicht anzeigen + Benutze CONTAINS und AND mit prefix_term @@ -11845,6 +13474,27 @@ Benutzernamen anzeigen + + Automatisch vergeben + + + Deaktiviert + + + Aktiviert + + + Anzeigen + + + Immer editierbar + + + Editierbar falls leer + + + Nicht anzeigen + Lesbar @@ -11854,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 @@ -11938,6 +13684,24 @@ Warnung + + Aktiviert anzeigen + + + Deaktiviert anzeigen + + + Nicht anzeigen + + + Aktiviert anzeigen + + + Deaktiviert anzeigen + + + Nicht anzeigen + Abgebrochen @@ -12227,9 +13991,6 @@ Forenthemen mit den neuesten Beiträgen - - {0} - Forum: {1} - Forumname @@ -12470,6 +14231,9 @@ Informationen + + Die Installationssprache '{0}' ist nicht registriert. + Bitte geben Sie eine gültige Kreditkartennummer ein. @@ -12719,12 +14483,48 @@ 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 @@ -12737,6 +14537,12 @@ Geschenkgutschein ({0}) + + Für die wiederkehrende Zahlung existiert kein Ausgangsauftrag. + + + Keine wiederkehrenden Produkte. + Notizen @@ -12746,6 +14552,9 @@ Notiz + + Der Auftrag {0} wurde nicht gefunden. + Auftragsnummer @@ -12762,11 +14571,14 @@ Gesamtsumme - Zahlungsmethode + Zahlungsart Zahlungsgebühr + + bestellung-{0}.pdf + Telefon @@ -12878,6 +14690,9 @@ Versandanschrift + + Die Lieferanschrift fehlt. + Versandart @@ -13028,6 +14843,9 @@ Wunschliste per E-Mail an einen Freund senden + + Das Datum der nächsten Zahlung kann nicht ermittelt werden. + Kartencode @@ -13052,6 +14870,9 @@ Falsche Kartennummer + + Die Zahlungsart konnte nicht geladen werden. + Gültig bis @@ -13061,6 +14882,24 @@ Ablaufjahr ist erforderlich + + Die Zahlungsart steht nicht zur Verfügung. + + + Mindestens ein Zahlungsart-Provider muss aktiviert sein. + + + 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. + + + Wiederkehrende Zahlung ist inaktiv. + + + Wiederkehrende Zahlungen sind für die gewählte Zahlungsart nicht möglich. + + + Der Typ von wiederkehrender Zahlung wird nicht unterstützt. + Wähle Kreditkarte aus @@ -13178,12 +15017,6 @@ Gewicht - - Gruppierte Produkte - - - Produktset besteht aus - E-Mail @@ -13199,32 +15032,14 @@ Kontaktdaten - - Höhe - - - Länge - - - Hersteller - - - Preis - - - Artikelnummer - - - Spezifikation - Lagermenge - - Gewicht + + Eine Umfrageantwort {0} wurde nicht gefunden. - - Breite + + Die Umfrage ist nicht verfügbar. Nur registrierte Benutzer können abstimmen @@ -13247,6 +15062,9 @@ Datenschutzerklärung + + Private Nachrichten sind deaktiviert. + Posteingang @@ -13370,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 @@ -13575,7 +15399,10 @@ Dieses Produkt ist ausverkauft. - Dieses Produkt ist ausverkauft. + Keine Bundle-Bestandteile vorhanden + + + Das Produkt {0} wurde nicht gefunden. Preis: @@ -13655,6 +15482,9 @@ Menge + + Die Produktvariante {0} wurde nicht gefunden. + Gewicht @@ -13700,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 @@ -13790,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. @@ -13904,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 @@ -14036,6 +15884,9 @@ Die eingegebene Nummer des Geschenkgutscheines konnte nicht angewendet werden. + + Der Warenkorb ist deaktiviert. + Gesamt @@ -14148,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 @@ -14229,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. @@ -14252,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> @@ -14621,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 @@ -14651,9 +16550,33 @@ Vor einem Jahr + + Sekunde + + + Sek. + + + Sekunden + + + Sek. + Vor {0} Sekunden + + Woche + + + Wochen + + + Jahr + + + Jahre + Vor {0} Jahren @@ -14714,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 9e3f271712..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 @@ -201504232202590_AutoUpdateRes \ 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 45d1081077..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}') @@ -783,6 +801,9 @@ Phone number is required. + + * Input elements with asterisk are required and have to be filled out. + State / province @@ -1395,6 +1416,9 @@ The category has been deleted successfully. + + Show other description + Discounts @@ -1444,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 @@ -1506,6 +1530,9 @@ Select a parent category for this category. Leave this field empty to make this the root level category + + Parent category + Picture @@ -1522,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 @@ -1534,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 @@ -1702,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 @@ -1984,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. @@ -2015,7 +2045,7 @@ Copy images - Check to copy the images. + Check the box the box to copy the images. New product name @@ -2033,7 +2063,7 @@ Cross-sells - Add new cross-sell product + Add checkout-selling product Product @@ -2042,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. @@ -2087,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 @@ -2095,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 @@ -2105,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 @@ -2117,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 @@ -2159,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. @@ -2192,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 @@ -2210,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 @@ -2266,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 @@ -2284,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 @@ -2294,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 @@ -2306,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 @@ -2338,6 +2386,9 @@ The length of the product. + + Lowest attribute combination price + Low stock activity @@ -2407,6 +2458,12 @@ Please provide a name. + + Not approved rating sum + + + Not approved total reviews + Notify admin for quantity below @@ -2474,7 +2531,7 @@ 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 @@ -2507,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 @@ -2531,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 @@ -2591,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 @@ -2632,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 @@ -2771,7 +2840,7 @@ Pictures - Check the images that shows this attribute combination + Check the box of the images that show this attribute combination # @@ -2903,7 +2972,7 @@ Related products - Add new related product + Add cross-selling product Display order @@ -2915,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 @@ -2945,7 +3014,7 @@ Show on product page - Check to display the attribute in the public product detail page + Check the box to display the attribute in the public product detail page Attribute @@ -3025,6 +3094,15 @@ 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 @@ -3040,26 +3118,17 @@ Update available - - Your version - Update now - - AutoUpdate possible - - - Unknown error during package download. Please try again later. - - - <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> + + Your version Actions - About + About SmartStore.NET Actions @@ -3124,12 +3193,15 @@ There were {0} mutual association(s) created. - - CSV file + + CSV Configuration The data was successfully changed. + + Data exchange + The data were saved successfully. @@ -3142,35 +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 @@ -3187,20 +3283,29 @@ Export to PDF (selected) - - Table of contents - 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. + + + File not found + + + {0} files were deleted + + + The file must be of the type {0}. - - Export to XML (selected) + + {0} folders were deleted + + + FTP status {0} ({1}). General @@ -3226,41 +3331,17 @@ Hide - - Import from CSV - - - Import from Excel - - - Active since: {0}. - - - Cancel import - - - Import process has been cancelled - - - Do you really want to cancel the import? Products imported so far will not be removed. - - - 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. + + HTTP status {0} ({1}). - - <b>Last import</b>: {0}{1}. + + Ignore - - No report available + + Import file - - {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 @@ -3268,6 +3349,9 @@ Info + + Last run + License @@ -3280,18 +3364,51 @@ 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 @@ -3301,6 +3418,9 @@ You must restart the application for the changes to take effect. + + Restrictions + Save @@ -3310,6 +3430,12 @@ Search + + Selected + + + Send now + Search engines (SEO) @@ -3319,6 +3445,12 @@ Show + + Values for skip and limit must be greater than or equal to 0. + + + Skipped + Standard @@ -3349,18 +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 @@ -3370,6 +3514,9 @@ Please wait while processing is running... + + Warnings + Wrong email @@ -3382,6 +3529,9 @@ Access control list + + No customer roles defined + Permission name @@ -3403,9 +3553,6 @@ The Activity Log Type. - - Activity Log Type - Message @@ -3430,6 +3577,15 @@ Customer Email + + Filters results by customer email address. + + + Customer system account + + + Filters results by customer system accounts. + Activity Types @@ -3445,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 @@ -3595,6 +3769,9 @@ back to country list + + The country cannot be deleted because it has associated addresses. + The country has been deleted successfully. @@ -3686,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 @@ -3742,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 @@ -3812,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 @@ -3835,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 @@ -3959,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 @@ -3998,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 @@ -4015,6 +4195,9 @@ Email has been successfully sent. + + Testing email functionality. + The email account has been updated successfully. @@ -4061,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 @@ -4094,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 @@ -4106,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 @@ -4271,19 +4454,34 @@ 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 @@ -4423,6 +4621,9 @@ The plugin has been uninstalled. + + An unknown error occurred when calling a plugin. Please refer to the following message for details. + Quantity units @@ -4453,6 +4654,9 @@ Regional Settings + + You need to save before you can specify restrictions. + Settings @@ -4487,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) @@ -4517,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 @@ -4526,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 @@ -4562,20 +4772,26 @@ 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 @@ -4598,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 @@ -4628,7 +4844,25 @@ 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 @@ -4640,13 +4874,13 @@ 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 @@ -4658,13 +4892,13 @@ 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 @@ -4691,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 @@ -4705,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 @@ -4715,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 @@ -4757,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 @@ -4790,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' @@ -4814,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' @@ -4822,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 @@ -4838,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 @@ -4880,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 @@ -4892,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 @@ -4910,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 @@ -4934,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 @@ -4958,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 @@ -4985,7 +5261,7 @@ 'City' required - Check if 'City' is required. + Check the box if 'City' is required. 'Company' enabled @@ -4997,7 +5273,7 @@ 'Company' required - Check if 'Company' is required. + Check the box if 'Company' is required. 'Country' enabled @@ -5018,7 +5294,7 @@ 'Fax number' required - Check if 'Fax number' is required. + Check the box if 'Fax number' is required. 'Phone number' enabled @@ -5030,7 +5306,7 @@ 'Phone number' required - Check if 'Phone number' is required. + Check the box if 'Phone number' is required. 'State/province' enabled @@ -5048,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 @@ -5060,7 +5336,7 @@ 'Street address' required - Check if 'Street address' is required. + Check the box if 'Street address' is required. 'Zip / postal code' enabled @@ -5072,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 @@ -5114,7 +5390,7 @@ 'City' required - Check if 'City' is required. + Check the box if 'City' is required. 'Company' enabled @@ -5126,7 +5402,7 @@ 'Company' required - Check if 'Company' is required. + Check the box if 'Company' is required. 'Country' enabled @@ -5134,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 @@ -5152,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 @@ -5182,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 @@ -5201,7 +5513,7 @@ 'Fax number' required - Check if 'Fax number' is required + Check the box if 'Fax number' is required 'Gender' enabled @@ -5213,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 @@ -5249,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 @@ -5285,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 @@ -5297,19 +5615,19 @@ '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 @@ -5327,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 @@ -5360,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 @@ -5402,7 +5738,7 @@ Forums enabled - Check to enable forums. + Check the box to enable forums. Notify about private messages @@ -5473,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 @@ -5528,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. @@ -5537,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 @@ -5771,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 @@ -5818,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 @@ -5872,6 +6232,12 @@ Localization settings + + Meta robots + + + Specifies if and how search engines indexing the pages of your store. + Page title SEO adjustment @@ -5888,7 +6254,7 @@ Enabled - Check to enabled PDF. + Check the box to enabled PDF. Use Letter page size @@ -5926,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 @@ -5969,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 @@ -6040,6 +6418,12 @@ 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 @@ -6104,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 @@ -6118,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 @@ -6128,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. @@ -6152,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 @@ -6160,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 @@ -6182,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 @@ -6214,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 @@ -6239,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 @@ -6272,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 @@ -6319,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 @@ -6326,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' @@ -6365,13 +6773,13 @@ 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 @@ -6401,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 @@ -6419,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 @@ -6431,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 @@ -6446,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 @@ -6467,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 @@ -6523,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 @@ -6566,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 @@ -6590,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 @@ -6650,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 @@ -6736,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. @@ -6754,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 @@ -6839,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 @@ -6846,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 @@ -6947,6 +7390,9 @@ Name + + Theme requires no configuration + Variables were successfully updated. @@ -6975,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 @@ -6999,7 +7445,7 @@ Mobile devices supported - Check to enable mobile devices support. + Check the box to enable mobile devices support. Save theme choice in cookie @@ -7235,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. @@ -7283,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. @@ -7313,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 @@ -7346,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 @@ -7545,7 +8027,7 @@ Allow guests to vote - Check to allow guests to vote. + Check the box to allow guests to vote. Display order @@ -7584,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 @@ -7635,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 @@ -7817,6 +8299,9 @@ back to customer role list + + The customer role "{0}" cannot be found. + The customer role has been deleted successfully. @@ -7833,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 @@ -7872,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. @@ -8009,6 +8494,9 @@ The date the customer record is created. + + Customer GUID + Customer roles @@ -8066,6 +8554,9 @@ The last used IP address. + + Is system account + Is tax exempt @@ -8078,6 +8569,9 @@ The last activity date. + + Last login date + Last name @@ -8099,6 +8593,9 @@ The customer's password. + + Password salt + Phone @@ -8471,29 +8968,752 @@ Store Statistics - - Download uploaded file + + New profile - - Download uploaded file + + Default Value - - Download URL + + Object property - - Download object is saved + + Import Field - - Remove download + + 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. - - Save download + + The stored field assignments are invalid due to the change of the delimiter and were reset. - - Upload file + + Delimiter - - Use download URL + + 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 + + + Download uploaded file + + + Download URL + + + Download object is saved + + + Remove download + + + Save download + + + Upload file + + + Use download URL Gift cards @@ -8657,8 +9877,11 @@ Help + + Documentation + - Community forums + Community SmartStore.NET is a fork of the ASP.NET open source e-commerce solution {0}. @@ -8708,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 @@ -8715,7 +9944,7 @@ Order has been captured - Error capturing order #{0}. Error: {1} + Unable to capture payment for order {0}. Order has been deleted @@ -8739,7 +9968,7 @@ Order has been partially refunded. Amount = {0} - Unable to partially refund order. {0} + Unable to partially refund order {0}. Order placed @@ -8748,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} @@ -8757,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 @@ -8828,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 @@ -9042,10 +10277,10 @@ 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 @@ -9389,6 +10624,12 @@ Note + + New IPN + + + A new notification from the payment provider has arrived in the order notes. + Order as PDF @@ -9800,6 +11041,9 @@ 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 @@ -10058,8 +11302,8 @@ Email is required. - - {0} email(s) were imported and {1} updated. + + Subscription GUID Providing plugin @@ -10607,6 +11851,9 @@ Insert your SQL query here + + The SQL command was executed successfully. + Execute @@ -10619,6 +11866,9 @@ back to message queue + + Could not download e-mail attachment: no data. + The queued email has been deleted successfully. @@ -10628,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 @@ -10691,6 +11953,12 @@ Enter priority. + + Only send manually + + + Indicates whether the email should only be send manually. + Sent on @@ -10733,6 +12001,9 @@ End date + + Number of attachments + End date @@ -10755,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 @@ -10763,6 +12034,12 @@ The maximum number of attempts to send a message. + + Load emails manually send only + + + Load emails manually send only. + Start date @@ -10784,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. + + + The task has been executed successfully - - Seconds should be positive. + + 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 @@ -10889,6 +12253,12 @@ The name of the data provider. + + Collect + + + The memory has been successfully cleaned up. + HTTP_HOST @@ -10925,6 +12295,9 @@ Server time zone + + Used memory (RAM) + Greenwich Mean Time (GMT/UTC) @@ -10934,6 +12307,12 @@ Warnings + + Access denied to anonymous request on {0}. + + + Access denied to user #{0} '{1}' on {2}. + Default dimension is not set @@ -10958,6 +12337,9 @@ Default weight is set + + Please enter digits only. + All directory permissions are OK @@ -10991,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 @@ -11015,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 @@ -11318,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. @@ -11480,6 +12895,9 @@ Checkout + + An anonymous checkout is not allowed. + Billing address @@ -11504,12 +12922,21 @@ Continue shopping + + Please agree to the user agreement for downloadable products. + Enter billing address 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). @@ -11603,6 +13030,9 @@ Submitting order information... + + Subscribed to newsletter + Terms of service @@ -11687,6 +13117,9 @@ Cancelled + + The API call to verify a CAPTCHA has failed. + Edit product @@ -11708,6 +13141,15 @@ Continue + + Copy to clipboard + + + Failed to copy. + + + Copied! + Count @@ -11720,6 +13162,9 @@ Cross references + + Customer number already exists, please choose another. + Date @@ -11738,33 +13183,84 @@ 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 @@ -11792,12 +13288,21 @@ Drop files here to upload + + Enter URL + Failed Upload a file + + Filter + + + Free shipping + Friendly name @@ -11813,6 +13318,9 @@ Home + + Image + Import @@ -11822,12 +13330,21 @@ Is active + + Language + List + + Loading + Loading next step... + + This function is not available for guests. + Miscellaneous @@ -11837,6 +13354,9 @@ More info + + My + Navigation @@ -11852,6 +13372,12 @@ No, cancel + + There are no files available. + + + There was no file uploaded. + The operation was not carried out for security reasons. @@ -11861,6 +13387,9 @@ Alert + + Not selectable + Off @@ -11876,6 +13405,12 @@ Optional + + Options + + + Partition + Capture method not supported @@ -11900,6 +13435,24 @@ Product + + Profile + + + Projection + + + Provider + + + public + + + Published + + + Publishing + Question @@ -11912,9 +13465,21 @@ Remove + + Replace + + + The request could not be processed.<br />Controller: {0}, Action: {1}, Reason: {2}. + + + Rule + Save + + Scheduled + Search @@ -11948,6 +13513,12 @@ Show + + Show all + + + Show more + Shrink @@ -11960,9 +13531,21 @@ System name + + Unavailable + Undefined + + Unknown + + + Unpublished + + + Unscheduled + Unspecified @@ -11981,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 @@ -12035,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. @@ -12044,6 +13642,9 @@ Currencies + + The customer does not exist. + Guest @@ -12065,6 +13666,9 @@ Product + + The product has no user agreement. + I agree @@ -12074,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 @@ -12137,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 @@ -12188,6 +13807,15 @@ Years + + Above product list + + + At bottom of page + + + Do not display + Using CONTAINS and AND with prefix_term @@ -12218,6 +13846,27 @@ Show usernames + + Automatically assigned + + + Disabled + + + Enabled + + + Display + + + Always editable + + + Editable if empty + + + Do not display + Clear @@ -12227,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 @@ -12311,6 +14056,24 @@ Warning + + Show activated + + + Show deactivated + + + Do not show + + + Show activated + + + Show deactivated + + + Do not show + Cancelled @@ -12577,9 +14340,6 @@ Forum's topics with newest posts. - - {0} - Forum: {1} - Forum Name @@ -12820,6 +14580,9 @@ Information + + The install language '{0}' is not registered. + Please enter a valid credit card number. @@ -13069,12 +14832,48 @@ 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 @@ -13087,6 +14886,12 @@ Gift card ({0}) + + No initial order exists for the recurring payment. + + + There are no recurring products. + Note(s) @@ -13096,6 +14901,9 @@ Note + + The order {0} was not found. + Order # @@ -13117,6 +14925,9 @@ Payment method additional fee + + order-{0}.pdf + Phone @@ -13228,6 +15039,9 @@ Shipping Address + + The shipping address is missing. + Shipping Method @@ -13378,6 +15192,9 @@ Email wishlist to a friend + + The next payment date could not be calculated. + Card code @@ -13402,6 +15219,9 @@ Wrong card number + + The payment method could not be loaded. + Valid until @@ -13411,6 +15231,24 @@ Expire year is required + + The payment method is not available. + + + At least one payment method provider is required to be active. + + + Unfortunately we can not handle this purchasing via your preferred payment method. Please select an alternate payment option to complete your order. + + + Recurring payment is not active. + + + Recurring payments are not supported by selected payment method. + + + The recurring payment type is not supported. + Select credit card @@ -13528,12 +15366,6 @@ Weight - - Associated products - - - Bundled items - Mail @@ -13549,32 +15381,14 @@ Contact data - - Height - - - Legth - - - Manufacturer - - - Price - - - SKU - - - Specification attributes - Stock quantity - - Weight + + The poll answer {0} was not found. - - Width + + The poll is not available. Only registered users can vote. @@ -13597,6 +15411,9 @@ Privacy Notice + + Private messages are disabled. + Inbox @@ -13720,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 @@ -13925,7 +15748,10 @@ This product is sold out. - This product is sold out. + No bundle items available + + + The product {0} was not found. Price: @@ -14002,6 +15828,9 @@ Quantity + + The product variant {0} was not found. + Weight @@ -14047,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 @@ -14137,6 +15969,9 @@ You cannot vote for your own review + + The product review {0} was not found. + Only registered users can write reviews @@ -14251,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 @@ -14383,6 +16230,9 @@ The coupon code you entered couldn't be applied to your order + + The shoping cart is disabled. + Total @@ -14495,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 @@ -14576,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 @@ -14599,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> @@ -14968,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 @@ -14998,9 +16896,33 @@ one year ago + + Second + + + sec + + + Seconds + + + sec + {0} seconds ago + + Week + + + Weeks + + + Year + + + Years + {0} years ago @@ -15061,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 9e3f271712..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 @@ -201504232202590_AutoUpdateRes \ 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/MvcLocalization.Designer.cs b/src/Presentation/SmartStore.Web/App_GlobalResources/MvcLocalization.Designer.cs index 07cb03a819..1eda45cf26 100644 --- a/src/Presentation/SmartStore.Web/App_GlobalResources/MvcLocalization.Designer.cs +++ b/src/Presentation/SmartStore.Web/App_GlobalResources/MvcLocalization.Designer.cs @@ -1,10 +1,10 @@ //------------------------------------------------------------------------------ // -// Dieser Code wurde von einem Tool generiert. -// Laufzeitversion:4.0.30319.34209 +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 // -// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn -// der Code erneut generiert wird. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. // //------------------------------------------------------------------------------ @@ -13,12 +13,12 @@ namespace Resources { /// - /// Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw. + /// A strongly-typed resource class, for looking up localized strings, etc. /// - // Diese Klasse wurde von der StronglyTypedResourceBuilder-Klasse - // über ein Tool wie ResGen oder Visual Studio automatisch generiert. - // Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen - // mit der /str-Option erneut aus, oder Sie erstellen das Visual Studio-Projekt neu. + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option or rebuild the Visual Studio project. [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Web.Application.StronglyTypedResourceProxyBuilder", "12.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] @@ -33,7 +33,7 @@ internal MvcLocalization() { } /// - /// Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird. + /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Resources.ResourceManager ResourceManager { @@ -47,8 +47,8 @@ internal MvcLocalization() { } /// - /// Überschreibt die CurrentUICulture-Eigenschaft des aktuellen Threads für alle - /// Ressourcenlookups, die diese stark typisierte Ressourcenklasse verwenden. + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Globalization.CultureInfo Culture { @@ -61,7 +61,7 @@ internal MvcLocalization() { } /// - /// Sucht eine lokalisierte Zeichenfolge, die The field {0} must be a date. ähnelt. + /// 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 { } /// - /// Sucht eine lokalisierte Zeichenfolge, die The field {0} must be a number. ähnelt. + /// 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 { } /// - /// Sucht eine lokalisierte Zeichenfolge, die The value '{0}' is not valid for {1}. ähnelt. + /// Looks up a localized string similar to The value '{0}' is not valid for '{1}'.. /// internal static string PropertyValueInvalid { get { @@ -88,12 +88,21 @@ internal static string PropertyValueInvalid { } /// - /// Sucht eine lokalisierte Zeichenfolge, die A value is required. ähnelt. + /// Looks up a localized string similar to A value is required.. /// internal static string PropertyValueRequired { get { 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/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 6b333e4237..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 { 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/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 e079affb89..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,63 +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 { @@ -145,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 { @@ -154,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 4d85eb8b75..d4daaab546 100644 --- a/src/Presentation/SmartStore.Web/Content/install/variables.less +++ b/src/Presentation/SmartStore.Web/Content/install/variables.less @@ -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; diff --git a/src/Presentation/SmartStore.Web/Content/jquery.pnotify.default.css b/src/Presentation/SmartStore.Web/Content/jquery.pnotify.default.css index 36a63c876f..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, 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 d9dcc8baaa..ed096936d8 100644 --- a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs @@ -16,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; @@ -263,48 +266,46 @@ public ActionResult ActiveDiscussions(int forumId = 0) 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); - return new RssActionResult() { Feed = feed }; + items.Add(item); + } + + feed.Items = items; + + return new RssActionResult { Feed = feed }; } public ActionResult ForumGroup(int id) @@ -354,7 +355,7 @@ 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; } } @@ -379,58 +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.GetLocalized(x => x.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] diff --git a/src/Presentation/SmartStore.Web/Controllers/CatalogController.cs b/src/Presentation/SmartStore.Web/Controllers/CatalogController.cs index 191bdc64fc..9db9fa78bb 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CatalogController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CatalogController.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.ServiceModel.Syndication; using System.Web.Mvc; @@ -9,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; @@ -21,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; @@ -30,7 +31,7 @@ namespace SmartStore.Web.Controllers { - public partial class CatalogController : PublicControllerBase + public partial class CatalogController : PublicControllerBase { #region Fields @@ -105,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)] @@ -144,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; @@ -157,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 @@ -193,13 +190,13 @@ public ActionResult Category(int categoryId, CatalogPagingFilteringModel command 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, @@ -216,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) }; @@ -288,6 +285,7 @@ public ActionResult Category(int categoryId, CatalogPagingFilteringModel command products, prepareColorAttributes: true, prepareManufacturers: command.ViewMode.IsCaseInsensitiveEqual("list")).ToList(); + model.PagingFilteringContext.LoadPagedList(products); } else @@ -368,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(""); @@ -408,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) }; @@ -445,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; @@ -457,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 { @@ -561,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); @@ -750,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, @@ -866,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 @@ -1000,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); } @@ -1047,8 +1070,10 @@ public ActionResult FlyoutCompare() 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); @@ -1067,16 +1092,22 @@ public ActionResult Search(SearchModel model, SearchPagingFilteringModel command model = new SearchModel(); // '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.PageSize <= 0) 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, @@ -1084,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); @@ -1103,7 +1127,7 @@ 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 @@ -1117,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 { 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) { @@ -1142,10 +1166,11 @@ 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 @@ -1163,16 +1188,16 @@ public ActionResult Search(SearchModel model, SearchPagingFilteringModel command manufacturerId = model.Mid; // min price - if (!string.IsNullOrEmpty(model.Pf)) + 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)) + 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); } @@ -1183,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; @@ -1204,13 +1227,17 @@ public ActionResult Search(SearchModel model, SearchPagingFilteringModel command products = _productService.SearchProducts(ctx); model.Products = _helper.PrepareProductOverviewModels( - products, - prepareColorAttributes: true, + 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); @@ -1233,35 +1260,32 @@ public ActionResult SearchTermAutoComplete(string term) if (String.IsNullOrWhiteSpace(term) || term.Length < _catalogSettings.ProductSearchTermMinimumLength) return Content(""); - // products - var pageSize = _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 = pageSize; + 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, + var models = _helper.PrepareProductOverviewModels(products, false, _catalogSettings.ShowProductImagesInSearchAutoComplete, - _mediaSettings.ProductThumbPictureSizeOnProductDetailsPage).ToList(); - - var result = (from p in models - select new - { - label = p.Name, - secondary = p.ShortDescription.Truncate(70, "...") ?? "", - producturl = Url.RouteUrl("Product", new { SeName = p.SeName }), - productpictureurl = p.DefaultPictureModel.ImageUrl - }) - .ToList(); + _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 d048a07105..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; + var attributeKey = "product_attribute_{0}_{1}_{2}_{3}".FormatInvariant(product.Id, bundleItemId, attribute.ProductAttributeId, attribute.Id); + pvaModel.TextValue = selectedAttributes[attributeKey]; + } + } - if (productBundleItem.FilterOut(pvaValue, out attributeFilter)) - continue; + var preSelectedValueId = 0; + var pvaValues = (attribute.ShouldHaveValues() ? _productAttributeService.GetProductVariantAttributeValues(attribute.Id) : new List()); - if (preSelectedValueId == 0 && attributeFilter != null && attributeFilter.IsPreSelected) - preSelectedValueId = attributeFilter.AttributeValueId; + foreach (var pvaValue in pvaValues) + { + ProductBundleItemAttributeFilter attributeFilter = null; - var linkedProduct = _productService.GetProductById(pvaValue.LinkedProductId); + if (productBundleItem.FilterOut(pvaValue, out attributeFilter)) + continue; - 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 (preSelectedValueId == 0 && attributeFilter != null && attributeFilter.IsPreSelected) + preSelectedValueId = attributeFilter.AttributeValueId; - if (linkedProduct != null && linkedProduct.VisibleIndividually) - pvaValueModel.SeName = linkedProduct.GetSeName(); + var linkedProduct = _productService.GetProductById(pvaValue.LinkedProductId); - if (hasSelectedAttributes) - pvaValueModel.IsPreSelected = false; // explicitly selected always discards pre-selected by merchant + 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; - // 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); - - 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,14 +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) + // 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); @@ -575,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 @@ -589,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) @@ -628,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(); @@ -653,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; @@ -664,39 +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" }); - - if (!product.IsTaxExempt && !product.IsShipEnabled) - model.LegalInfo += taxInfo + " " + defaultTaxRate; - - if(product.IsShipEnabled) + if (!product.IsShipEnabled || (addShippingPrice == 0 && product.IsFreeShipping)) { - model.LegalInfo = T("Tax.LegalInfoProductDetail", + model.LegalInfo += "{0} {1}, {2}".FormatInvariant( product.IsTaxExempt ? "" : taxInfo, product.IsTaxExempt ? "" : defaultTaxRate, - additionalShippingCosts, - shippingInfoLink); + T("Common.FreeShipping")); + } + else + { + var topic = _topicService.Value.GetTopicBySystemName("ShippingInfo", store.Id); + + 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" })); + } } - string dimension = _measureService.GetMeasureDimensionById(_measureSettings.BaseDimensionId).Name; + var dimension = _measureService.GetMeasureDimensionById(_measureSettings.BaseDimensionId).Name; model.WeightValue = product.Weight; if (!isBundle) @@ -755,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 @@ -809,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) { @@ -835,16 +934,35 @@ public ProductDetailsModel PrepareProductDetailModel( model.ProductPrice.PriceValue = finalPriceWithoutDiscount; model.ProductPrice.PriceWithDiscountValue = finalPriceWithDiscount; - 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)); } } } @@ -879,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() @@ -906,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 @@ -982,20 +1102,27 @@ public IEnumerable PrepareProductOverviewModels( bool prepareSpecificationAttributes = false, bool forceRedirectionAfterAddingToCart = false, bool prepareColorAttributes = false, - bool prepareManufacturers = 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 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" }); + 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") }, @@ -1003,25 +1130,44 @@ public IEnumerable PrepareProductOverviewModels( { "Media.Product.ImageLinkTitleFormat", T("Media.Product.ImageLinkTitleFormat") }, { "Media.Product.ImageAlternateTextFormat", T("Media.Product.ImageAlternateTextFormat") }, { "Products.DimensionsValue", T("Products.DimensionsValue") }, - { "Tax.LegalInfoFooter", T("Tax.LegalInfoFooter") }, { "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 { 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) { @@ -1033,167 +1179,155 @@ public IEnumerable PrepareProductOverviewModels( 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 (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 = res["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(res["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 + } + else + { + #region Simple product + + //add to cart button + priceModel.DisableBuyButton = product.DisableBuyButton || !enableShoppingCart || !displayPrices; + + //add to wishlist button + priceModel.DisableWishListButton = product.DisableWishlistButton || !enableWishlist || !displayPrices; - #endregion + //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; + + 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); + } - //add to cart button - priceModel.DisableBuyButton = product.DisableBuyButton || !enableShoppingCart || !displayPrices; + 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 || !enableWishlist || !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 (displayPrices) + if (displayFromMessage) { - if (!product.CustomerEntersPrice) - { - if (product.CallForPrice) - { - //call for price - priceModel.OldPrice = null; - priceModel.Price = res["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(res["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; @@ -1202,6 +1336,35 @@ public IEnumerable PrepareProductOverviewModels( #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) { @@ -1212,15 +1375,15 @@ public IEnumerable PrepareProductOverviewModels( //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), + 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) }; @@ -1235,60 +1398,30 @@ public IEnumerable PrepareProductOverviewModels( { 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 - } - model.ProductMinPriceId = minPriceProduct.Id; + model.MinPriceProductId = contextProduct.Id; model.ShowSku = _catalogSettings.ShowProductSku; model.ShowWeight = _catalogSettings.ShowWeight; model.ShowDimensions = _catalogSettings.ShowDimensions; - model.Sku = minPriceProduct.Sku; + model.Sku = contextProduct.Sku; model.Dimensions = res["Products.DimensionsValue"].Text.FormatCurrent( - minPriceProduct.Width.ToString("F2"), - minPriceProduct.Height.ToString("F2"), - minPriceProduct.Length.ToString("F2") + 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 = res["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); - 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.CompareEnabled = _catalogSettings.CompareProductsEnabled; @@ -1296,7 +1429,7 @@ from a in colorAttr.ProductVariantAttributeValues.Take(50) if (model.ShowDeliveryTimes) { - var deliveryTime = _deliveryTimeService.GetDeliveryTime(minPriceProduct); + var deliveryTime = _deliveryTimeService.GetDeliveryTime(contextProduct); if (deliveryTime != null) { model.DeliveryTimeName = deliveryTime.GetLocalized(x => x.Name); @@ -1306,36 +1439,38 @@ from a in colorAttr.ProductVariantAttributeValues.Take(50) if (prepareManufacturers) { - model.Manufacturers = PrepareManufacturersOverviewModel(_manufacturerService.GetProductManufacturersByProductId(product.Id), cachedManufacturerModels); + model.Manufacturers = PrepareManufacturersOverviewModel(_manufacturerService.GetProductManufacturersByProductId(product.Id), cachedManufacturerModels, false); } - if (_catalogSettings.ShowBasePriceInProductLists) + if (finalPrice != decimal.Zero && (_catalogSettings.ShowBasePriceInProductLists || isCompareList)) { - model.BasePriceInfo = minPriceProduct.GetBasePriceInfo(_localizationService, _priceFormatter); + model.BasePriceInfo = contextProduct.GetBasePriceInfo(finalPrice, _localizationService, _priceFormatter, workingCurrency); } - var addShippingPrice = _currencyService.ConvertCurrency( - minPriceProduct.AdditionalShippingCharge, - _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId), _services.WorkContext.WorkingCurrency); - - if (addShippingPrice > 0 && displayPrices) + if (displayPrices) { - model.TransportSurcharge = res["Common.AdditionalShippingSurcharge"].Text.FormatWith(_priceFormatter.FormatPrice(addShippingPrice, true, false)); + 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 (minPriceProduct.Weight > 0) + if (contextProduct.Weight > 0) { - model.Weight = "{0} {1}".FormatCurrent(minPriceProduct.Weight.ToString("F2"), _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId).Name); + 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; } @@ -1514,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 @@ -1638,7 +1773,8 @@ public void PreparePagingFilteringModel(PagingFilteringModel model, PagingFilter public List PrepareManufacturersOverviewModel( ICollection manufacturers, - IDictionary cachedModels = null) + IDictionary cachedModels = null, + bool forProductDetailPage = false) { var model = new List(); @@ -1663,16 +1799,9 @@ public List PrepareManufacturersOverviewModel( }; - Picture pic = manufacturer.Picture; - if (pic != null) + if (_catalogSettings.ShowManufacturerPicturesInProductDetail) { - item.PictureModel = new PictureModel - { - PictureId = pic.Id, - Title = T("Media.Product.ImageLinkTitleFormat", manufacturer.Name), - AlternateText = T("Media.Product.ImageAlternateTextFormat", manufacturer.Name), - ImageUrl = _pictureService.GetPictureUrl(pic), - }; + item.PictureModel = PrepareManufacturerPictureModel(manufacturer, manufacturer.GetLocalized(x => x.Name)); } cachedModels.Add(item.Id, item); @@ -1684,6 +1813,35 @@ public List PrepareManufacturersOverviewModel( 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 87febc0ba2..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,6 +15,7 @@ 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; @@ -34,21 +35,24 @@ 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 static string[] s_hints = new string[] { "Shopsystem", "Onlineshop Software", "Shopsoftware", "E-Commerce Solution" }; - private readonly ITopicService _topicService; + private readonly ICommonServices _services; + private readonly ITopicService _topicService; private readonly Lazy _languageService; private readonly Lazy _currencyService; private readonly IThemeContext _themeContext; @@ -56,30 +60,33 @@ public partial class CommonController : PublicControllerBase private readonly Lazy _forumservice; private readonly Lazy _genericAttributeService; private readonly Lazy _mobileDeviceHelper; + private readonly Lazy _compareProductsService; + private readonly Lazy _urlRecordService; - private readonly static string[] s_hints = new string[] { "Shopsystem", "Onlineshop Software", "Shopsoftware", "E-Commerce Solution" }; - - private readonly CustomerSettings _customerSettings; + 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 Lazy _securitySettings; - - private readonly IOrderTotalCalculationService _orderTotalCalculationService; + 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, Lazy languageService, Lazy currencyService, @@ -88,23 +95,32 @@ public CommonController( Lazy forumService, Lazy genericAttributeService, Lazy mobileDeviceHelper, - CustomerSettings customerSettings, + Lazy compareProductsService, + Lazy urlRecordService, + StoreInformationSettings storeInfoSettings, + CustomerSettings customerSettings, TaxSettings taxSettings, CatalogSettings catalogSettings, EmailAccountSettings emailAccountSettings, CommonSettings commonSettings, + NewsSettings newsSettings, BlogSettings blogSettings, ForumSettings forumSettings, LocalizationSettings localizationSettings, Lazy securitySettings, - IOrderTotalCalculationService orderTotalCalculationService, + 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; @@ -112,15 +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; @@ -128,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] @@ -150,7 +168,7 @@ protected LanguageSelectorModel PrepareLanguageSelectorModel() { 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 @@ -161,7 +179,7 @@ protected LanguageSelectorModel PrepareLanguageSelectorModel() var workingLanguage = _services.WorkContext.WorkingLanguage; - var model = new LanguageSelectorModel() + var model = new LanguageSelectorModel { CurrentLanguageId = workingLanguage.Id, AvailableLanguages = availableLanguages, @@ -172,9 +190,10 @@ protected LanguageSelectorModel PrepareLanguageSelectorModel() 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) { @@ -192,27 +211,39 @@ 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() @@ -226,7 +257,7 @@ protected CurrencySelectorModel PrepareCurrencySelectorModel() 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; @@ -240,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() { @@ -359,18 +368,6 @@ public ActionResult SetLanguage(int langid, string returnUrl = "") { _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) { @@ -379,10 +376,9 @@ public ActionResult SetLanguage(int langid, string returnUrl = "") returnUrl = helper.GetAbsolutePath(); } - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } - //currency [ChildActionOnly] public ActionResult CurrencySelector() { @@ -393,40 +389,31 @@ public ActionResult CurrencySelector() return PartialView(model); } + public ActionResult CurrencySelected(int customerCurrency, string returnUrl = "") { 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) @@ -506,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) @@ -542,8 +540,8 @@ public ActionResult ShopBar() } 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), @@ -555,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(); @@ -566,7 +564,7 @@ public ActionResult ShopBar() if (_catalogSettings.CompareProductsEnabled) { - model.CompareItems = EngineContext.Current.Resolve().GetComparedProductsCount(); + model.CompareItems = _compareProductsService.Value.GetComparedProductsCount(); } return PartialView(model); @@ -575,12 +573,11 @@ 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 = !_themeSettings.AllowCustomerToSelectTheme ? new List() : _themeRegistry.Value.GetThemeManifests() .Where(x => !x.MobileTheme) @@ -597,7 +594,6 @@ public ActionResult Footer() var model = new FooterModel { StoreName = store.Name, - LegalInfo = T("Tax.LegalInfoFooter", taxInfo, shippingInfoLink), ShowLegalInfo = _taxSettings.ShowLegalHintsInFooter, ShowThemeSelector = availableStoreThemes.Count > 1, BlogEnabled = _blogSettings.Enabled, @@ -605,6 +601,26 @@ public ActionResult Footer() 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()) { @@ -612,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); - } - } + 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; - var socialSettings = EngineContext.Current.Resolve(); - - 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); } @@ -643,11 +644,13 @@ public ActionResult Footer() [ChildActionOnly] public ActionResult Menu() { + var store = _services.StoreContext.CurrentStore; var customer = _services.WorkContext.CurrentCustomer; var model = new MenuModel { RecentlyAddedProductsEnabled = _catalogSettings.RecentlyAddedProductsEnabled, + NewsEnabled = _newsSettings.Enabled, BlogEnabled = _blogSettings.Enabled, ForumEnabled = _forumSettings.ForumsEnabled, AllowPrivateMessages = _forumSettings.AllowPrivateMessages && customer.IsRegistered(), @@ -656,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); } @@ -665,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, @@ -676,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] @@ -721,12 +741,7 @@ public ActionResult ChangeTheme(string themeName, string returnUrl = null) return Json(new { Success = true }); } - if (returnUrl.IsEmpty()) - { - return RedirectToRoute("HomePage"); - } - - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } [ChildActionOnly] @@ -769,15 +784,16 @@ public ActionResult Favicon() ///
    /// True - use desktop version; false - use version for mobile devices /// Action result + [HttpPost] public ActionResult ChangeDevice(bool dontUseMobileVersion) { - _genericAttributeService.Value.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] @@ -800,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" }; @@ -859,7 +876,8 @@ public ActionResult RobotsTextFile() 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 @@ -874,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); @@ -885,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 @@ -988,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 52d7118d68..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) { 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 745da0006d..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,14 +71,11 @@ 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() { @@ -90,7 +90,7 @@ public ActionResult ContentSlider() var settings = _services.Settings.LoadSetting(); settings.BackgroundPictureUrl = pictureService.GetPictureUrl(settings.BackgroundPictureId, 0, false); - + var slides = settings.Slides .Where(s => s.LanguageCulture == _services.WorkContext.WorkingLanguage.LanguageCulture && @@ -123,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); @@ -145,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; @@ -254,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 844c3c36b9..7c5a919eb0 100644 --- a/src/Presentation/SmartStore.Web/Controllers/InstallController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/InstallController.cs @@ -1,33 +1,28 @@ -using Autofac; -using System; +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 { @@ -146,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; @@ -160,7 +155,6 @@ protected string CreateConnectionString( builder.PersistSecurityInfo = false; //builder.MultipleActiveResultSets = true; - // codehint: sm-add builder.UserInstance = false; builder.Pooling = true; builder.MinPoolSize = 1; @@ -183,8 +177,8 @@ 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 { @@ -481,7 +475,7 @@ protected virtual InstallationResult InstallCore(ILifetimeScope scope, InstallMo { 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; @@ -546,7 +540,7 @@ protected virtual InstallationResult InstallCore(ILifetimeScope scope, InstallMo 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 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 57c7c32649..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); @@ -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)); @@ -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 e2886f2bc0..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; @@ -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 d778b741b5..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); } @@ -466,7 +480,7 @@ public ActionResult ProductsAlsoPurchased(int productId, int? productThumbPictur return Content(""); // prepare model - 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); } @@ -515,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; @@ -544,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")); @@ -604,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()) @@ -614,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); } } } @@ -648,76 +671,89 @@ public ActionResult UpdateProductDetails(int productId, string itemType, int bun } else { - var allCombinationImageIds = new List(); + var allCombinationPictureIds = _productAttributeService.GetAllProductVariantAttributeCombinationPictureIds(product.Id); - _productAttributeService - .GetAllProductVariantAttributeCombinations(product.Id) - .GetAllCombinationImageIds(allCombinationImageIds); - - _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 }; @@ -733,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, () => @@ -864,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) { @@ -1003,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 9229462276..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 @@ -892,7 +950,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, @@ -959,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); @@ -996,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); @@ -1011,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: @@ -1029,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]; @@ -1043,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: { @@ -1058,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))); @@ -1095,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; } @@ -1123,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 @@ -1141,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) }); } @@ -1368,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"); + }); } @@ -1682,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)] @@ -1807,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); @@ -1816,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, @@ -1828,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 { @@ -1843,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( @@ -1857,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")); } } } @@ -2561,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/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 4fc9355f74..4297e54186 100644 --- a/src/Presentation/SmartStore.Web/Global.asax.cs +++ b/src/Presentation/SmartStore.Web/Global.asax.cs @@ -1,33 +1,34 @@ -using System.Linq; +using System; +using System.Linq; using System.Web; using System.Web.Hosting; 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.Services.Customers; using SmartStore.Services.Tasks; -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(); @@ -115,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 { @@ -161,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 54df8e0005..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 @@ -265,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 => { @@ -290,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; }) @@ -2112,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"; @@ -2525,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"; }); } diff --git a/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallDataSeeder.cs b/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallDataSeeder.cs index 60107decb1..4e964cc565 100644 --- a/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallDataSeeder.cs +++ b/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallDataSeeder.cs @@ -539,6 +539,7 @@ public virtual void Seed(SmartObjectContext context) _ctx.Configuration.AutoDetectChangesEnabled = false; _ctx.Configuration.ValidateOnSaveEnabled = false; + _ctx.HooksEnabled = false; _config.ProgressMessageCallback("Progress.CreatingRequiredData"); @@ -549,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()); @@ -614,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 e9352a710e..04ba8b6e49 100644 --- a/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallationLocalizationService.cs +++ b/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallationLocalizationService.cs @@ -53,7 +53,7 @@ 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 @@ -79,7 +79,7 @@ public virtual InstallationLanguage GetCurrentLanguage() private bool MatchLanguageByCurrentCulture(InstallationLanguage language) { - var curCulture = CultureInfo.GetCultureInfoByIetfLanguageTag("tr-TR"); // Thread.CurrentThread.CurrentCulture; + var curCulture = Thread.CurrentThread.CurrentUICulture; if (language.Code.IsCaseInsensitiveEqual(curCulture.IetfLanguageTag)) return true; 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 52be36ea9b..a09f815652 100644 --- a/src/Presentation/SmartStore.Web/Models/Catalog/CategoryModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Catalog/CategoryModel.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using SmartStore.Core.Domain.Catalog; -using SmartStore.Web.Framework.Mvc; +using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Framework.UI; using SmartStore.Web.Models.Media; 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 38b63ac37e..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,7 +24,6 @@ public SelectListItem ToSelectListItem() } } - // codehint: sm-add public partial class PagingFilteringModel : PagedListBase { 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 eb39ca78ee..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; } @@ -51,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 22b9ccc117..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 { 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 9e9ce393fc..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(); } 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 f1d0053cf7..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 @@ -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 84b7ffab8d..ff2f5be51f 100644 --- a/src/Presentation/SmartStore.Web/Scripts/smartstore.common.js +++ b/src/Presentation/SmartStore.Web/Scripts/smartstore.common.js @@ -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 () { @@ -219,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 index 517d1ac8af..957e35f1a9 100644 --- a/src/Presentation/SmartStore.Web/Scripts/smartstore.instantsearch.js +++ b/src/Presentation/SmartStore.Web/Scripts/smartstore.instantsearch.js @@ -21,7 +21,12 @@ menu: ''.format(showThumbs ? " rich" : ""), autoSelectFirstItem: false, source: function (query, process) { - $('#instantsearch-progress').removeClass('hide'); + 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, @@ -38,7 +43,7 @@ searchResult = null; }, complete: function () { - $('#instantsearch-progress').addClass('hide'); + spinner.removeClass('active'); } }); }, 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 e4db2c4359..8340a62bfb 100644 --- a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj +++ b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj @@ -48,6 +48,7 @@ 4 false AnyCPU + AllFilesInProjectFolder pdbonly @@ -57,27 +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 @@ -96,30 +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\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 @@ -129,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 @@ -253,10 +253,10 @@ - + @@ -337,6 +337,7 @@ + @@ -346,6 +347,7 @@ + @@ -643,291 +645,19 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ResXFileCodeGenerator MvcLocalization.de.Designer.cs Designer + Designer - - - - - - - - - - - @@ -941,11 +671,11 @@ Designer - - - - - + + + + + @@ -1049,6 +779,7 @@ + @@ -1060,7 +791,6 @@ - @@ -1077,6 +807,8 @@ + + @@ -1194,6 +926,7 @@ + @@ -1217,7 +950,6 @@ - @@ -1519,14 +1251,6 @@ - - - - - - - - @@ -1550,12 +1274,6 @@ - - - - - - @@ -1564,7 +1282,6 @@ - @@ -1587,15 +1304,12 @@ - - - - + @@ -1864,9 +1578,13 @@ + + + + @@ -1981,7 +1699,6 @@ - @@ -2141,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" + + @@ -2170,9 +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/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/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 d221631a55..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 { @@ -66,7 +57,7 @@ position: absolute; right: 45px; top: 50%; - margin-top: -8px; + margin-top: -10px; z-index: 10; } @@ -139,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 & { @@ -172,8 +163,6 @@ position: relative; display: inline-block; margin: 2px 4px 0 0; - width: 24px; - height: 24px; span.label { position: absolute; @@ -185,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%); } @@ -254,7 +242,6 @@ margin: 0; list-style: none; -webkit-background-clip: padding-box; - -moz-background-clip: padding; background-clip: padding-box; height: 50px; @@ -273,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/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/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/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/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/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/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.cshtml b/src/Presentation/SmartStore.Web/Views/Catalog/CategoryTemplate.ProductsInGridOrLines.cshtml index f96a1c71c6..cbe8111e78 100644 --- a/src/Presentation/SmartStore.Web/Views/Catalog/CategoryTemplate.ProductsInGridOrLines.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Catalog/CategoryTemplate.ProductsInGridOrLines.cshtml @@ -44,9 +44,12 @@ @item.Name
      - - @item.PictureModel.AlternateText - + @if (item.PictureModel.ImageUrl.HasValue()) + { + + @item.PictureModel.AlternateText + + }
      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 156c24a9d0..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,7 +28,9 @@ diff --git a/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.cshtml b/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.cshtml index 45bfb15e42..10c8f7d30f 100644 --- a/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.cshtml @@ -8,8 +8,8 @@ //title Html.AddTitleParts(T("PageTitle.Checkout").Text); - string termsLink = ""; - string disclaimerLink = ""; + string termsLink = ""; + string disclaimerLink = ""; string terms = string.Format(T("Checkout.TermsOfService.IAccept"), termsLink, "", disclaimerLink); } @section orderProgress{ @@ -27,18 +27,22 @@ @Html.Raw(@T("Checkout.ConfirmHint"))
      - + + + - if (Model.TermsOfServiceEnabled) - { + if (Model.TermsOfServiceEnabled) + {
      @@ -48,8 +52,8 @@ - @if (Model.ShowConfirmOrderLegalHint) - { - - } + { + + }
      @@ -109,26 +113,59 @@ $(function () { var checkoutButton = $(".confirm-order-next-step-button"); checkoutButton.click(function () { + var termOfServiceOk = true, + userAgreementsOk = true, + esdRevocationWaiverOk = true; - $("#customercommenthidden").val($("#CustomerComment").val()); + $("#customercommenthidden").val($("#CustomerComment").val()); + $('#SubscribeToNewsLetterHidden').val($('input[name=SubscribeToNewsLetter]').is(':checked')); + $('#AcceptThirdPartyEmailHandOverHidden').val($('input[name=AcceptThirdPartyEmailHandOver]').is(':checked')); - //terms of services - var termOfServiceOk = true; + // terms of services @if (Model.TermsOfServiceEnabled) { - + if (!$('#termsofservice').is(':checked')) { - displayNotification('@T("Checkout.TermsOfService.PleaseAccept").ToString().EncodeJsString()', "error"); + displayNotification('@T("Checkout.TermsOfService.PleaseAccept").ToString().EncodeJsString('"', false)', "error"); termOfServiceOk = false; $.scrollTo($('#termsofservice'), 800, { offset: -70 }); } else { termOfServiceOk = true; } - + } + + // agree user agreement for downloadable products + $('table.table-order-products').find('input[name^=AgreeUserAgreement]').each(function () { + if (!$(this).is(':checked')) { + userAgreementsOk = false; + displayNotification('@T("Checkout.DownloadUserAgreement.PleaseAgree").ToString().EncodeJsString('"', false)', 'error'); + if (termOfServiceOk) { + $.scrollTo($('table.table-order-products'), 800, { offset: -20 }); + } + return false; + } + }); + + // agree esd revocation waiver + @if(Model.ShowEsdRevocationWaiverBox) + { + + $('table.table-order-products').find('input[name^=AgreeEsdRevocationWaiver]').each(function () { + if (!$(this).is(':checked')) { + esdRevocationWaiverOk = false; + displayNotification('@T("Checkout.EsdRevocationWaiverConfirmation.PleaseAgree").ToString().EncodeJsString('"', false)', 'error'); + if (termOfServiceOk) { + $.scrollTo($('table.table-order-products'), 800, { offset: -20 }); + } + return false; + } + }); + + } - if (termOfServiceOk) { + if (termOfServiceOk && userAgreementsOk && esdRevocationWaiverOk) { var submitOrderEvent = jQuery.Event('submitOrder'); submitOrderEvent.isOrderValid = true; submitOrderEvent.isMobile = false; diff --git a/src/Presentation/SmartStore.Web/Views/Checkout/PaymentMethod.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/Checkout/PaymentMethod.Mobile.cshtml index 5bc4697448..cab3cdbc8c 100644 --- a/src/Presentation/SmartStore.Web/Views/Checkout/PaymentMethod.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Checkout/PaymentMethod.Mobile.cshtml @@ -47,13 +47,13 @@ - @if (!String.IsNullOrWhiteSpace(paymentMethod.Description)) - { -
      - @Html.Raw(paymentMethod.Description) -
      - }
      + @if (paymentMethod.FullDescription.HasValue()) + { +
      + @Html.Raw(paymentMethod.FullDescription) +
      + } @if (!paymentMethod.RequiresInteraction || (paymentMethod.Selected && infoRoute != null)) {
      } - @Html.Action(infoRoute.Action, infoRoute.Controller, infoRoute.RouteValues) + @if (!(paymentMethod.FullDescription.HasValue() && !paymentMethod.RequiresInteraction)) + { + @Html.Action(infoRoute.Action, infoRoute.Controller, infoRoute.RouteValues) + }
      +
      }
      } diff --git a/src/Presentation/SmartStore.Web/Views/Checkout/PaymentMethod.cshtml b/src/Presentation/SmartStore.Web/Views/Checkout/PaymentMethod.cshtml index 0d8df1ae8a..569189379a 100644 --- a/src/Presentation/SmartStore.Web/Views/Checkout/PaymentMethod.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Checkout/PaymentMethod.cshtml @@ -57,6 +57,12 @@
      + @if (paymentMethod.FullDescription.HasValue()) + { +
      + @Html.Raw(paymentMethod.FullDescription) +
      + } @if (!paymentMethod.RequiresInteraction || (paymentMethod.Selected && infoRoute != null)) {
      } - @Html.Action(infoRoute.Action, infoRoute.Controller, infoRoute.RouteValues) + @if(!(paymentMethod.FullDescription.HasValue() && !paymentMethod.RequiresInteraction)) + { + @Html.Action(infoRoute.Action, infoRoute.Controller, infoRoute.RouteValues) + }
      }
      diff --git a/src/Presentation/SmartStore.Web/Views/Checkout/ShippingAddress.cshtml b/src/Presentation/SmartStore.Web/Views/Checkout/ShippingAddress.cshtml index 1d7d4e6552..e003882015 100644 --- a/src/Presentation/SmartStore.Web/Views/Checkout/ShippingAddress.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Checkout/ShippingAddress.cshtml @@ -45,8 +45,8 @@ @item.PhoneNumber
      } - @if (item.FaxEnabled) - { + @if (item.FaxEnabled && !String.IsNullOrEmpty(item.FaxNumber)) +{
      @T("Address.Fields.FaxNumber"): @item.FaxNumber diff --git a/src/Presentation/SmartStore.Web/Views/Checkout/ShippingMethod.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/Checkout/ShippingMethod.Mobile.cshtml index 6674bafc8e..07fc9cbf98 100644 --- a/src/Presentation/SmartStore.Web/Views/Checkout/ShippingMethod.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Checkout/ShippingMethod.Mobile.cshtml @@ -27,7 +27,7 @@
      - +
      @if (!String.IsNullOrEmpty(shippingMethod.Description)) { diff --git a/src/Presentation/SmartStore.Web/Views/Checkout/ShippingMethod.cshtml b/src/Presentation/SmartStore.Web/Views/Checkout/ShippingMethod.cshtml index 54c1a51dce..68d6091639 100644 --- a/src/Presentation/SmartStore.Web/Views/Checkout/ShippingMethod.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Checkout/ShippingMethod.cshtml @@ -42,7 +42,7 @@ - @shippingMethod.Name + @Html.Raw(HttpUtility.HtmlDecode(shippingMethod.Name))
      diff --git a/src/Presentation/SmartStore.Web/Views/Common/AccountDropdown.cshtml b/src/Presentation/SmartStore.Web/Views/Common/AccountDropdown.cshtml index 8e4a0e5ce4..96b1274211 100644 --- a/src/Presentation/SmartStore.Web/Views/Common/AccountDropdown.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Common/AccountDropdown.cshtml @@ -17,9 +17,10 @@ @if (Model.IsAuthenticated) { } else diff --git a/src/Presentation/SmartStore.Web/Views/Common/ChangeDeviceBlock.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/Common/ChangeDeviceBlock.Mobile.cshtml index 7d7fb0d8ad..a6be354634 100644 --- a/src/Presentation/SmartStore.Web/Views/Common/ChangeDeviceBlock.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Common/ChangeDeviceBlock.Mobile.cshtml @@ -1,5 +1,6 @@ 
      - - @T("Mobile.ViewFullSite") - + @using (Html.BeginRouteForm("ChangeDevice", new { dontusemobileversion = true }, FormMethod.Post)) + { + + }
      diff --git a/src/Presentation/SmartStore.Web/Views/Common/ChangeDeviceBlock.cshtml b/src/Presentation/SmartStore.Web/Views/Common/ChangeDeviceBlock.cshtml index b380d92dc5..3c92219985 100644 --- a/src/Presentation/SmartStore.Web/Views/Common/ChangeDeviceBlock.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Common/ChangeDeviceBlock.cshtml @@ -1,5 +1,6 @@ 
      - - @T("Mobile.ViewMobileVersion") - + @using (Html.BeginRouteForm("ChangeDevice", new { dontusemobileversion = false }, FormMethod.Post)) + { + + }
      diff --git a/src/Presentation/SmartStore.Web/Views/Common/EntityPicker.cshtml b/src/Presentation/SmartStore.Web/Views/Common/EntityPicker.cshtml new file mode 100644 index 0000000000..f2ce8112b9 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Views/Common/EntityPicker.cshtml @@ -0,0 +1,96 @@ +@model EntityPickerModel +@using SmartStore.Web.Models.Common; + + + +@helper FormButtons() +{ +
      + @Html.LabelFor(model => model.ProductName, new { @class = "control-label", @for = "ProductName" }) +
      + @Html.TextBoxFor(model => model.ProductName, new { @class = "entity-picker-searchterm" }) + + +
      +
      +} + +@helper ProductSearchForm() +{ +
      + @FormButtons() + +
      +
      + @Html.LabelFor(model => model.CategoryId, new { @class = "control-label", @for = "CategoryId" }) +
      + @Html.DropDownListFor(model => model.CategoryId, Model.AvailableCategories, Model.AllString, new { @class = "item" }) +
      +
      + +
      + @Html.LabelFor(model => model.ManufacturerId, new { @class = "control-label", @for = "ManufacturerId" }) +
      + @Html.DropDownListFor(model => model.ManufacturerId, Model.AvailableManufacturers, Model.AllString, new { @class = "item" }) +
      +
      + + @if (Model.AvailableStores.Count > 1) + { +
      + @Html.LabelFor(model => model.StoreId, new { @class = "control-label", @for = "StoreId" }) +
      + @Html.DropDownListFor(model => model.StoreId, Model.AvailableStores, Model.AllString, new { @class = "item" }) +
      +
      + } + +
      + @Html.LabelFor(model => model.ProductTypeId, new { @class = "control-label", @for = "ProductTypeId" }) +
      + @Html.DropDownListFor(model => model.ProductTypeId, Model.AvailableProductTypes, Model.AllString, new { @class = "item" }) +
      +
      +
      +
      +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Views/Common/EntityPickerList.cshtml b/src/Presentation/SmartStore.Web/Views/Common/EntityPickerList.cshtml new file mode 100644 index 0000000000..7537bfba4f --- /dev/null +++ b/src/Presentation/SmartStore.Web/Views/Common/EntityPickerList.cshtml @@ -0,0 +1,56 @@ +@using SmartStore.Web.Models.Common; +@model EntityPickerModel +@helper HighlightSearchTermInTitle(EntityPickerModel.SearchResultModel item) +{ + if (Model.HighligtSearchTerm && Model.SearchTerm.HasValue() && item.Title.HasValue()) + { + @Html.Raw(item.Title.Replace(Model.SearchTerm, "{0}".FormatInvariant(Model.SearchTerm), StringComparison.OrdinalIgnoreCase)) + } + else + { + @item.Title + } +} +@foreach (var item in Model.SearchResult ?? Enumerable.Empty()) +{ +
      +
      +
      + @if (item.ImageUrl.HasValue()) + { + + } +
      +
      +
      + @if (item.LabelText.HasValue()) + { + @item.LabelText + } + @HighlightSearchTermInTitle(item) +
      +
      + @if (item.Published.HasValue) + { + + } + @item.Summary +
      +
      +
      +
      +} + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Views/Common/Footer.cshtml b/src/Presentation/SmartStore.Web/Views/Common/Footer.cshtml index ed4d34d103..cb9d4ab796 100644 --- a/src/Presentation/SmartStore.Web/Views/Common/Footer.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Common/Footer.cshtml @@ -52,28 +52,45 @@