From e0660bde70ba89ef8d385ca02d71ac18b4d55a8e Mon Sep 17 00:00:00 2001 From: Markus Falk Date: Thu, 22 Dec 2016 20:33:04 +0100 Subject: [PATCH 01/13] VAT calculation and rounding, allow negative payment fee as discount --- src/Libraries/Nop.Core/Domain/Orders/Order.cs | 69 +- .../Nop.Core/Domain/Orders/OrderItem.cs | 7 +- .../Nop.Core/Domain/Orders/OrderSettings.cs | 8 + .../Domain/Orders/ShoppingCartItem.cs | 19 +- .../Nop.Data/Mapping/Orders/OrderItemMap.cs | 1 + .../Nop.Data/Mapping/Orders/OrderMap.cs | 6 +- .../Mapping/Orders/ShoppingCartItemMap.cs | 3 + .../Nop.Services/Catalog/IPriceFormatter.cs | 37 +- .../Nop.Services/Catalog/PriceFormatter.cs | 76 +- .../Nop.Services/Common/PdfService.cs | 54 +- .../Nop.Services/Common/PdfService7.cs | 1072 + .../ExportImport/ExportManager.cs | 14 +- .../CodeFirstInstallationService.cs | 46 +- .../Messages/MessageTokenProvider.cs | 74 +- .../Nop.Services/Nop.Services.csproj | 34 +- .../Orders/IOrderProcessingService.cs | 5 + .../Orders/IOrderTotalCalculationService.cs | 41 +- .../Orders/OrderProcessingService.cs | 159 +- .../Orders/OrderTotalCalculationService.cs | 2585 +- .../Orders/UpdateOrderParameters.cs | 2 + .../Payments/PaymentExtensions.cs | 416 +- .../Nop.Services/Payments/PaymentService.cs | 837 +- src/Libraries/Nop.Services/Tax/TaxService.cs | 38 +- src/Libraries/Nop.Services/Tax/TaxSummary.cs | 229 + src/Libraries/Nop.Services/packages.config | 1 + .../Controllers/OrderController.cs | 182 +- .../Mapper/AdminMapperConfiguration.cs | 1 + .../Models/Orders/OrderModel.cs | 31 +- .../Models/Settings/OrderSettingsModel.cs | 2 + .../Order/AddProductToOrderDetails.cshtml | 8 + .../Views/Order/_OrderDetails.Info.cshtml | 127 +- .../Views/Order/_OrderDetails.Products.cshtml | 15 + .../App_Data/Install/SqlServer.Indexes.sql | 3 + .../Localization/defaultResources.nopres.xml | 182 +- .../Nop.Web/Factories/CheckoutModelFactory.cs | 2 +- .../Nop.Web/Factories/OrderModelFactory.cs | 34 +- .../Factories/ShoppingCartModelFactory.cs | 182 +- .../Nop.Web/Models/Order/OrderDetailsModel.cs | 26 +- .../ShoppingCart/MiniShoppingCartModel.cs | 1 + .../Models/ShoppingCart/OrderTotalsModel.cs | 14 +- .../Models/ShoppingCart/ShoppingCartModel.cs | 3 +- .../DefaultClean/Content/css/styles.css | 70 +- .../Nop.Web/Views/Order/Details.cshtml | 85 +- .../Views/ShoppingCart/OrderSummary.cshtml | 10 +- .../Views/ShoppingCart/OrderTotals.cshtml | 72 +- .../Orders/OrderPersistenceTests.cs | 2 +- .../ExportImport/ExportManagerTests.cs | 2 + .../Orders/OrderProcessingServiceTests.cs | 9 +- .../OrderTotalCalculationServiceTests.cs | 60 +- src/packages/itext7.7.0.1/LICENSE.md | 36 + src/packages/itext7.7.0.1/gnu-agpl-v3.0.md | 651 + src/packages/itext7.7.0.1/itext7.7.0.1.nupkg | Bin 0 -> 2093436 bytes .../itext7.7.0.1/lib/net40/itext.barcodes.dll | Bin 0 -> 135168 bytes .../itext7.7.0.1/lib/net40/itext.barcodes.xml | 1660 + .../itext7.7.0.1/lib/net40/itext.forms.dll | Bin 0 -> 71680 bytes .../itext7.7.0.1/lib/net40/itext.forms.xml | 2938 + .../itext7.7.0.1/lib/net40/itext.io.dll | Bin 0 -> 1172992 bytes .../itext7.7.0.1/lib/net40/itext.io.xml | 6205 ++ .../itext7.7.0.1/lib/net40/itext.kernel.dll | Bin 0 -> 2908672 bytes .../itext7.7.0.1/lib/net40/itext.kernel.xml | 50386 ++++++++++++++++ .../itext7.7.0.1/lib/net40/itext.layout.dll | Bin 0 -> 161792 bytes .../itext7.7.0.1/lib/net40/itext.layout.xml | 6143 ++ .../itext7.7.0.1/lib/net40/itext.pdfa.dll | Bin 0 -> 77824 bytes .../itext7.7.0.1/lib/net40/itext.pdfa.xml | 316 + .../itext7.7.0.1/lib/net40/itext.sign.dll | Bin 0 -> 86016 bytes .../itext7.7.0.1/lib/net40/itext.sign.xml | 2444 + .../3.80-the next version/upgrade.sql | 298 +- 67 files changed, 75539 insertions(+), 2494 deletions(-) create mode 100644 src/Libraries/Nop.Services/Common/PdfService7.cs create mode 100644 src/Libraries/Nop.Services/Tax/TaxSummary.cs create mode 100644 src/packages/itext7.7.0.1/LICENSE.md create mode 100644 src/packages/itext7.7.0.1/gnu-agpl-v3.0.md create mode 100644 src/packages/itext7.7.0.1/itext7.7.0.1.nupkg create mode 100644 src/packages/itext7.7.0.1/lib/net40/itext.barcodes.dll create mode 100644 src/packages/itext7.7.0.1/lib/net40/itext.barcodes.xml create mode 100644 src/packages/itext7.7.0.1/lib/net40/itext.forms.dll create mode 100644 src/packages/itext7.7.0.1/lib/net40/itext.forms.xml create mode 100644 src/packages/itext7.7.0.1/lib/net40/itext.io.dll create mode 100644 src/packages/itext7.7.0.1/lib/net40/itext.io.xml create mode 100644 src/packages/itext7.7.0.1/lib/net40/itext.kernel.dll create mode 100644 src/packages/itext7.7.0.1/lib/net40/itext.kernel.xml create mode 100644 src/packages/itext7.7.0.1/lib/net40/itext.layout.dll create mode 100644 src/packages/itext7.7.0.1/lib/net40/itext.layout.xml create mode 100644 src/packages/itext7.7.0.1/lib/net40/itext.pdfa.dll create mode 100644 src/packages/itext7.7.0.1/lib/net40/itext.pdfa.xml create mode 100644 src/packages/itext7.7.0.1/lib/net40/itext.sign.dll create mode 100644 src/packages/itext7.7.0.1/lib/net40/itext.sign.xml diff --git a/src/Libraries/Nop.Core/Domain/Orders/Order.cs b/src/Libraries/Nop.Core/Domain/Orders/Order.cs index 5566caf5a09..9b576f6b608 100644 --- a/src/Libraries/Nop.Core/Domain/Orders/Order.cs +++ b/src/Libraries/Nop.Core/Domain/Orders/Order.cs @@ -26,9 +26,10 @@ public partial class Order : BaseEntity #region Utilities - protected virtual SortedDictionary ParseTaxRates(string taxRatesStr) + protected virtual SortedDictionary ParseTaxRates(string taxRatesStr) { - var taxRatesDictionary = new SortedDictionary(); + //var taxRatesDictionary = new SortedDictionary(); + var taxRatesDictionary = new SortedDictionary(); if (String.IsNullOrEmpty(taxRatesStr)) return taxRatesDictionary; @@ -39,13 +40,20 @@ protected virtual SortedDictionary ParseTaxRates(string taxRat continue; string[] taxes = line.Split(new [] { ':' }); - if (taxes.Length == 2) + if (taxes.Length == 6) { try { - decimal taxRate = decimal.Parse(taxes[0].Trim(), CultureInfo.InvariantCulture); - decimal taxValue = decimal.Parse(taxes[1].Trim(), CultureInfo.InvariantCulture); - taxRatesDictionary.Add(taxRate, taxValue); + decimal rate = decimal.Parse(taxes[0].Trim(), CultureInfo.InvariantCulture); + taxRatesDictionary.Add(rate, new TaxRateRec() + { + VatRate = rate, + Amount = decimal.Parse(taxes[1].Trim(), CultureInfo.InvariantCulture), + DiscountAmount = decimal.Parse(taxes[2].Trim(), CultureInfo.InvariantCulture), + BaseAmount = decimal.Parse(taxes[3].Trim(), CultureInfo.InvariantCulture), + VatAmount = decimal.Parse(taxes[4].Trim(), CultureInfo.InvariantCulture), + AmountIncludingVAT = decimal.Parse(taxes[5].Trim(), CultureInfo.InvariantCulture) + }); } catch (Exception exc) { @@ -56,7 +64,15 @@ protected virtual SortedDictionary ParseTaxRates(string taxRat //add at least one tax rate (0%) if (!taxRatesDictionary.Any()) - taxRatesDictionary.Add(decimal.Zero, decimal.Zero); + taxRatesDictionary.Add(decimal.Zero, new TaxRateRec() + { + VatRate = decimal.Zero, + Amount = decimal.Zero, + DiscountAmount = decimal.Zero, + BaseAmount = decimal.Zero, + VatAmount = decimal.Zero, + AmountIncludingVAT = decimal.Zero + }); return taxRatesDictionary; } @@ -196,7 +212,7 @@ protected virtual SortedDictionary ParseTaxRates(string taxRat public decimal OrderDiscount { get; set; } /// - /// Gets or sets the order total + /// Gets or sets the order total to pay /// public decimal OrderTotal { get; set; } @@ -209,7 +225,7 @@ protected virtual SortedDictionary ParseTaxRates(string taxRat /// Gets or sets the reward points history entry identifier when reward points were earned (gained) for placing this order /// public int? RewardPointsHistoryEntryId { get; set; } - + /// /// Gets or sets the checkout attribute description /// @@ -309,7 +325,7 @@ protected virtual SortedDictionary ParseTaxRates(string taxRat /// Gets or sets the paid date and time /// public DateTime? PaidDateUtc { get; set; } - + /// /// Gets or sets the shipping method /// @@ -336,10 +352,23 @@ protected virtual SortedDictionary ParseTaxRates(string taxRat public DateTime CreatedOnUtc { get; set; } /// - /// Gets or sets the custom order number without prefix + /// Gets or sets the invoice ID + /// + public string InvoiceId { get; set; } + /// + /// Gets or sets the invoice date UTC + /// + public DateTime? InvoiceDateUtc { get; set; } + + /// + /// Gets or sets the order total base amount excl. tax /// - public string CustomOrderNumber { get; set; } + public decimal OrderAmount { get; set; } //MF 09.12.16 + /// + /// Gets or sets the order total amount incl. tax + /// + public decimal OrderAmountIncl { get; set; } //MF 09.12.16 #endregion #region Navigation properties @@ -481,14 +510,26 @@ public TaxDisplayType CustomerTaxDisplayType /// /// Gets the applied tax rates /// - public SortedDictionary TaxRatesDictionary + public SortedDictionary TaxRatesDictionary { get { return ParseTaxRates(this.TaxRates); } } - + #endregion } + + #region Nested classes + public partial class TaxRateRec + { + public decimal VatRate { get; set; } + public decimal Amount { get; set; } + public decimal DiscountAmount { get; set; } + public decimal BaseAmount { get; set; } + public decimal VatAmount { get; set; } + public decimal AmountIncludingVAT { get; set; } + } + #endregion } diff --git a/src/Libraries/Nop.Core/Domain/Orders/OrderItem.cs b/src/Libraries/Nop.Core/Domain/Orders/OrderItem.cs index 00782772ec7..5c35a560b87 100644 --- a/src/Libraries/Nop.Core/Domain/Orders/OrderItem.cs +++ b/src/Libraries/Nop.Core/Domain/Orders/OrderItem.cs @@ -75,7 +75,7 @@ public partial class OrderItem : BaseEntity /// Gets or sets the product attributes in XML format /// public string AttributesXml { get; set; } - + /// /// Gets or sets the download count /// @@ -125,5 +125,10 @@ public virtual ICollection AssociatedGiftCards get { return _associatedGiftCards ?? (_associatedGiftCards = new List()); } protected set { _associatedGiftCards = value; } } + + /// + /// VAT% of product + /// + public decimal VatRate { get; set; } //MF 25.11.16 } } diff --git a/src/Libraries/Nop.Core/Domain/Orders/OrderSettings.cs b/src/Libraries/Nop.Core/Domain/Orders/OrderSettings.cs index adc1cf5fb00..99e1351611f 100644 --- a/src/Libraries/Nop.Core/Domain/Orders/OrderSettings.cs +++ b/src/Libraries/Nop.Core/Domain/Orders/OrderSettings.cs @@ -120,6 +120,14 @@ public class OrderSettings : ISettings /// Gets or sets a value indicating whether an order status should be set to "Complete" only when its shipping status is "Delivered". Otherwise, "Shipped" status will be enough. /// public bool CompleteOrderWhenDelivered { get; set; } + /// + /// Last issued Invoice Id + /// + public int InvoiceIdent { get; set; } + /// + /// Last issued Invoice Date + /// + public int InvoiceYear { get; set; } /// /// Gets or sets a custom order number mask diff --git a/src/Libraries/Nop.Core/Domain/Orders/ShoppingCartItem.cs b/src/Libraries/Nop.Core/Domain/Orders/ShoppingCartItem.cs index 94d18561078..62d533609e5 100644 --- a/src/Libraries/Nop.Core/Domain/Orders/ShoppingCartItem.cs +++ b/src/Libraries/Nop.Core/Domain/Orders/ShoppingCartItem.cs @@ -43,7 +43,6 @@ public partial class ShoppingCartItem : BaseEntity /// Gets or sets the quantity /// public int Quantity { get; set; } - /// /// Gets or sets the rental product start date (null if it's not a rental product) /// @@ -63,7 +62,7 @@ public partial class ShoppingCartItem : BaseEntity /// Gets or sets the date and time of instance update /// public DateTime UpdatedOnUtc { get; set; } - + /// /// Gets the log type /// @@ -119,7 +118,7 @@ public bool IsShipEnabled /// /// Gets the additional shipping charge - /// + /// public decimal AdditionalShippingCharge { get @@ -145,5 +144,19 @@ public bool IsTaxExempt return false; } } + //fields for restored cart + /// + /// VatRate for restored cart. Only used by UpdateOrderTotal and can be null + /// + public decimal? VatRate { get; set; } + /// + /// Subtotal of item with tax + /// + public decimal? SubTotalInclTax { get; set; } + + /// + /// Subtotal of item without tax + /// + public decimal? SubTotalExclTax { get; set; } } } diff --git a/src/Libraries/Nop.Data/Mapping/Orders/OrderItemMap.cs b/src/Libraries/Nop.Data/Mapping/Orders/OrderItemMap.cs index bc9b4de313a..99b6ea2c6d0 100644 --- a/src/Libraries/Nop.Data/Mapping/Orders/OrderItemMap.cs +++ b/src/Libraries/Nop.Data/Mapping/Orders/OrderItemMap.cs @@ -17,6 +17,7 @@ public OrderItemMap() this.Property(orderItem => orderItem.DiscountAmountExclTax).HasPrecision(18, 4); this.Property(orderItem => orderItem.OriginalProductCost).HasPrecision(18, 4); this.Property(orderItem => orderItem.ItemWeight).HasPrecision(18, 4); + this.Property(orderItem => orderItem.VatRate).HasPrecision(18, 4); //MF 25.11.16 this.HasRequired(orderItem => orderItem.Order) diff --git a/src/Libraries/Nop.Data/Mapping/Orders/OrderMap.cs b/src/Libraries/Nop.Data/Mapping/Orders/OrderMap.cs index 74eb3da187a..d8a0ee13603 100644 --- a/src/Libraries/Nop.Data/Mapping/Orders/OrderMap.cs +++ b/src/Libraries/Nop.Data/Mapping/Orders/OrderMap.cs @@ -19,6 +19,8 @@ public OrderMap() this.Property(o => o.PaymentMethodAdditionalFeeExclTax).HasPrecision(18, 4); this.Property(o => o.OrderTax).HasPrecision(18, 4); this.Property(o => o.OrderDiscount).HasPrecision(18, 4); + this.Property(o => o.OrderAmount).HasPrecision(18, 4); //MF 09.12.16 + this.Property(o => o.OrderAmountIncl).HasPrecision(18, 4); //MF 09.12.16 this.Property(o => o.OrderTotal).HasPrecision(18, 4); this.Property(o => o.RefundedAmount).HasPrecision(18, 4); this.Property(o => o.CustomOrderNumber).IsRequired(); @@ -28,11 +30,11 @@ public OrderMap() this.Ignore(o => o.ShippingStatus); this.Ignore(o => o.CustomerTaxDisplayType); this.Ignore(o => o.TaxRatesDictionary); - + this.HasRequired(o => o.Customer) .WithMany() .HasForeignKey(o => o.CustomerId); - + //code below is commented because it causes some issues on big databases - http://www.nopcommerce.com/boards/t/11126/bug-version-20-command-confirm-takes-several-minutes-using-big-databases.aspx //this.HasRequired(o => o.BillingAddress).WithOptional().Map(x => x.MapKey("BillingAddressId")).WillCascadeOnDelete(false); //this.HasOptional(o => o.ShippingAddress).WithOptionalDependent().Map(x => x.MapKey("ShippingAddressId")).WillCascadeOnDelete(false); diff --git a/src/Libraries/Nop.Data/Mapping/Orders/ShoppingCartItemMap.cs b/src/Libraries/Nop.Data/Mapping/Orders/ShoppingCartItemMap.cs index 4f14308ba1b..edaa5eb28cd 100644 --- a/src/Libraries/Nop.Data/Mapping/Orders/ShoppingCartItemMap.cs +++ b/src/Libraries/Nop.Data/Mapping/Orders/ShoppingCartItemMap.cs @@ -16,6 +16,9 @@ public ShoppingCartItemMap() this.Ignore(sci => sci.IsShipEnabled); this.Ignore(sci => sci.AdditionalShippingCharge); this.Ignore(sci => sci.IsTaxExempt); + this.Ignore(sci => sci.VatRate); + this.Ignore(sci => sci.SubTotalExclTax); + this.Ignore(sci => sci.SubTotalInclTax); this.HasRequired(sci => sci.Customer) .WithMany(c => c.ShoppingCartItems) diff --git a/src/Libraries/Nop.Services/Catalog/IPriceFormatter.cs b/src/Libraries/Nop.Services/Catalog/IPriceFormatter.cs index 1ff03d84f61..1ce377eb4d5 100644 --- a/src/Libraries/Nop.Services/Catalog/IPriceFormatter.cs +++ b/src/Libraries/Nop.Services/Catalog/IPriceFormatter.cs @@ -43,7 +43,7 @@ public partial interface IPriceFormatter /// A value indicating whether to show tax suffix /// Language /// Price - string FormatPrice(decimal price, bool showCurrency, + string FormatPrice(decimal price, bool showCurrency, string currencyCode, bool showTax, Language language); /// @@ -67,7 +67,7 @@ string FormatPrice(decimal price, bool showCurrency, /// Language /// A value indicating whether price includes tax /// Price - string FormatPrice(decimal price, bool showCurrency, + string FormatPrice(decimal price, bool showCurrency, Currency targetCurrency, Language language, bool priceIncludesTax); /// @@ -80,7 +80,7 @@ string FormatPrice(decimal price, bool showCurrency, /// A value indicating whether price includes tax /// A value indicating whether to show tax suffix /// Price - string FormatPrice(decimal price, bool showCurrency, + string FormatPrice(decimal price, bool showCurrency, Currency targetCurrency, Language language, bool priceIncludesTax, bool showTax); /// @@ -110,7 +110,7 @@ string FormatPrice(decimal price, bool showCurrency, /// Language /// A value indicating whether price includes tax /// Price - string FormatShippingPrice(decimal price, bool showCurrency, + string FormatShippingPrice(decimal price, bool showCurrency, Currency targetCurrency, Language language, bool priceIncludesTax); /// /// Formats the shipping price @@ -122,9 +122,9 @@ string FormatShippingPrice(decimal price, bool showCurrency, /// A value indicating whether price includes tax /// A value indicating whether to show tax suffix /// Price - string FormatShippingPrice(decimal price, bool showCurrency, + string FormatShippingPrice(decimal price, bool showCurrency, Currency targetCurrency, Language language, bool priceIncludesTax, bool showTax); - + /// /// Formats the shipping price /// @@ -134,7 +134,7 @@ string FormatShippingPrice(decimal price, bool showCurrency, /// Language /// A value indicating whether price includes tax /// Price - string FormatShippingPrice(decimal price, bool showCurrency, + string FormatShippingPrice(decimal price, bool showCurrency, string currencyCode, Language language, bool priceIncludesTax); @@ -169,7 +169,7 @@ string FormatPaymentMethodAdditionalFee(decimal price, bool showCurrency, /// A value indicating whether price includes tax /// A value indicating whether to show tax suffix /// Price - string FormatPaymentMethodAdditionalFee(decimal price, bool showCurrency, + string FormatPaymentMethodAdditionalFee(decimal price, bool showCurrency, Currency targetCurrency, Language language, bool priceIncludesTax, bool showTax); /// @@ -181,7 +181,7 @@ string FormatPaymentMethodAdditionalFee(decimal price, bool showCurrency, /// Language /// A value indicating whether price includes tax /// Price - string FormatPaymentMethodAdditionalFee(decimal price, bool showCurrency, + string FormatPaymentMethodAdditionalFee(decimal price, bool showCurrency, string currencyCode, Language language, bool priceIncludesTax); @@ -192,5 +192,24 @@ string FormatPaymentMethodAdditionalFee(decimal price, bool showCurrency, /// Tax rate /// Formatted tax rate string FormatTaxRate(decimal taxRate); + + /// + /// Adds tax suffix to text + /// + /// Text to format + /// Language + /// A value indicating whether price includes tax + /// + string FormatTaxString(string text, Language language, bool priceIncludesTax); + + /// + /// Adds tax suffix to text + /// + /// Text to format + /// Language + /// A value indicating whether price includes tax + /// Optional. A value indicating whether to show tax suffix. + /// + string FormatTaxString(string text, Language language, bool priceIncludesTax, bool showTax); } } diff --git a/src/Libraries/Nop.Services/Catalog/PriceFormatter.cs b/src/Libraries/Nop.Services/Catalog/PriceFormatter.cs index 0f026f4b19a..e0b37650166 100644 --- a/src/Libraries/Nop.Services/Catalog/PriceFormatter.cs +++ b/src/Libraries/Nop.Services/Catalog/PriceFormatter.cs @@ -169,7 +169,7 @@ public virtual string FormatPrice(decimal price, bool showCurrency, public virtual string FormatPrice(decimal price, bool showCurrency, string currencyCode, Language language, bool priceIncludesTax) { - var currency = _currencyService.GetCurrencyByCode(currencyCode) + var currency = _currencyService.GetCurrencyByCode(currencyCode) ?? new Currency { CurrencyCode = currencyCode @@ -186,10 +186,10 @@ public virtual string FormatPrice(decimal price, bool showCurrency, /// Language /// A value indicating whether price includes tax /// Price - public virtual string FormatPrice(decimal price, bool showCurrency, + public virtual string FormatPrice(decimal price, bool showCurrency, Currency targetCurrency, Language language, bool priceIncludesTax) { - return FormatPrice(price, showCurrency, targetCurrency, language, + return FormatPrice(price, showCurrency, targetCurrency, language, priceIncludesTax, _taxSettings.DisplayTaxSuffix); } @@ -203,12 +203,12 @@ public virtual string FormatPrice(decimal price, bool showCurrency, /// A value indicating whether price includes tax /// A value indicating whether to show tax suffix /// Price - public virtual string FormatPrice(decimal price, bool showCurrency, + public virtual string FormatPrice(decimal price, bool showCurrency, Currency targetCurrency, Language language, bool priceIncludesTax, bool showTax) { //we should round it no matter of "ShoppingCartSettings.RoundPricesDuringCalculation" setting price = RoundingHelper.RoundPrice(price); - + string currencyString = GetCurrencyString(price, showCurrency, targetCurrency); if (showTax) { @@ -228,7 +228,7 @@ public virtual string FormatPrice(decimal price, bool showCurrency, } return string.Format(formatStr, currencyString); } - + return currencyString; } @@ -293,7 +293,7 @@ public virtual string FormatShippingPrice(decimal price, bool showCurrency) /// Language /// A value indicating whether price includes tax /// Price - public virtual string FormatShippingPrice(decimal price, bool showCurrency, + public virtual string FormatShippingPrice(decimal price, bool showCurrency, Currency targetCurrency, Language language, bool priceIncludesTax) { bool showTax = _taxSettings.ShippingIsTaxable && _taxSettings.DisplayTaxSuffix; @@ -310,12 +310,12 @@ public virtual string FormatShippingPrice(decimal price, bool showCurrency, /// A value indicating whether price includes tax /// A value indicating whether to show tax suffix /// Price - public virtual string FormatShippingPrice(decimal price, bool showCurrency, + public virtual string FormatShippingPrice(decimal price, bool showCurrency, Currency targetCurrency, Language language, bool priceIncludesTax, bool showTax) { return FormatPrice(price, showCurrency, targetCurrency, language, priceIncludesTax, showTax); } - + /// /// Formats the shipping price /// @@ -325,10 +325,10 @@ public virtual string FormatShippingPrice(decimal price, bool showCurrency, /// Language /// A value indicating whether price includes tax /// Price - public virtual string FormatShippingPrice(decimal price, bool showCurrency, + public virtual string FormatShippingPrice(decimal price, bool showCurrency, string currencyCode, Language language, bool priceIncludesTax) { - var currency = _currencyService.GetCurrencyByCode(currencyCode) + var currency = _currencyService.GetCurrencyByCode(currencyCode) ?? new Currency { CurrencyCode = currencyCode @@ -347,7 +347,7 @@ public virtual string FormatShippingPrice(decimal price, bool showCurrency, public virtual string FormatPaymentMethodAdditionalFee(decimal price, bool showCurrency) { bool priceIncludesTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; - return FormatPaymentMethodAdditionalFee(price, showCurrency, _workContext.WorkingCurrency, + return FormatPaymentMethodAdditionalFee(price, showCurrency, _workContext.WorkingCurrency, _workContext.WorkingLanguage, priceIncludesTax); } @@ -377,10 +377,10 @@ public virtual string FormatPaymentMethodAdditionalFee(decimal price, bool showC /// A value indicating whether price includes tax /// A value indicating whether to show tax suffix /// Price - public virtual string FormatPaymentMethodAdditionalFee(decimal price, bool showCurrency, + public virtual string FormatPaymentMethodAdditionalFee(decimal price, bool showCurrency, Currency targetCurrency, Language language, bool priceIncludesTax, bool showTax) { - return FormatPrice(price, showCurrency, targetCurrency, language, + return FormatPrice(price, showCurrency, targetCurrency, language, priceIncludesTax, showTax); } @@ -393,7 +393,7 @@ public virtual string FormatPaymentMethodAdditionalFee(decimal price, bool showC /// Language /// A value indicating whether price includes tax /// Price - public virtual string FormatPaymentMethodAdditionalFee(decimal price, bool showCurrency, + public virtual string FormatPaymentMethodAdditionalFee(decimal price, bool showCurrency, string currencyCode, Language language, bool priceIncludesTax) { var currency = _currencyService.GetCurrencyByCode(currencyCode) @@ -401,7 +401,7 @@ public virtual string FormatPaymentMethodAdditionalFee(decimal price, bool showC { CurrencyCode = currencyCode }; - return FormatPaymentMethodAdditionalFee(price, showCurrency, currency, + return FormatPaymentMethodAdditionalFee(price, showCurrency, currency, language, priceIncludesTax); } @@ -417,6 +417,50 @@ public virtual string FormatTaxRate(decimal taxRate) return taxRate.ToString("G29"); } + /// + /// Adds tax suffix to text + /// + /// Text to format + /// Language + /// A value indicating whether price includes tax + /// + public virtual string FormatTaxString(string text, Language language, bool priceIncludesTax) + { + bool showTax = _taxSettings.DisplayTaxSuffix; + return FormatTaxString(text, language, priceIncludesTax, showTax); + } + /// + /// Adds tax suffix to text + /// + /// Text to format + /// Language + /// A value indicating whether price includes tax + /// Optional. A value indicating whether to show tax suffix. + /// + public virtual string FormatTaxString(string text, Language language, bool priceIncludesTax, bool showTax) + { + + if (showTax) + { + //show tax suffix + string formatStr; + if (priceIncludesTax) + { + formatStr = _localizationService.GetResource("Products.InclTaxSuffix", language.Id, false); + if (String.IsNullOrEmpty(formatStr)) + formatStr = "{0} incl tax"; + } + else + { + formatStr = _localizationService.GetResource("Products.ExclTaxSuffix", language.Id, false); + if (String.IsNullOrEmpty(formatStr)) + formatStr = "{0} excl tax"; + } + return string.Format(formatStr, text); + } + + return text; + } #endregion } } diff --git a/src/Libraries/Nop.Services/Common/PdfService.cs b/src/Libraries/Nop.Services/Common/PdfService.cs index 8ae14490aa7..59bcc8006ee 100644 --- a/src/Libraries/Nop.Services/Common/PdfService.cs +++ b/src/Libraries/Nop.Services/Common/PdfService.cs @@ -410,18 +410,18 @@ public virtual void PrintOrdersToPdf(Stream stream, IList orders, int lan } else if (order.PickupAddress != null) - { - shippingAddress.AddCell(new Paragraph(_localizationService.GetResource("PDFInvoice.Pickup", lang.Id), titleFont)); - if (!string.IsNullOrEmpty(order.PickupAddress.Address1)) - shippingAddress.AddCell(new Paragraph(string.Format(" {0}", string.Format(_localizationService.GetResource("PDFInvoice.Address", lang.Id), order.PickupAddress.Address1)), font)); - if (!string.IsNullOrEmpty(order.PickupAddress.City)) - shippingAddress.AddCell(new Paragraph(string.Format(" {0}", order.PickupAddress.City), font)); - if (order.PickupAddress.Country != null) - shippingAddress.AddCell(new Paragraph(string.Format(" {0}", order.PickupAddress.Country.GetLocalized(x => x.Name, lang.Id)), font)); - if (!string.IsNullOrEmpty(order.PickupAddress.ZipPostalCode)) - shippingAddress.AddCell(new Paragraph(string.Format(" {0}", order.PickupAddress.ZipPostalCode), font)); - shippingAddress.AddCell(new Paragraph(" ")); - } + { + shippingAddress.AddCell(new Paragraph(_localizationService.GetResource("PDFInvoice.Pickup", lang.Id), titleFont)); + if (!string.IsNullOrEmpty(order.PickupAddress.Address1)) + shippingAddress.AddCell(new Paragraph(string.Format(" {0}", string.Format(_localizationService.GetResource("PDFInvoice.Address", lang.Id), order.PickupAddress.Address1)), font)); + if (!string.IsNullOrEmpty(order.PickupAddress.City)) + shippingAddress.AddCell(new Paragraph(string.Format(" {0}", order.PickupAddress.City), font)); + if (order.PickupAddress.Country != null) + shippingAddress.AddCell(new Paragraph(string.Format(" {0}", order.PickupAddress.Country.GetLocalized(x => x.Name, lang.Id)), font)); + if (!string.IsNullOrEmpty(order.PickupAddress.ZipPostalCode)) + shippingAddress.AddCell(new Paragraph(string.Format(" {0}", order.PickupAddress.ZipPostalCode), font)); + shippingAddress.AddCell(new Paragraph(" ")); + } shippingAddress.AddCell(new Paragraph(" " + String.Format(_localizationService.GetResource("PDFInvoice.ShippingMethod", lang.Id), order.ShippingMethod), font)); shippingAddress.AddCell(new Paragraph()); @@ -734,7 +734,7 @@ public virtual void PrintOrdersToPdf(Stream stream, IList orders, int lan //tax string taxStr = string.Empty; - var taxRates = new SortedDictionary(); + var taxRates = new SortedDictionary(); bool displayTax = true; bool displayTaxRates = true; if (_taxSettings.HideTaxInOrderSummary && order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax) @@ -771,7 +771,7 @@ public virtual void PrintOrdersToPdf(Stream stream, IList orders, int lan foreach (var item in taxRates) { string taxRate = String.Format(_localizationService.GetResource("PDFInvoice.TaxRate", lang.Id), _priceFormatter.FormatTaxRate(item.Key)); - string taxValue = _priceFormatter.FormatPrice(_currencyService.ConvertCurrency(item.Value, order.CurrencyRate), true, order.CustomerCurrencyCode, false, lang); + string taxValue = _priceFormatter.FormatPrice(_currencyService.ConvertCurrency(item.Value.VatAmount, order.CurrencyRate), true, order.CustomerCurrencyCode, false, lang); var p = new PdfPCell(new Paragraph(String.Format("{0} {1}", taxRate, taxValue), font)); p.HorizontalAlignment = Element.ALIGN_RIGHT; @@ -1072,19 +1072,19 @@ public virtual void PrintPackagingSlipsToPdf(Stream stream, IList ship } else if (order.PickupAddress != null) - { - addressTable.AddCell(new Paragraph(_localizationService.GetResource("PDFInvoice.Pickup", lang.Id), titleFont)); - if (!string.IsNullOrEmpty(order.PickupAddress.Address1)) - addressTable.AddCell(new Paragraph(string.Format(" {0}", string.Format(_localizationService.GetResource("PDFInvoice.Address", lang.Id), order.PickupAddress.Address1)), font)); - if (!string.IsNullOrEmpty(order.PickupAddress.City)) - addressTable.AddCell(new Paragraph(string.Format(" {0}", order.PickupAddress.City), font)); - if (order.PickupAddress.Country != null) - addressTable.AddCell(new Paragraph(string.Format(" {0}", order.PickupAddress.Country.GetLocalized(x => x.Name, lang.Id)), font)); - if (!string.IsNullOrEmpty(order.PickupAddress.ZipPostalCode)) - addressTable.AddCell(new Paragraph(string.Format(" {0}", order.PickupAddress.ZipPostalCode), font)); - addressTable.AddCell(new Paragraph(" ")); - } - + { + addressTable.AddCell(new Paragraph(_localizationService.GetResource("PDFInvoice.Pickup", lang.Id), titleFont)); + if (!string.IsNullOrEmpty(order.PickupAddress.Address1)) + addressTable.AddCell(new Paragraph(string.Format(" {0}", string.Format(_localizationService.GetResource("PDFInvoice.Address", lang.Id), order.PickupAddress.Address1)), font)); + if (!string.IsNullOrEmpty(order.PickupAddress.City)) + addressTable.AddCell(new Paragraph(string.Format(" {0}", order.PickupAddress.City), font)); + if (order.PickupAddress.Country != null) + addressTable.AddCell(new Paragraph(string.Format(" {0}", order.PickupAddress.Country.GetLocalized(x => x.Name, lang.Id)), font)); + if (!string.IsNullOrEmpty(order.PickupAddress.ZipPostalCode)) + addressTable.AddCell(new Paragraph(string.Format(" {0}", order.PickupAddress.ZipPostalCode), font)); + addressTable.AddCell(new Paragraph(" ")); + } + addressTable.AddCell(new Paragraph(" ")); addressTable.AddCell(new Paragraph(String.Format(_localizationService.GetResource("PDFPackagingSlip.ShippingMethod", lang.Id), order.ShippingMethod), font)); diff --git a/src/Libraries/Nop.Services/Common/PdfService7.cs b/src/Libraries/Nop.Services/Common/PdfService7.cs new file mode 100644 index 00000000000..e8c112a4652 --- /dev/null +++ b/src/Libraries/Nop.Services/Common/PdfService7.cs @@ -0,0 +1,1072 @@ +// RTL Support provided by Credo inc (www.credo.co.il || info@credo.co.il) + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using Nop.Core; +using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Common; +using Nop.Core.Domain.Directory; +using Nop.Core.Domain.Orders; +using Nop.Core.Domain.Shipping; +using Nop.Core.Domain.Tax; +using Nop.Core.Html; +using Nop.Services.Catalog; +using Nop.Services.Configuration; +using Nop.Services.Directory; +using Nop.Services.Helpers; +using Nop.Services.Localization; +using Nop.Services.Media; +using Nop.Services.Orders; +using Nop.Services.Payments; +using Nop.Services.Stores; +using iText.Kernel.Font; +using iText.IO.Font; +using iText.Kernel.Geom; +using iText.Kernel.Pdf; +using iText.Kernel.Pdf.Canvas; +using iText.Layout; +using iText.Layout.Element; +using iText.Layout.Borders; +using iText.Layout.Properties; +using iText.Kernel.Colors; +using iText.Kernel.Pdf.Action; +using iText.IO.Image; +using iText.Kernel.Pdf.Xobject; +using iText.Kernel.Events; +using Nop.Services.Tax; +using System.Text.RegularExpressions; + +namespace Nop.Services.Common +{ + /// + /// PDF service + /// + public partial class PdfService7 : IPdfService + { + #region Fields + + private readonly ILocalizationService _localizationService; + private readonly ILanguageService _languageService; + private readonly IWorkContext _workContext; + private readonly IOrderService _orderService; + private readonly IPaymentService _paymentService; + private readonly IDateTimeHelper _dateTimeHelper; + private readonly IPriceFormatter _priceFormatter; + private readonly ICurrencyService _currencyService; + private readonly IMeasureService _measureService; + private readonly IPictureService _pictureService; + private readonly IProductService _productService; + private readonly IProductAttributeParser _productAttributeParser; + private readonly IStoreService _storeService; + private readonly IStoreContext _storeContext; + private readonly ISettingService _settingContext; + private readonly IAddressAttributeFormatter _addressAttributeFormatter; + + private readonly CatalogSettings _catalogSettings; + private readonly CurrencySettings _currencySettings; + private readonly MeasureSettings _measureSettings; + private readonly PdfSettings _pdfSettings; + private readonly TaxSettings _taxSettings; + private readonly AddressSettings _addressSettings; + private readonly ICheckoutAttributeParser _checkoutAttributeParser; + private readonly ITaxService _taxService; + + #endregion + + #region Ctor + + public PdfService7(ILocalizationService localizationService, + ILanguageService languageService, + IWorkContext workContext, + IOrderService orderService, + IPaymentService paymentService, + IDateTimeHelper dateTimeHelper, + IPriceFormatter priceFormatter, + ICurrencyService currencyService, + IMeasureService measureService, + IPictureService pictureService, + IProductService productService, + IProductAttributeParser productAttributeParser, + IStoreService storeService, + IStoreContext storeContext, + ISettingService settingContext, + IAddressAttributeFormatter addressAttributeFormatter, + CatalogSettings catalogSettings, + CurrencySettings currencySettings, + MeasureSettings measureSettings, + PdfSettings pdfSettings, + TaxSettings taxSettings, + AddressSettings addressSettings, + ICheckoutAttributeParser checkoutAttributeParser, + ITaxService taxService) + { + this._localizationService = localizationService; + this._languageService = languageService; + this._workContext = workContext; + this._orderService = orderService; + this._paymentService = paymentService; + this._dateTimeHelper = dateTimeHelper; + this._priceFormatter = priceFormatter; + this._currencyService = currencyService; + this._measureService = measureService; + this._pictureService = pictureService; + this._productService = productService; + this._productAttributeParser = productAttributeParser; + this._storeService = storeService; + this._storeContext = storeContext; + this._settingContext = settingContext; + this._addressAttributeFormatter = addressAttributeFormatter; + this._currencySettings = currencySettings; + this._catalogSettings = catalogSettings; + this._measureSettings = measureSettings; + this._pdfSettings = pdfSettings; + this._taxSettings = taxSettings; + this._addressSettings = addressSettings; + this._checkoutAttributeParser = checkoutAttributeParser; + this._taxService = taxService; + } + + #endregion + + #region Utilities + + #region fonts + /// + /// Get font + /// + /// Font + protected virtual PdfFont GetFont() + { + //nopCommerce supports unicode characters + //nopCommerce uses Free Serif font by default (~/App_Data/Pdf/FreeSerif.ttf file) + //It was downloaded from http://savannah.gnu.org/projects/freefont + return GetFont(_pdfSettings.FontFileName); + } + /// + /// Get font + /// + /// Font file name + /// Font + protected virtual PdfFont GetFont(string fontFileName) + { + //if (fontFileName == null) + // throw new ArgumentNullException("fontFileName"); + + //string fontPath = Path.Combine(CommonHelper.MapPath("~/App_Data/Pdf/"), fontFileName); + //var baseFont = BaseFont.CreateFont(fontPath, BaseFont.IDENTITY_H, BaseFont.EMBEDDED); + var font = PdfFontFactory.CreateFont(FontConstants.HELVETICA, PdfEncodings.CP1252, true); + return font; + } + /// + /// Get font bold + /// + /// Font + protected virtual PdfFont GetFontBold() + { + var font = PdfFontFactory.CreateFont(FontConstants.HELVETICA_BOLD, PdfEncodings.CP1252, true); + return font; + } + /// + /// Get font italic + /// + /// Font + protected virtual PdfFont GetFontItalic() + { + var font = PdfFontFactory.CreateFont(FontConstants.HELVETICA_OBLIQUE, PdfEncodings.CP1252, true); + return font; + } + /// + /// Get font bold italic + /// + /// Font + protected virtual PdfFont GetFontBoldItalic() + { + var font = PdfFontFactory.CreateFont(FontConstants.HELVETICA_BOLDOBLIQUE, PdfEncodings.CP1252, true); + return font; + } + #endregion + + #region generic + protected virtual float millimetersToPoints(float value) + { + return (value / 25.4f) * 72f; + } + + protected virtual string TakeCountLines(string text, int count) + { + string lines = ""; + int i = 0; + Match match = Regex.Match(text, "^.*$", RegexOptions.Multiline); + + while (match.Success && i < count) + { + lines += match + "\n"; + match = match.NextMatch(); + i++; + } + + return lines; + } + #endregion + #region pagehandler + private class PageNumSetter : IEventHandler + { + PdfFormXObject placeholder; + float side = 20; + float space = 3.5f; + float descent = 3; + float x; float y; + string txtPage = "Page {0}"; + int pageOffset = 0; + + //public string txtPage { get; set; } = "Page {0}"; + //public int pageOffset { get; set; } = 0; + + public PageNumSetter(float posX, float posY, string TxtPage, int PageOffset) + { + placeholder = new PdfFormXObject(new Rectangle(0, 0, side, side)); + x = posX; + y = posY; + txtPage = TxtPage; + pageOffset = PageOffset; + + } + + public void HandleEvent(Event e) + { + PdfDocumentEvent docEvent = (PdfDocumentEvent)e; + PdfDocument pdf = docEvent.GetDocument(); + PdfPage page = docEvent.GetPage(); + int pageNumber = pdf.GetPageNumber(page) - pageOffset; + Rectangle pageSize = page.GetPageSize(); + PdfCanvas pdfCanvas = new PdfCanvas(page.NewContentStreamBefore(), page.GetResources(), pdf); + Canvas canvas = new Canvas(pdfCanvas, pdf, pageSize); + Paragraph p = new Paragraph() + .Add(string.Format(txtPage, pageNumber)).Add(" / ").SetFontSize(8); + canvas.ShowTextAligned(p, x, y, TextAlignment.RIGHT); + pdfCanvas.AddXObject(placeholder, x + space, y - descent); + pdfCanvas.Release(); + + + } + public void writeTotPageNum(PdfDocument pdf) + { + Canvas canvas = new Canvas(placeholder, pdf); + Paragraph p = new Paragraph() + .Add((pdf.GetNumberOfPages() - pageOffset).ToString()).SetFontSize(8); + canvas.ShowTextAligned(p, 0, descent, TextAlignment.LEFT); + } + + + } + #endregion + #endregion + + #region Methods + + /// + /// Print an order to PDF + /// + /// Order + /// Language identifier; 0 to use a language used when placing an order + /// Vendor identifier to limit products; 0 to to print all products. If specified, then totals won't be printed + /// A path of generated file + public virtual string PrintOrderToPdf(Order order, int languageId = 0, int vendorId = 0) + { + if (order == null) + throw new ArgumentNullException("order"); + + string fileName = string.Format("order_{0}_{1}.pdf", order.OrderGuid, CommonHelper.GenerateRandomDigitCode(4)); + string filePath = System.IO.Path.Combine(CommonHelper.MapPath("~/content/files/ExportImport"), fileName); + using (var fileStream = new FileStream(filePath, FileMode.Create)) + { + var orders = new List(); + orders.Add(order); + PrintOrdersToPdf(fileStream, orders, languageId, vendorId); + } + return filePath; + } + + /// + /// Print orders to PDF + /// + /// Stream + /// Orders + /// Language identifier; 0 to use a language used when placing an order + /// Vendor identifier to limit products; 0 to to print all products. If specified, then totals won't be printed + public virtual void PrintOrdersToPdf(Stream stream, IList orders, int languageId = 0, int vendorId = 0) + { + #region doc settings + if (stream == null) + throw new ArgumentNullException("stream"); + + if (orders == null) + throw new ArgumentNullException("orders"); + + var pageSize = PageSize.A4; //595 x 842 + + if (_pdfSettings.LetterPageSizeEnabled) + { + pageSize = PageSize.LETTER; + } + + var pdfWriter = new PdfWriter(stream); + var pdfDoc = new PdfDocument(pdfWriter); + var doc = new Document(pdfDoc, pageSize);//, false); + + //store footer properties + var footerY = doc.GetLeftMargin(); + var footerX = doc.GetBottomMargin(); + var footerWidht = pageSize.GetWidth() - doc.GetLeftMargin() - doc.GetRightMargin(); + + //set margin for footer + var bottomMatgin = 100f; + doc.SetBottomMargin(bottomMatgin); + + //generic vars + var cellPdf = new Cell(); + var cellPdf2 = new Cell(); + var paraPdf = new Paragraph(); + + pdfDoc.GetCatalog().SetPageLayout(PdfName.SinglePage); + //info + PdfDocumentInfo info = pdfDoc.GetDocumentInfo(); + info.SetTitle("Rechnung"); + info.SetAuthor("Förderverein The FoodCoop"); + info.SetSubject("Invoice"); + //info.SetCreator("The FoodCoop"); + + //styles + Style styleTitle = new Style().SetFont(GetFontBold()).SetFontSize(12).SetFontColor(Color.BLACK).SetHorizontalAlignment(HorizontalAlignment.LEFT).SetTextAlignment(TextAlignment.LEFT); + Style styleNormal = new Style().SetFont(GetFont()).SetFontSize(8).SetFontColor(Color.BLACK).SetHorizontalAlignment(HorizontalAlignment.LEFT).SetTextAlignment(TextAlignment.LEFT); + Style styleAttrib = new Style().SetFont(GetFontItalic()).SetFontSize(8).SetFontColor(Color.BLACK).SetHorizontalAlignment(HorizontalAlignment.LEFT).SetTextAlignment(TextAlignment.LEFT); + Style style5 = new Style().SetFont(GetFont()).SetFontSize(5).SetFontColor(Color.BLACK).SetHorizontalAlignment(HorizontalAlignment.LEFT).SetTextAlignment(TextAlignment.LEFT); + Style style5b = new Style().SetFont(GetFontBold()).SetFontSize(5).SetFontColor(Color.BLACK).SetHorizontalAlignment(HorizontalAlignment.LEFT).SetTextAlignment(TextAlignment.LEFT); + Style style6 = new Style().SetFont(GetFont()).SetFontSize(6).SetFontColor(Color.BLACK).SetHorizontalAlignment(HorizontalAlignment.LEFT).SetTextAlignment(TextAlignment.LEFT); + Style style6b = new Style().SetFont(GetFontBold()).SetFontSize(6).SetFontColor(Color.BLACK).SetHorizontalAlignment(HorizontalAlignment.LEFT).SetTextAlignment(TextAlignment.LEFT); + Style style8 = new Style().SetFont(GetFont()).SetFontSize(8).SetFontColor(Color.BLACK).SetHorizontalAlignment(HorizontalAlignment.LEFT).SetTextAlignment(TextAlignment.LEFT); + Style style8b = new Style().SetFont(GetFontBold()).SetFontSize(8).SetFontColor(Color.BLACK).SetHorizontalAlignment(HorizontalAlignment.LEFT).SetTextAlignment(TextAlignment.LEFT); + Style style9b = new Style().SetFont(GetFontBold()).SetFontSize(9).SetFontColor(Color.BLACK).SetHorizontalAlignment(HorizontalAlignment.LEFT).SetTextAlignment(TextAlignment.LEFT); + Style styleCell = new Style().SetBorder(Border.NO_BORDER).SetHorizontalAlignment(HorizontalAlignment.LEFT).SetTextAlignment(TextAlignment.LEFT); + + #endregion + + + int ordCount = orders.Count; + int ordNum = 0; + int pagesSofar = 0; + + foreach (var order in orders) + { + bool includingTax = order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax; + + //by default _pdfSettings contains settings for the current active store + //and we need PdfSettings for the store which was used to place an order + //so let's load it based on a store of the current order + var pdfSettingsByStore = _settingContext.LoadSetting(order.StoreId); + + + var lang = _languageService.GetLanguageById(languageId == 0 ? order.CustomerLanguageId : languageId); + if (lang == null || !lang.Published) + lang = _workContext.WorkingLanguage; + + var currency = _currencyService.GetCurrencyByCode(order.CustomerCurrencyCode) + ?? new Currency + { + CurrencyCode = order.CustomerCurrencyCode + }; + bool showCurrency = false; + + var pageEvent = new PageNumSetter(pageSize.GetWidth() / 2, footerX - 20, _localizationService.GetResource("PDFInvoice.Page", lang.Id) + " {0}", pagesSofar); + pdfDoc.AddEventHandler(PdfDocumentEvent.INSERT_PAGE, pageEvent); + + //there is no need for a new page as last element is a footer + //if (ordNum > 0) + // doc.Add(new AreaBreak(AreaBreakType.NEXT_PAGE)); + + //main layout is based on a single table + int col = 7; + int headerCol = 4; int logoCol = col - headerCol; + int shippAddrCol = 4; int ivAddrCol = col - shippAddrCol; + //main layout table. Will have only 1 column. + var tabPage = new Table(col).SetBorder(Border.NO_BORDER).SetWidthPercent(100f); + tabPage.SetProperty(Property.BORDER, null); + tabPage.SetProperty(Property.TEXT_ALIGNMENT, TextAlignment.LEFT); + + #region Header&Logo + + var logoPicture = _pictureService.GetPictureById(pdfSettingsByStore.LogoPictureId); + var logoExists = logoPicture != null; + + //store info + var store = _storeService.GetStoreById(order.StoreId) ?? _storeContext.CurrentStore; + var anchor = new Text(store.Url.Trim(new[] { '/' })).SetAction(PdfAction.CreateURI(store.Url)); + + paraPdf = new Paragraph(new Text(store.CompanyName).AddStyle(styleTitle)).AddStyle(styleNormal).SetMultipliedLeading(1.2f).Add("\n"); + + + //We use seetings for address and bank + if (!string.IsNullOrEmpty(pdfSettingsByStore.InvoiceFooterTextColumn1)) + { + paraPdf.Add(new Text(pdfSettingsByStore.InvoiceFooterTextColumn1).AddStyle(style5)); + paraPdf.Add(new Text("\n").AddStyle(style6)); + } + if (!string.IsNullOrEmpty(pdfSettingsByStore.InvoiceFooterTextColumn2)) + { + paraPdf.Add(new Text(pdfSettingsByStore.InvoiceFooterTextColumn2).AddStyle(style5)); + paraPdf.Add(new Text("\n").AddStyle(style6)); + } + + paraPdf.Add(anchor.AddStyle(style5)).Add("\n"); + tabPage.AddHeaderCell(new Cell(1, logoExists ? headerCol : col).Add(paraPdf).AddStyle(styleCell)); + + //logo + if (logoExists) + { + var logoFilePath = _pictureService.GetThumbLocalPath(logoPicture, 0, false); + var logo = new Image(ImageDataFactory.Create(logoFilePath)).SetAutoScale(true); + tabPage.AddHeaderCell(new Cell(1, logoCol).Add(logo).AddStyle(styleCell)); + } + + //empty Line + tabPage.AddHeaderCell(new Cell(1, col).Add("\n").AddStyle(styleCell)); + + #endregion + + #region Addresses + + #region shippingaddr + + //shipping info + if (order.ShippingStatus != ShippingStatus.ShippingNotRequired) + { + if (!order.PickUpInStore) + { + if (order.ShippingAddress == null) + throw new NopException(string.Format("Shipping is required, but address is not available. Order ID = {0}", order.Id)); + + paraPdf = new Paragraph(new Text(_localizationService.GetResource("PDFInvoice.ShippingInformation", lang.Id)).AddStyle(style8b)).AddStyle(styleNormal).SetMultipliedLeading(1.2f).SetFirstLineIndent(-5f).SetMarginLeft(5f); + paraPdf.Add("\n"); + if (!String.IsNullOrEmpty(order.ShippingAddress.Company)) + paraPdf.Add(new Text(order.ShippingAddress.Company)).Add("\n"); + if (!String.IsNullOrEmpty(order.ShippingAddress.FirstName)) + paraPdf.Add(new Text(order.ShippingAddress.FirstName)).Add(" "); + if (!String.IsNullOrEmpty(order.ShippingAddress.LastName)) + paraPdf.Add(new Text(order.ShippingAddress.LastName)).Add("\n"); + if (_addressSettings.PhoneEnabled && !String.IsNullOrEmpty(order.ShippingAddress.PhoneNumber)) + paraPdf.Add(new Text(String.Format(_localizationService.GetResource("PDFInvoice.Phone", lang.Id), order.ShippingAddress.PhoneNumber))).Add("\n"); + if (_addressSettings.FaxEnabled && !String.IsNullOrEmpty(order.ShippingAddress.FaxNumber)) + paraPdf.Add(new Text(String.Format(_localizationService.GetResource("PDFInvoice.FaxNumber", lang.Id), order.ShippingAddress.FaxNumber))).Add("\n"); + if (_addressSettings.StreetAddressEnabled && !String.IsNullOrEmpty(order.ShippingAddress.Address1)) + paraPdf.Add(new Text(order.ShippingAddress.Address1)).Add("\n"); + if (_addressSettings.StreetAddress2Enabled && !String.IsNullOrEmpty(order.ShippingAddress.Address2)) + paraPdf.Add(new Text(order.ShippingAddress.Address2)).Add("\n"); + + if (_addressSettings.CityEnabled || _addressSettings.StateProvinceEnabled || _addressSettings.ZipPostalCodeEnabled) + paraPdf.Add(new Text(String.Format("{2} {0}, {1}", order.ShippingAddress.City, order.ShippingAddress.StateProvince != null ? order.ShippingAddress.StateProvince.GetLocalized(x => x.Name, lang.Id) : "", order.ShippingAddress.ZipPostalCode))).Add("\n"); + if (_addressSettings.CountryEnabled && order.ShippingAddress.Country != null) + paraPdf.Add(new Text(String.Format("{0}", order.ShippingAddress.Country != null ? order.ShippingAddress.Country.GetLocalized(x => x.Name, lang.Id) : ""))).Add("\n"); + + //custom attributes + var customShippingAddressAttributes = _addressAttributeFormatter.FormatAttributes(order.ShippingAddress.CustomAttributes); + if (!String.IsNullOrEmpty(customShippingAddressAttributes)) + { + //TODO: we should add padding to each line (in case if we have sevaral custom address attributes) + paraPdf.Add(new Text(HtmlHelper.ConvertHtmlToPlainText(customShippingAddressAttributes, true, true))).Add("\n"); + } + } + else + if (order.PickupAddress != null) + { + paraPdf = new Paragraph(new Text(_localizationService.GetResource("PDFInvoice.Pickup", lang.Id)).AddStyle(style9b)).AddStyle(styleNormal).SetMultipliedLeading(1f).SetFirstLineIndent(5f); + paraPdf.Add("\n"); + if (!string.IsNullOrEmpty(order.PickupAddress.Address1)) + paraPdf.Add(new Text(String.Format(_localizationService.GetResource("PDFInvoice.Address", lang.Id), order.PickupAddress.Address1))).Add("\n"); + if (!string.IsNullOrEmpty(order.PickupAddress.City)) + paraPdf.Add(order.PickupAddress.City).Add("\n"); + if (order.PickupAddress.Country != null) + paraPdf.Add(order.PickupAddress.Country.GetLocalized(x => x.Name, lang.Id)).Add("\n"); + if (!string.IsNullOrEmpty(order.PickupAddress.ZipPostalCode)) + paraPdf.Add(order.PickupAddress.ZipPostalCode).Add("\n"); + + } + + tabPage.AddHeaderCell(new Cell(1, shippAddrCol).Add(paraPdf).AddStyle(styleCell)); + } + else + { + paraPdf.Add("\n"); + tabPage.AddHeaderCell(new Cell(1, shippAddrCol).Add(paraPdf).AddStyle(styleCell)); + } + + #endregion + + #region billing + //billing info + paraPdf = new Paragraph(new Text(_localizationService.GetResource("PDFInvoice.BillingInformation", lang.Id)).AddStyle(style8b)).AddStyle(styleNormal).SetMultipliedLeading(1.2f).SetFirstLineIndent(-5f).SetMarginLeft(5f); + paraPdf.Add("\n"); + //paraPdf = new Paragraph().AddStyle(styleNormal).SetMultipliedLeading(1.2f); + if (_addressSettings.CompanyEnabled && !String.IsNullOrEmpty(order.BillingAddress.Company)) + paraPdf.Add(new Text(order.BillingAddress.Company)).Add("\n"); + if (_addressSettings.CompanyEnabled && !String.IsNullOrEmpty(order.BillingAddress.FirstName)) + paraPdf.Add(new Text(order.BillingAddress.FirstName)).Add(" "); + if (_addressSettings.CompanyEnabled && !String.IsNullOrEmpty(order.BillingAddress.LastName)) + paraPdf.Add(new Text(order.BillingAddress.LastName)).Add("\n"); + if (_addressSettings.PhoneEnabled && !String.IsNullOrEmpty(order.BillingAddress.PhoneNumber)) + paraPdf.Add(new Text(String.Format(_localizationService.GetResource("PDFInvoice.Phone", lang.Id), order.BillingAddress.PhoneNumber))).Add("\n"); + if (_addressSettings.FaxEnabled && !String.IsNullOrEmpty(order.BillingAddress.FaxNumber)) + paraPdf.Add(new Text(String.Format(_localizationService.GetResource("PDFInvoice.FaxNumber", lang.Id), order.BillingAddress.FaxNumber))).Add("\n"); + if (_addressSettings.StreetAddressEnabled && !String.IsNullOrEmpty(order.BillingAddress.Address1)) + paraPdf.Add(new Text(order.BillingAddress.Address1)).Add("\n"); + if (_addressSettings.StreetAddress2Enabled && !String.IsNullOrEmpty(order.BillingAddress.Address2)) + paraPdf.Add(new Text(order.BillingAddress.Address2)).Add("\n"); + + if (_addressSettings.CityEnabled || _addressSettings.StateProvinceEnabled || _addressSettings.ZipPostalCodeEnabled) + paraPdf.Add(new Text(String.Format("{2} {0}, {1}", order.BillingAddress.City, order.BillingAddress.StateProvince != null ? order.BillingAddress.StateProvince.GetLocalized(x => x.Name, lang.Id) : "", order.BillingAddress.ZipPostalCode))).Add("\n"); + if (_addressSettings.CountryEnabled && order.BillingAddress.Country != null) + paraPdf.Add(new Text(String.Format("{0}", order.BillingAddress.Country != null ? order.BillingAddress.Country.GetLocalized(x => x.Name, lang.Id) : ""))).Add("\n"); + + //VAT number + //if (!String.IsNullOrEmpty(order.VatNumber)) + // paraPdf.Add(new Text(String.Format(_localizationService.GetResource("PDFInvoice.VATNumber", lang.Id), order.VatNumber))).Add("\n"); + + //custom attributes + var customBillingAddressAttributes = _addressAttributeFormatter.FormatAttributes(order.BillingAddress.CustomAttributes); + if (!String.IsNullOrEmpty(customBillingAddressAttributes)) + { + //TODO: we should add padding to each line (in case if we have sevaral custom address attributes) + paraPdf.Add(new Text(HtmlHelper.ConvertHtmlToPlainText(customBillingAddressAttributes, true, true))).Add("\n"); + } + + //vendors payment details from payment provider + if (vendorId == 0) + { + //custom values + var customValues = order.DeserializeCustomValues(); + if (customValues != null) + { + foreach (var item in customValues) + { + if (!String.IsNullOrEmpty(item.Value.ToString())) + paraPdf.Add(new Text(item.Key + ": " + item.Value)).Add("\n"); + } + } + } + + //add + tabPage.AddHeaderCell(new Cell(1, ivAddrCol).Add(paraPdf).AddStyle(styleCell)); + #endregion + + #endregion + + #region invoice header + //empty Line + tabPage.AddHeaderCell(new Cell(1, col).Add("\n").AddStyle(styleCell)); + //Invoice + paraPdf = new Paragraph(new Text(_localizationService.GetResource(order.InvoiceId != null? "PDFInvoice.Invoice" :"PDFInvoice.Order", lang.Id)).AddStyle(styleTitle)); + if (order.InvoiceId != null) + paraPdf.Add(new Text(" " + order.InvoiceId).AddStyle(styleTitle)); + tabPage.AddHeaderCell(new Cell(1, col - 3).Add(paraPdf).AddStyle(styleCell)); + paraPdf = new Paragraph(); + if (order.InvoiceDateUtc.HasValue) + { + paraPdf.Add(new Text(_localizationService.GetResource("PDFInvoice.InvoiceDate", lang.Id)).AddStyle(style9b)).SetTextAlignment(TextAlignment.RIGHT); + tabPage.AddHeaderCell(new Cell(1, 2).Add(paraPdf).AddStyle(styleCell)); + paraPdf = new Paragraph(new Text(_dateTimeHelper.ConvertToUserTime((System.DateTime)(order.InvoiceDateUtc), DateTimeKind.Utc).ToString("d", new CultureInfo(lang.LanguageCulture)) ?? "").AddStyle(style9b)).SetTextAlignment(TextAlignment.RIGHT); + tabPage.AddHeaderCell(new Cell(1, 1).Add(paraPdf).AddStyle(styleCell)); + } + else + tabPage.AddHeaderCell(new Cell(1, 3).Add(paraPdf).AddStyle(styleCell)); + + #region invoice titles + //compose titles + + //payment method + var paymentMethod = _paymentService.LoadPaymentMethodBySystemName(order.PaymentMethodSystemName); + string paymentMethodStr = paymentMethod != null ? paymentMethod.GetLocalizedFriendlyName(_localizationService, lang.Id) : order.PaymentMethodSystemName; + + //titles + string[,] tit = { { "PDFInvoice.Order#", order.Id.ToString() ?? ""} + , { "PDFInvoice.OrderDate", _dateTimeHelper.ConvertToUserTime(order.CreatedOnUtc, DateTimeKind.Utc).ToString("d", new CultureInfo(lang.LanguageCulture)) ?? ""} + , { "Order.ShippingMethod", order.ShippingMethod ?? ""} + , { "Order.PaymentMethod", paymentMethodStr ?? ""} + , { "Order.Payment.Status", order.PaymentStatus.GetLocalizedEnum(_localizationService, _workContext) ?? ""} + , { "Order.VatNumber", order.VatNumber ?? ""} + , { "PDFInvoice.Currency", order.CustomerCurrencyCode} + }; + + for (var i = 0; i <= tit.GetUpperBound(0); i++) + { + cellPdf = new Cell(); + paraPdf = new Paragraph(new Text(String.Format(_localizationService.GetResource(tit[i, 0], lang.Id))).AddStyle(style8)); + cellPdf.Add(paraPdf); + tabPage.AddHeaderCell(cellPdf.SetBorder(null) + .SetVerticalAlignment(VerticalAlignment.TOP) + .SetBackgroundColor(Color.LIGHT_GRAY) + .SetPadding(0) + .SetTextAlignment(TextAlignment.LEFT) + .SetBorderTop(new SolidBorder(1)) + ); + } + + for (var i = 0; i <= tit.GetUpperBound(0); i++) + { + cellPdf = new Cell(); + paraPdf = new Paragraph(new Text(tit[i, 1]).AddStyle(style9b)); + cellPdf.Add(paraPdf); + tabPage.AddHeaderCell(cellPdf.SetBorder(null) + .SetVerticalAlignment(VerticalAlignment.BOTTOM) + .SetTextAlignment(TextAlignment.LEFT) + .SetBorderBottom(new SolidBorder(0.5f)) + ); + } + + #endregion + + #endregion + + #region Products + + //products + var orderItems = order.OrderItems; + + var hasSku = _catalogSettings.ShowSkuOnProductDetailsPage; + + tabPage.AddHeaderCell(new Cell(1, hasSku ? 2 : 3).Add(_localizationService.GetResource("PDFInvoice.ProductName", lang.Id)).AddStyle(styleCell).AddStyle(style9b).SetBackgroundColor(Color.LIGHT_GRAY)); + if (_catalogSettings.ShowSkuOnProductDetailsPage) + { + tabPage.AddHeaderCell(new Cell(1, 1).Add(_localizationService.GetResource("PDFInvoice.SKU", lang.Id)).AddStyle(styleCell).AddStyle(style9b).SetBackgroundColor(Color.LIGHT_GRAY)); + } + tabPage.AddHeaderCell(new Cell(1, 1).Add(_localizationService.GetResource("PDFInvoice.VatRate", lang.Id)).AddStyle(styleCell).AddStyle(style9b).SetBackgroundColor(Color.LIGHT_GRAY).SetTextAlignment(TextAlignment.CENTER)); + tabPage.AddHeaderCell(new Cell(1, 1).Add(_priceFormatter.FormatTaxString(_localizationService.GetResource("PDFInvoice.ProductPrice", lang.Id), lang, includingTax)).AddStyle(styleCell).AddStyle(style9b).SetBackgroundColor(Color.LIGHT_GRAY).SetTextAlignment(TextAlignment.RIGHT)); + tabPage.AddHeaderCell(new Cell(1, 1).Add(_localizationService.GetResource("PDFInvoice.ProductQuantity", lang.Id)).AddStyle(styleCell).AddStyle(style9b).SetBackgroundColor(Color.LIGHT_GRAY).SetTextAlignment(TextAlignment.RIGHT)); + tabPage.AddHeaderCell(new Cell(1, 1).Add(_priceFormatter.FormatTaxString(_localizationService.GetResource("PDFInvoice.ProductTotal", lang.Id), lang, includingTax)).AddStyle(styleCell).AddStyle(style9b).SetBackgroundColor(Color.LIGHT_GRAY).SetTextAlignment(TextAlignment.RIGHT)); + int ic = 1; //init product numerator + + foreach (var orderItem in orderItems) + { + var p = orderItem.Product; + + //a vendor should have access only to his products + if (vendorId > 0 && p.VendorId != vendorId) + continue; + + //product name + string name; + name = ic.ToString() + ") " + p.GetLocalized(x => x.Name, lang.Id); + paraPdf = new Paragraph(new Text(name).AddStyle(style9b)).AddStyle(styleNormal).SetMultipliedLeading(1.5f).Add("\n"); + + //attributes + if (!String.IsNullOrEmpty(orderItem.AttributeDescription)) + { + paraPdf.Add(HtmlHelper.ConvertHtmlToPlainText(orderItem.AttributeDescription, true, true)).AddStyle(styleNormal).Add("\n"); + } + //rental info + if (orderItem.Product != null && orderItem.Product.IsRental) + { + var rentalStartDate = orderItem.RentalStartDateUtc.HasValue ? orderItem.Product.FormatRentalDate(orderItem.RentalStartDateUtc.Value) : ""; + var rentalEndDate = orderItem.RentalEndDateUtc.HasValue ? orderItem.Product.FormatRentalDate(orderItem.RentalEndDateUtc.Value) : ""; + var rentalInfo = string.Format(_localizationService.GetResource("Order.Rental.FormattedDate"), + rentalStartDate, rentalEndDate); + + paraPdf.Add(rentalInfo).AddStyle(styleNormal).Add("\n"); + } + + tabPage.AddCell(new Cell(1, hasSku ? 2 : 3).Add(paraPdf).AddStyle(styleCell).SetKeepTogether(true)); + + //SKU + if (_catalogSettings.ShowSkuOnProductDetailsPage) + { + var sku = p.FormatSku(orderItem.AttributesXml, _productAttributeParser); + paraPdf = new Paragraph(sku ?? String.Empty).AddStyle(styleNormal); + tabPage.AddCell(new Cell().Add(paraPdf).AddStyle(styleCell)); + } + //vatrate + paraPdf = new Paragraph(_priceFormatter.FormatTaxRate(orderItem.VatRate)).AddStyle(styleNormal).SetTextAlignment(TextAlignment.CENTER); + tabPage.AddCell(new Cell().Add(paraPdf).AddStyle(styleCell)); + + //price + string unitPrice; + var unitPriceInCustomerCurrency = _currencyService.ConvertCurrency(includingTax ? orderItem.UnitPriceInclTax : orderItem.UnitPriceExclTax, order.CurrencyRate); + unitPrice = _priceFormatter.FormatPrice(unitPriceInCustomerCurrency, showCurrency, currency, lang, true, false); + + paraPdf = new Paragraph(unitPrice).AddStyle(styleNormal).SetTextAlignment(TextAlignment.RIGHT); + tabPage.AddCell(new Cell().Add(paraPdf).AddStyle(styleCell)); + + //qty + paraPdf = new Paragraph(orderItem.Quantity.ToString()).AddStyle(styleNormal).SetTextAlignment(TextAlignment.RIGHT); + tabPage.AddCell(new Cell().Add(paraPdf).AddStyle(styleCell)); + + //total + string subTotal; + var priceInCustomerCurrency = _currencyService.ConvertCurrency(includingTax ? orderItem.PriceInclTax : orderItem.PriceExclTax, order.CurrencyRate); + subTotal = _priceFormatter.FormatPrice(priceInCustomerCurrency, showCurrency, currency, lang, true, false); + + paraPdf = new Paragraph(subTotal).AddStyle(styleNormal).SetTextAlignment(TextAlignment.RIGHT); + tabPage.AddCell(new Cell().Add(paraPdf).AddStyle(styleCell)); + + //increase product numerator + ++ic; + + } + + #endregion + + #region checkout attributes + //checkout attributes + var attributeValues = _checkoutAttributeParser.ParseCheckoutAttributeValues(order.CheckoutAttributesXml); + if (attributeValues != null && attributeValues.Any()) + { + string name; + name = _localizationService.GetResource("PDFInvoice.CheckOutAttrib", lang.Id); + paraPdf = new Paragraph(new Text(name).AddStyle(styleTitle)).AddStyle(styleNormal).SetMultipliedLeading(1.5f); + tabPage.AddCell(new Cell(1, col).Add(paraPdf).AddStyle(styleCell)); + + foreach (var attributeValue in attributeValues) + { + decimal taxRate = 0; + var customer = order.Customer; + decimal itemAmount = _taxService.GetCheckoutAttributePrice(attributeValue, includingTax, customer, out taxRate); + + //a vendor should have access only to his products + if (vendorId > 0) + continue; + + //product name + paraPdf = new Paragraph(HtmlHelper.ConvertHtmlToPlainText(attributeValue.Name, true, true)).AddStyle(styleNormal).Add("\n"); + tabPage.AddCell(new Cell(1, hasSku ? 2 : 3).Add(paraPdf).AddStyle(styleCell)); + + //SKU + if (_catalogSettings.ShowSkuOnProductDetailsPage) + { + paraPdf = new Paragraph("").AddStyle(styleNormal); + tabPage.AddCell(new Cell().Add(paraPdf).AddStyle(styleCell)); + } + //vatrate + paraPdf = new Paragraph(_priceFormatter.FormatTaxRate(taxRate)).AddStyle(styleNormal).SetTextAlignment(TextAlignment.CENTER); + tabPage.AddCell(new Cell().Add(paraPdf).AddStyle(styleCell)); + + //price + string unitPrice; + var unitPriceInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(itemAmount, order.CurrencyRate); + unitPrice = _priceFormatter.FormatPrice(unitPriceInclTaxInCustomerCurrency, showCurrency, currency, lang, true, false); + + paraPdf = new Paragraph(unitPrice).AddStyle(styleNormal).SetTextAlignment(TextAlignment.RIGHT); + tabPage.AddCell(new Cell().Add(paraPdf).AddStyle(styleCell)); + + //qty + paraPdf = new Paragraph("1").AddStyle(styleNormal).SetTextAlignment(TextAlignment.RIGHT); + tabPage.AddCell(new Cell().Add(paraPdf).AddStyle(styleCell)); + + //total + string subTotal = unitPrice; + paraPdf = new Paragraph(subTotal).AddStyle(styleNormal).SetTextAlignment(TextAlignment.RIGHT); + tabPage.AddCell(new Cell().Add(paraPdf).AddStyle(styleCell)); + + } + } + #endregion + + //footer products, checkout table + paraPdf = new Paragraph(_localizationService.GetResource("PDFInvoice.Continue", lang.Id)).AddStyle(styleNormal).SetTextAlignment(TextAlignment.RIGHT); + tabPage.AddFooterCell(new Cell(1, col).Add(paraPdf).AddStyle(styleCell)).SetSkipLastFooter(true); + + tabPage.AddCell(new Cell(1, col).Add(" ").AddStyle(styleCell).SetBorderTop(new SolidBorder(0.1f))); + + #region Order notes first. not used atm + //var notesCell = 4; + //int totNotes = 0; + //if (pdfSettingsByStore.RenderOrderNotes) + //{ + // var orderNotes = order.OrderNotes + // .Where(on => on.DisplayToCustomer) + // .OrderByDescending(on => on.CreatedOnUtc) + // .ToList(); + + // if (orderNotes.Any()) + // { + // totNotes = orderNotes.Count(); + // int notesCol = 4; + // var tabNotes = new Table(notesCol).SetBorder(Border.NO_BORDER).SetWidthPercent(100f); + // paraPdf = new Paragraph(new Text(_localizationService.GetResource("PDFInvoice.OrderNotes", lang.Id)).AddStyle(styleTitle)).AddStyle(styleNormal).SetMultipliedLeading(1.5f); + // tabNotes.AddHeaderCell(new Cell(1, notesCol).Add(paraPdf).AddStyle(styleCell)); + + // //created on + // tabNotes.AddHeaderCell(new Cell(1, 1).Add(_localizationService.GetResource("PDFInvoice.OrderNotes.CreatedOn", lang.Id)).AddStyle(styleCell).AddStyle(style9b).SetTextAlignment(TextAlignment.LEFT)); + + // //note + // tabNotes.AddHeaderCell(new Cell(1, notesCol - 1).Add(_localizationService.GetResource("PDFInvoice.OrderNotes.Note", lang.Id)).AddStyle(styleCell).AddStyle(style9b).SetTextAlignment(TextAlignment.LEFT)); + // var orderNote = orderNotes.FirstOrDefault(); + + // paraPdf = new Paragraph(_dateTimeHelper.ConvertToUserTime(orderNote.CreatedOnUtc, DateTimeKind.Utc).ToString("g", new CultureInfo(lang.LanguageCulture))).AddStyle(styleNormal).SetTextAlignment(TextAlignment.LEFT); + // tabNotes.AddCell(new Cell(1, 1).Add(paraPdf).AddStyle(styleCell).SetKeepTogether(true)); + // var strNotes = TakeCountLines(HtmlHelper.ConvertHtmlToPlainText(orderNote.FormatOrderNoteText(), true, true), 20); + + // paraPdf = new Paragraph(strNotes).AddStyle(styleNormal).SetTextAlignment(TextAlignment.LEFT); + // tabNotes.AddCell(new Cell(1, notesCol - 1).Add(paraPdf).AddStyle(styleCell).SetKeepTogether(true)); + + // //should we display a link to downloadable files here? + // //I think, no. Onyway, PDFs are printable documents and links (files) are useful here + + // tabPage.AddCell(new Cell(1, notesCell).Add(tabNotes).AddStyle(styleCell).SetPadding(0)); + // } + //} + + #endregion + + #region Order totals + var taxRates = order.TaxRatesDictionary; + + var sumCell = col; // - notesCell; + int subcol = col; // totNotes > 0 ? sumCell : col; + var tabTot = new Table(subcol).SetBorder(Border.NO_BORDER).SetWidthPercent(100f); + + //vendors cannot see totals + if (vendorId == 0) + { + var lstSummary = new List> //desc, amount, show when zero, doLocalize, borderTop, borderBottom + { + Tuple.Create("PDFInvoice.Sub-Total", includingTax ? order.OrderSubtotalInclTax : order.OrderSubtotalExclTax, true, true, false, false) //order subtotal + ,Tuple.Create("PDFInvoice.Discount", includingTax ? -order.OrderSubTotalDiscountInclTax : -order.OrderSubTotalDiscountExclTax, false, true, false, false) //discount (applied to order subtotal) + ,Tuple.Create("PDFInvoice.Shipping", includingTax ? order.OrderShippingInclTax : order.OrderShippingExclTax, false, true, order.ShippingStatus != ShippingStatus.ShippingNotRequired, false) //shipping + ,Tuple.Create("PDFInvoice.PaymentMethodAdditionalFee", includingTax ? order.PaymentMethodAdditionalFeeInclTax : order.PaymentMethodAdditionalFeeExclTax, false, true, false, false) //payment fee + ,Tuple.Create("PDFInvoice.InvoiceDiscount", -order.OrderDiscount, false, true, false, false) //discount (applied to order total) + }; + + if (!includingTax) + { + lstSummary.Add(new Tuple("PDFInvoice.OrderAmount", order.OrderAmount, true, true, false, false)); //tax base + lstSummary.Add(new Tuple("PDFInvoice.Tax", order.OrderTax, true, true, false, false)); //tax amount + } + //order total incl. + lstSummary.Add(new Tuple("PDFInvoice.OrderAmountIncl", order.OrderAmountIncl, true, true, false, false)); + + //gift cards + foreach (var gcuh in order.GiftCardUsageHistory) + { + lstSummary.Add(new Tuple( + string.Format(_localizationService.GetResource("PDFInvoice.GiftCardInfo", lang.Id), gcuh.GiftCard.GiftCardCouponCode), -gcuh.UsedValue, false, false, false, false + ) + ); + } + + //reward points + if (order.RedeemedRewardPointsEntry != null) + { + lstSummary.Add(new Tuple( + string.Format(_localizationService.GetResource("PDFInvoice.RewardPoints", lang.Id), -order.RedeemedRewardPointsEntry.Points), -order.RedeemedRewardPointsEntry.UsedAmount, false, false, false, false + ) + ); + } + + //order total to pay + lstSummary.Add(new Tuple("PDFInvoice.AmountToPay", order.OrderTotal, true, true, true, false)); + + foreach (var tupSummary in lstSummary) + { + var desc = tupSummary.Item1; + var amount = tupSummary.Item2; + var showZero = tupSummary.Item3; + var doLocalize = tupSummary.Item4; + var borderTop = tupSummary.Item5; + var borderBottom = tupSummary.Item6; + + var amountInCustomerCurrency = _currencyService.ConvertCurrency(amount, order.CurrencyRate); + var amountInCustomerCurrencyStr = _priceFormatter.FormatPrice(amountInCustomerCurrency, showCurrency, currency, lang, includingTax, false); + + if (amountInCustomerCurrency != decimal.Zero || showZero) + { + paraPdf = new Paragraph(doLocalize ? _localizationService.GetResource(desc, lang.Id) : desc).AddStyle(style9b).SetTextAlignment(TextAlignment.RIGHT); + cellPdf = new Cell(1, subcol - 1).Add(paraPdf).AddStyle(styleCell); + paraPdf = new Paragraph(amountInCustomerCurrencyStr).AddStyle(style9b).SetTextAlignment(TextAlignment.RIGHT); + cellPdf2 = new Cell(1, 1).Add(paraPdf).AddStyle(styleCell); + if (borderTop) + { + cellPdf.SetBorderTop(new SolidBorder(0.5f)); cellPdf2.SetBorderTop(new SolidBorder(0.5f)); + } + if (borderBottom) + { + cellPdf.SetBorderBottom(new SolidBorder(0.5f)); cellPdf2.SetBorderBottom(new SolidBorder(0.5f)); + } + tabTot.AddCell(cellPdf); + tabTot.AddCell(cellPdf2); + } + } + + + } + else + tabTot.AddCell(""); + + tabPage.AddCell(new Cell(1, col) //totNotes > 0 ? sumCell : col) + .Add(tabTot).AddStyle(styleCell).SetPadding(0).SetKeepTogether(true)); + #endregion + + #region Order notes + + if (pdfSettingsByStore.RenderOrderNotes ) //&& totNotes > 1) + { + var orderNotes = order.OrderNotes + .Where(on => on.DisplayToCustomer) + .OrderByDescending(on => on.CreatedOnUtc) + .ToList(); + + + if (orderNotes.Any()) + { + paraPdf = new Paragraph(new Text(_localizationService.GetResource("PDFInvoice.OrderNotes", lang.Id)).AddStyle(styleTitle)).AddStyle(styleNormal).SetMultipliedLeading(1.5f); + tabPage.AddCell(new Cell(1, col).Add(paraPdf).AddStyle(styleCell)); + + //created on + tabPage.AddCell(new Cell(1, 1).Add(_localizationService.GetResource("PDFInvoice.OrderNotes.CreatedOn", lang.Id)).AddStyle(styleCell).AddStyle(style9b).SetTextAlignment(TextAlignment.LEFT)); + + //note + tabPage.AddCell(new Cell(1, col - 1).Add(_localizationService.GetResource("PDFInvoice.OrderNotes.Note", lang.Id)).AddStyle(styleCell).AddStyle(style9b).SetTextAlignment(TextAlignment.LEFT)); + + foreach (var orderNote in orderNotes)//.Skip(1)) + { + paraPdf = new Paragraph(_dateTimeHelper.ConvertToUserTime(orderNote.CreatedOnUtc, DateTimeKind.Utc).ToString("g", new CultureInfo(lang.LanguageCulture))).AddStyle(styleNormal).SetTextAlignment(TextAlignment.LEFT); + tabPage.AddCell(new Cell(1, 1).Add(paraPdf).AddStyle(styleCell)); + var strNotes = TakeCountLines(HtmlHelper.ConvertHtmlToPlainText(orderNote.FormatOrderNoteText(), true, true), 20); + + paraPdf = new Paragraph(strNotes).AddStyle(styleNormal).SetTextAlignment(TextAlignment.LEFT); + tabPage.AddCell(new Cell(1, col - 1).Add(paraPdf).AddStyle(styleCell)); + } + + } + } + + #endregion + + #region tax summary + var footerTable = new Table(col).SetBorder(Border.NO_BORDER).SetWidthPercent(100f); + var displayTaxRates = _taxSettings.DisplayTaxRates; + if (displayTaxRates) + { + var taxTable = new Table(new float[] { 20, 20, 20, 20, 20 }).SetBorder(Border.NO_BORDER).SetWidthPercent(100f).AddStyle(styleNormal).SetHorizontalAlignment(HorizontalAlignment.LEFT); + + //header + taxTable.AddCell(new Cell().Add(_localizationService.GetResource("PDFInvoice.VatRate", lang.Id)).AddStyle(styleCell).AddStyle(style8).SetBackgroundColor(Color.LIGHT_GRAY).SetTextAlignment(TextAlignment.CENTER)); + taxTable.AddCell(new Cell().Add(_localizationService.GetResource(includingTax ? "PDFInvoice.OrderAmountIncl" : "PDFInvoice.OrderAmount", lang.Id)).AddStyle(styleCell).AddStyle(style8).SetBackgroundColor(Color.LIGHT_GRAY).SetTextAlignment(TextAlignment.CENTER)); + taxTable.AddCell(new Cell().Add(_localizationService.GetResource(includingTax ? "PDFInvoice.DiscountAmountIncl" : "PDFInvoice.DiscountAmount", lang.Id)).AddStyle(styleCell).AddStyle(style8).SetBackgroundColor(Color.LIGHT_GRAY).SetTextAlignment(TextAlignment.CENTER)); + taxTable.AddCell(new Cell().Add(_localizationService.GetResource("PDFInvoice.BaseAmount", lang.Id)).AddStyle(styleCell).AddStyle(style8).SetBackgroundColor(Color.LIGHT_GRAY).SetTextAlignment(TextAlignment.CENTER)); + taxTable.AddCell(new Cell().Add(_localizationService.GetResource("PDFInvoice.VatAmount", lang.Id)).AddStyle(styleCell).AddStyle(style8).SetBackgroundColor(Color.LIGHT_GRAY).SetTextAlignment(TextAlignment.CENTER)); + + + foreach (var item in taxRates) + { + string taxRate = String.Format(_priceFormatter.FormatTaxRate(item.Key)); + string Amount = _priceFormatter.FormatPrice(_currencyService.ConvertCurrency(item.Value.Amount, order.CurrencyRate), showCurrency, order.CustomerCurrencyCode, false, lang); + string DiscountAmount = _priceFormatter.FormatPrice(_currencyService.ConvertCurrency(item.Value.DiscountAmount, order.CurrencyRate), showCurrency, order.CustomerCurrencyCode, false, lang); + string BaseAmount = _priceFormatter.FormatPrice(_currencyService.ConvertCurrency(item.Value.BaseAmount, order.CurrencyRate), showCurrency, order.CustomerCurrencyCode, false, lang); + string VatAmount = _priceFormatter.FormatPrice(_currencyService.ConvertCurrency(item.Value.VatAmount, order.CurrencyRate), showCurrency, order.CustomerCurrencyCode, false, lang); + //string AmountIncludingVAT = _priceFormatter.FormatPrice(_currencyService.ConvertCurrency(item.Value.AmountIncludingVAT, order.CurrencyRate), true, order.CustomerCurrencyCode, false, lang); + + taxTable.AddCell(new Cell().Add(taxRate).AddStyle(styleCell).AddStyle(style8b).SetTextAlignment(TextAlignment.CENTER).SetBackgroundColor(Color.WHITE)); + taxTable.AddCell(new Cell().Add(Amount).AddStyle(styleCell).AddStyle(style8b).SetTextAlignment(TextAlignment.CENTER).SetBackgroundColor(Color.WHITE)); + taxTable.AddCell(new Cell().Add(DiscountAmount).AddStyle(styleCell).AddStyle(style8b).SetTextAlignment(TextAlignment.CENTER).SetBackgroundColor(Color.WHITE)); + taxTable.AddCell(new Cell().Add(BaseAmount).AddStyle(styleCell).AddStyle(style8b).SetTextAlignment(TextAlignment.CENTER).SetBackgroundColor(Color.WHITE)); + taxTable.AddCell(new Cell().Add(VatAmount).AddStyle(styleCell).AddStyle(style8b).SetTextAlignment(TextAlignment.CENTER).SetBackgroundColor(Color.WHITE)); + } + + footerTable.AddCell(new Cell(1, 5).Add(taxTable).AddStyle(styleCell).SetPadding(0).SetBorderTop(new SolidBorder(0.1f)).SetBorderBottom(new SolidBorder(0.1f))); + + var taxAmountTable = new Table(2).SetBorder(Border.NO_BORDER).SetWidthPercent(100f).AddStyle(styleNormal).SetHorizontalAlignment(HorizontalAlignment.LEFT); + + //base amount head + paraPdf = new Paragraph(_localizationService.GetResource("PDFInvoice.BaseAmountTotal", lang.Id)).AddStyle(style8).SetTextAlignment(TextAlignment.CENTER); + taxAmountTable.AddCell(new Cell(1, 1).Add(paraPdf).AddStyle(styleCell).SetBackgroundColor(Color.LIGHT_GRAY)); + + //total pay header + paraPdf = new Paragraph(_localizationService.GetResource("PDFInvoice.AmountToPay", lang.Id)).AddStyle(style8).SetTextAlignment(TextAlignment.CENTER); + taxAmountTable.AddCell(new Cell(2, 1).Add(paraPdf).AddStyle(styleCell).SetBackgroundColor(Color.LIGHT_GRAY).SetVerticalAlignment(VerticalAlignment.TOP)); + + //base amount + var amountInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderAmount, order.CurrencyRate); + string amountInCustomerCurrencyStr = _priceFormatter.FormatPrice(amountInCustomerCurrency, showCurrency, currency, lang, false, false); + paraPdf = new Paragraph(amountInCustomerCurrencyStr).AddStyle(style9b).SetTextAlignment(TextAlignment.CENTER); + taxAmountTable.AddCell(new Cell(1, 1).Add(paraPdf).AddStyle(styleCell).SetBackgroundColor(Color.WHITE)); + + //vat amount head + paraPdf = new Paragraph(_localizationService.GetResource("PDFInvoice.VatAmount", lang.Id)).AddStyle(style8).SetTextAlignment(TextAlignment.CENTER); + taxAmountTable.AddCell(new Cell(1, 1).Add(paraPdf).AddStyle(styleCell).SetBackgroundColor(Color.LIGHT_GRAY)); + + //total pay amount + amountInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderTotal, order.CurrencyRate); + amountInCustomerCurrencyStr = _priceFormatter.FormatPrice(amountInCustomerCurrency, showCurrency, currency, lang, false, false); + paraPdf = new Paragraph(amountInCustomerCurrencyStr).AddStyle(style9b).SetTextAlignment(TextAlignment.CENTER); + taxAmountTable.AddCell(new Cell(2, 1).Add(paraPdf).AddStyle(styleCell).SetVerticalAlignment(VerticalAlignment.MIDDLE).SetBackgroundColor(Color.WHITE)); + + //vat amount + amountInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderTax, order.CurrencyRate); + amountInCustomerCurrencyStr = _priceFormatter.FormatPrice(amountInCustomerCurrency, showCurrency, currency, lang, false, false); + paraPdf = new Paragraph(amountInCustomerCurrencyStr).AddStyle(style9b).SetTextAlignment(TextAlignment.CENTER); + taxAmountTable.AddCell(new Cell(1, 1).Add(paraPdf).AddStyle(styleCell).SetBackgroundColor(Color.WHITE)); + + footerTable.AddCell(new Cell(1, 2).Add(taxAmountTable).AddStyle(styleCell).SetPadding(0).SetBorderTop(new SolidBorder(0.1f)).SetBorderBottom(new SolidBorder(0.1f)).SetKeepTogether(true)); + + //place footer in tabPage cell and position it using setfixedpos + tabPage.AddCell(new Cell(1, col).Add(footerTable).AddStyle(styleCell).SetPadding(0).SetKeepTogether(true).SetFixedPosition(footerX, footerY, footerWidht)); + + } + #endregion + + + doc.Add(tabPage); + + //tmp margin marker + //var pdfcan = new PdfCanvas(pdfDoc.GetLastPage()); + //pdfcan.MoveTo(0, bottomMatgin).LineTo(100, bottomMatgin).Stroke(); + + + //doc.Relayout(); + + //finalize page + pdfDoc.RemoveEventHandler(PdfDocumentEvent.INSERT_PAGE, pageEvent); + pageEvent.writeTotPageNum(pdfDoc); + pageEvent = null; + pagesSofar = pdfDoc.GetNumberOfPages(); + + + ordNum++; + + } + doc.Close(); + } + + + + + + /// + /// Print packaging slips to PDF + /// + /// Stream + /// Shipments + /// Language identifier; 0 to use a language used when placing an order + public virtual void PrintPackagingSlipsToPdf(Stream stream, IList shipments, int languageId = 0) + { + var _pdfService = new PdfService(_localizationService, _languageService, _workContext, _orderService, _paymentService, _dateTimeHelper, _priceFormatter, _currencyService, _measureService, _pictureService, _productService, _productAttributeParser, _storeService, _storeContext, _settingContext, _addressAttributeFormatter, _catalogSettings, _currencySettings, _measureSettings, _pdfSettings, _taxSettings, _addressSettings); + _pdfService.PrintPackagingSlipsToPdf(stream, shipments, languageId); + + } + + /// + /// Print products to PDF + /// + /// Stream + /// Products + public virtual void PrintProductsToPdf(Stream stream, IList products) + { + var _pdfService = new PdfService(_localizationService, _languageService, _workContext, _orderService, _paymentService, _dateTimeHelper, _priceFormatter, _currencyService, _measureService, _pictureService, _productService, _productAttributeParser, _storeService, _storeContext, _settingContext, _addressAttributeFormatter, _catalogSettings, _currencySettings, _measureSettings, _pdfSettings, _taxSettings, _addressSettings); + _pdfService.PrintProductsToPdf(stream, products); + + } + #endregion + } + + +} \ No newline at end of file diff --git a/src/Libraries/Nop.Services/ExportImport/ExportManager.cs b/src/Libraries/Nop.Services/ExportImport/ExportManager.cs index 1e0400059f8..0196b4f1c88 100644 --- a/src/Libraries/Nop.Services/ExportImport/ExportManager.cs +++ b/src/Libraries/Nop.Services/ExportImport/ExportManager.cs @@ -291,7 +291,7 @@ protected virtual byte[] ExportToXlsx(PropertyByName[] properties, IEnumer using (var xlPackage = new ExcelPackage(stream)) { // uncomment this line if you want the XML written out to the outputDir - //xlPackage.DebugMode = true; + //xlPackage.DebugMode = true; // get handles to the worksheets var worksheet = xlPackage.Workbook.Worksheets.Add(typeof(T).Name); @@ -355,7 +355,7 @@ private byte[] ExportProductsToXlsxWithAttributes(PropertyByName[] prop using (var xlPackage = new ExcelPackage(stream)) { // uncomment this line if you want the XML written out to the outputDir - //xlPackage.DebugMode = true; + //xlPackage.DebugMode = true; // get handles to the worksheets var worksheet = xlPackage.Workbook.Worksheets.Add(typeof(Product).Name); @@ -1178,6 +1178,9 @@ public virtual string ExportOrdersToXml(IList orders) xmlWriter.WriteString("TaxRates", order.TaxRates, ignore); xmlWriter.WriteString("OrderTax", order.OrderTax, ignore); xmlWriter.WriteString("OrderTotal", order.OrderTotal, ignore); + xmlWriter.WriteString("OrderAmount", order.OrderAmount, ignore); + xmlWriter.WriteString("OrderAmountIncl", order.OrderAmountIncl, ignore); + xmlWriter.WriteString("RefundedAmount", order.RefundedAmount, ignore); xmlWriter.WriteString("OrderDiscount", order.OrderDiscount, ignore); xmlWriter.WriteString("CurrencyRate", order.CurrencyRate); @@ -1232,6 +1235,7 @@ public virtual string ExportOrdersToXml(IList orders) xmlWriter.WriteString("DiscountInclTax", orderItem.DiscountAmountInclTax); xmlWriter.WriteString("TotalExclTax", orderItem.PriceExclTax); xmlWriter.WriteString("TotalInclTax", orderItem.PriceInclTax); + xmlWriter.WriteString("VatRate", orderItem.VatRate); xmlWriter.WriteEndElement(); } xmlWriter.WriteEndElement(); @@ -1250,9 +1254,9 @@ public virtual string ExportOrdersToXml(IList orders) xmlWriter.WriteElementString("TrackingNumber", null, shipment.TrackingNumber); xmlWriter.WriteElementString("TotalWeight", null, shipment.TotalWeight.HasValue ? shipment.TotalWeight.Value.ToString() : String.Empty); - xmlWriter.WriteElementString("ShippedDateUtc", null, shipment.ShippedDateUtc.HasValue ? + xmlWriter.WriteElementString("ShippedDateUtc", null, shipment.ShippedDateUtc.HasValue ? shipment.ShippedDateUtc.ToString() : String.Empty); - xmlWriter.WriteElementString("DeliveryDateUtc", null, shipment.DeliveryDateUtc.HasValue ? + xmlWriter.WriteElementString("DeliveryDateUtc", null, shipment.DeliveryDateUtc.HasValue ? shipment.DeliveryDateUtc.Value.ToString() : String.Empty); xmlWriter.WriteElementString("CreatedOnUtc", null, shipment.CreatedOnUtc.ToString()); xmlWriter.WriteEndElement(); @@ -1298,6 +1302,8 @@ public virtual byte[] ExportOrdersToXlsx(IList orders) new PropertyByName("TaxRates", p => p.TaxRates, ignore), new PropertyByName("OrderTax", p => p.OrderTax, ignore), new PropertyByName("OrderTotal", p => p.OrderTotal, ignore), + new PropertyByName("OrderAmount", p => p.OrderAmount, ignore), + new PropertyByName("OrderAmountIncl", p => p.OrderAmountIncl, ignore), new PropertyByName("RefundedAmount", p => p.RefundedAmount, ignore), new PropertyByName("OrderDiscount", p => p.OrderDiscount, ignore), new PropertyByName("CurrencyRate", p => p.CurrencyRate), diff --git a/src/Libraries/Nop.Services/Installation/CodeFirstInstallationService.cs b/src/Libraries/Nop.Services/Installation/CodeFirstInstallationService.cs index 72d17029e86..986420f155f 100644 --- a/src/Libraries/Nop.Services/Installation/CodeFirstInstallationService.cs +++ b/src/Libraries/Nop.Services/Installation/CodeFirstInstallationService.cs @@ -4450,6 +4450,8 @@ protected virtual void InstallOrders() TaxRates = "0:0;", OrderTax = decimal.Zero, OrderTotal = 1855M, + OrderAmount = 1855M, + OrderAmountIncl = 1855M, RefundedAmount = decimal.Zero, OrderDiscount = decimal.Zero, CheckoutAttributeDescription = string.Empty, @@ -4511,7 +4513,8 @@ protected virtual void InstallOrders() LicenseDownloadId = 0, ItemWeight = null, RentalStartDateUtc = null, - RentalEndDateUtc = null + RentalEndDateUtc = null, + VatRate = 0 }; _orderItemRepository.Insert(firstOrderItem1); @@ -4536,7 +4539,8 @@ protected virtual void InstallOrders() LicenseDownloadId = 0, ItemWeight = null, RentalStartDateUtc = null, - RentalEndDateUtc = null + RentalEndDateUtc = null, + VatRate = 0 }; _orderItemRepository.Insert(fierstOrderItem2); @@ -4561,7 +4565,8 @@ protected virtual void InstallOrders() LicenseDownloadId = 0, ItemWeight = null, RentalStartDateUtc = null, - RentalEndDateUtc = null + RentalEndDateUtc = null, + VatRate = 0 }; _orderItemRepository.Insert(firstOrderItem3); @@ -4617,6 +4622,8 @@ protected virtual void InstallOrders() TaxRates = "0:0;", OrderTax = decimal.Zero, OrderTotal = 2460M, + OrderAmount = 2460M, + OrderAmountIncl = 2460M, RefundedAmount = decimal.Zero, OrderDiscount = decimal.Zero, CheckoutAttributeDescription = string.Empty, @@ -4686,7 +4693,8 @@ protected virtual void InstallOrders() LicenseDownloadId = 0, ItemWeight = null, RentalStartDateUtc = null, - RentalEndDateUtc = null + RentalEndDateUtc = null, + VatRate = 0 }; _orderItemRepository.Insert(secondOrderItem1); @@ -4711,7 +4719,8 @@ protected virtual void InstallOrders() LicenseDownloadId = 0, ItemWeight = null, RentalStartDateUtc = null, - RentalEndDateUtc = null + RentalEndDateUtc = null, + VatRate = 0 }; _orderItemRepository.Insert(secondOrderItem2); @@ -4736,6 +4745,8 @@ protected virtual void InstallOrders() TaxRates = "0:0;", OrderTax = decimal.Zero, OrderTotal = 8.80M, + OrderAmount = 8.80M, + OrderAmountIncl = 8.80M, RefundedAmount = decimal.Zero, OrderDiscount = decimal.Zero, CheckoutAttributeDescription = string.Empty, @@ -4805,7 +4816,8 @@ protected virtual void InstallOrders() LicenseDownloadId = 0, ItemWeight = null, RentalStartDateUtc = null, - RentalEndDateUtc = null + RentalEndDateUtc = null, + VatRate = 0 }; _orderItemRepository.Insert(thirdOrderItem1); @@ -4830,7 +4842,8 @@ protected virtual void InstallOrders() LicenseDownloadId = 0, ItemWeight = null, RentalStartDateUtc = null, - RentalEndDateUtc = null + RentalEndDateUtc = null, + VatRate = 0 }; _orderItemRepository.Insert(thirdOrderItem2); @@ -4855,7 +4868,8 @@ protected virtual void InstallOrders() LicenseDownloadId = 0, ItemWeight = null, RentalStartDateUtc = null, - RentalEndDateUtc = null + RentalEndDateUtc = null, + VatRate = 0 }; _orderItemRepository.Insert(thirdOrderItem3); @@ -4880,6 +4894,8 @@ protected virtual void InstallOrders() TaxRates = "0:0;", OrderTax = decimal.Zero, OrderTotal = 102M, + OrderAmount = 102M, + OrderAmountIncl = 102M, RefundedAmount = decimal.Zero, OrderDiscount = decimal.Zero, CheckoutAttributeDescription = string.Empty, @@ -4962,7 +4978,8 @@ protected virtual void InstallOrders() LicenseDownloadId = 0, ItemWeight = null, RentalStartDateUtc = null, - RentalEndDateUtc = null + RentalEndDateUtc = null, + VatRate = 0 }; _orderItemRepository.Insert(fourthOrderItem1); @@ -4987,7 +5004,8 @@ protected virtual void InstallOrders() LicenseDownloadId = 0, ItemWeight = null, RentalStartDateUtc = null, - RentalEndDateUtc = null + RentalEndDateUtc = null, + VatRate = 0 }; _orderItemRepository.Insert(fourthOrderItem2); @@ -5012,7 +5030,8 @@ protected virtual void InstallOrders() LicenseDownloadId = 0, ItemWeight = null, RentalStartDateUtc = null, - RentalEndDateUtc = null + RentalEndDateUtc = null, + VatRate = 0 }; _orderItemRepository.Insert(fourthOrderItem3); @@ -5093,6 +5112,8 @@ protected virtual void InstallOrders() TaxRates = "0:0;", OrderTax = decimal.Zero, OrderTotal = 43.50M, + OrderAmount = 43.50M, + OrderAmountIncl = 43.50M, RefundedAmount = decimal.Zero, OrderDiscount = decimal.Zero, CheckoutAttributeDescription = string.Empty, @@ -5180,7 +5201,8 @@ protected virtual void InstallOrders() LicenseDownloadId = 0, ItemWeight = null, RentalStartDateUtc = null, - RentalEndDateUtc = null + RentalEndDateUtc = null, + VatRate = 0 }; _orderItemRepository.Insert(fifthOrderItem1); diff --git a/src/Libraries/Nop.Services/Messages/MessageTokenProvider.cs b/src/Libraries/Nop.Services/Messages/MessageTokenProvider.cs index 876a32dddbe..bb893b4f3a7 100644 --- a/src/Libraries/Nop.Services/Messages/MessageTokenProvider.cs +++ b/src/Libraries/Nop.Services/Messages/MessageTokenProvider.cs @@ -411,7 +411,7 @@ protected Dictionary> AllowedTokens protected virtual string ProductListToHtmlTable(Order order, int languageId, int vendorId) { string result; - + var includingTax = order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax; var language = _languageService.GetLanguageById(languageId); var sb = new StringBuilder(); @@ -488,7 +488,7 @@ protected virtual string ProductListToHtmlTable(Order order, int languageId, int sb.AppendLine(""); string unitPriceStr; - if (order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax) + if (includingTax) { //including tax var unitPriceInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(orderItem.UnitPriceInclTax, order.CurrencyRate); @@ -504,8 +504,8 @@ protected virtual string ProductListToHtmlTable(Order order, int languageId, int sb.AppendLine(string.Format("{0}", orderItem.Quantity)); - string priceStr; - if (order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax) + string priceStr; + if (includingTax) { //including tax var priceInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(orderItem.PriceInclTax, order.CurrencyRate); @@ -544,7 +544,7 @@ protected virtual string ProductListToHtmlTable(Order order, int languageId, int string cusSubTotal; bool displaySubTotalDiscount = false; string cusSubTotalDiscount = string.Empty; - if (order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax && !_taxSettings.ForceTaxExclusionFromOrderSubtotal) + if (includingTax && !_taxSettings.ForceTaxExclusionFromOrderSubtotal) { //including tax @@ -578,11 +578,13 @@ protected virtual string ProductListToHtmlTable(Order order, int languageId, int //shipping, payment method fee string cusShipTotal; string cusPaymentMethodAdditionalFee; - var taxRates = new SortedDictionary(); + var taxRates = new SortedDictionary(); string cusTaxTotal = string.Empty; string cusDiscount = string.Empty; - string cusTotal; - if (order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax) + string cusTotal; + string cusAmount; + string cusAmountIncl; + if (includingTax) { //including tax @@ -614,7 +616,7 @@ protected virtual string ProductListToHtmlTable(Order order, int languageId, int //tax bool displayTax = true; bool displayTaxRates = true; - if (_taxSettings.HideTaxInOrderSummary && order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax) + if (_taxSettings.HideTaxInOrderSummary && includingTax) { displayTax = false; displayTaxRates = false; @@ -628,9 +630,18 @@ protected virtual string ProductListToHtmlTable(Order order, int languageId, int } else { - taxRates = new SortedDictionary(); + taxRates = new SortedDictionary(); foreach (var tr in order.TaxRatesDictionary) - taxRates.Add(tr.Key, _currencyService.ConvertCurrency(tr.Value, order.CurrencyRate)); + //taxRates.Add(tr.Key, _currencyService.ConvertCurrency(tr.Value, order.CurrencyRate)); + taxRates.Add(tr.Key, new TaxRateRec() + { + VatRate = tr.Key, + Amount = _currencyService.ConvertCurrency(tr.Value.Amount, order.CurrencyRate), + DiscountAmount = _currencyService.ConvertCurrency(tr.Value.DiscountAmount, order.CurrencyRate), + BaseAmount = _currencyService.ConvertCurrency(tr.Value.BaseAmount, order.CurrencyRate), + VatAmount = _currencyService.ConvertCurrency(tr.Value.VatAmount, order.CurrencyRate), + AmountIncludingVAT = _currencyService.ConvertCurrency(tr.Value.AmountIncludingVAT, order.CurrencyRate) + }); displayTaxRates = _taxSettings.DisplayTaxRates && taxRates.Any(); displayTax = !displayTaxRates; @@ -654,8 +665,11 @@ protected virtual string ProductListToHtmlTable(Order order, int languageId, int var orderTotalInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderTotal, order.CurrencyRate); cusTotal = _priceFormatter.FormatPrice(orderTotalInCustomerCurrency, true, order.CustomerCurrencyCode, false, language); + var orderAmountInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderAmount, order.CurrencyRate); + cusAmount = _priceFormatter.FormatPrice(orderAmountInCustomerCurrency, true, order.CustomerCurrencyCode, false, language); - + var orderAmountInclInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderAmountIncl, order.CurrencyRate); + cusAmountIncl = _priceFormatter.FormatPrice(orderAmountInclInCustomerCurrency, true, order.CustomerCurrencyCode, false, language); //subtotal sb.AppendLine(string.Format(" {1} {2}", _templatesSettings.Color3, _localizationService.GetResource("Messages.Order.SubTotal", languageId), cusSubTotal)); @@ -680,27 +694,45 @@ protected virtual string ProductListToHtmlTable(Order order, int languageId, int sb.AppendLine(string.Format(" {1} {2}", _templatesSettings.Color3, paymentMethodFeeTitle, cusPaymentMethodAdditionalFee)); } + //discount + if (displayDiscount) + { + sb.AppendLine(string.Format(" {1} {2}", _templatesSettings.Color3, _localizationService.GetResource("Messages.Order.TotalDiscount", languageId), cusDiscount)); + } + + //amount + if (includingTax) + { + sb.AppendLine(string.Format(" {1} {2}", _templatesSettings.Color3, _localizationService.GetResource("Messages.Order.OrderAmountIncl", languageId), cusAmountIncl)); + } + //tax if (displayTax) { - sb.AppendLine(string.Format(" {1} {2}", _templatesSettings.Color3, _localizationService.GetResource("Messages.Order.Tax", languageId), cusTaxTotal)); + sb.AppendLine(string.Format(" {1} {2}", _templatesSettings.Color3, _localizationService.GetResource(includingTax ? "Messages.Order.TaxIncl" : "Messages.Order.Tax", languageId), cusTaxTotal)); + } + + //amount + if (!includingTax) + { + sb.AppendLine(string.Format(" {1} {2}", _templatesSettings.Color3, _localizationService.GetResource("Messages.Order.OrderAmount", languageId), cusAmount)); } if (displayTaxRates) { foreach (var item in taxRates) { string taxRate = String.Format(_localizationService.GetResource("Messages.Order.TaxRateLine"), _priceFormatter.FormatTaxRate(item.Key)); - string taxValue = _priceFormatter.FormatPrice(item.Value, true, order.CustomerCurrencyCode, false, language); - sb.AppendLine(string.Format(" {1} {2}", _templatesSettings.Color3, taxRate, taxValue)); + string OrderAmount = _priceFormatter.FormatPrice(item.Value.Amount, true, order.CustomerCurrencyCode, false, language); + string DiscountAmount = _priceFormatter.FormatPrice(item.Value.DiscountAmount, true, order.CustomerCurrencyCode, false, language); + string Amount = _priceFormatter.FormatPrice(item.Value.BaseAmount, true, order.CustomerCurrencyCode, false, language); + string VatAmount = _priceFormatter.FormatPrice(item.Value.VatAmount, true, order.CustomerCurrencyCode, false, language); + string AmountIncludingVAT = _priceFormatter.FormatPrice(item.Value.AmountIncludingVAT, true, order.CustomerCurrencyCode, false, language); + + sb.AppendLine(string.Format(" {1} {2}", + _templatesSettings.Color3, taxRate, VatAmount, OrderAmount, DiscountAmount, Amount, AmountIncludingVAT)); } } - //discount - if (displayDiscount) - { - sb.AppendLine(string.Format(" {1} {2}", _templatesSettings.Color3, _localizationService.GetResource("Messages.Order.TotalDiscount", languageId), cusDiscount)); - } - //gift cards var gcuhC = order.GiftCardUsageHistory; foreach (var gcuh in gcuhC) diff --git a/src/Libraries/Nop.Services/Nop.Services.csproj b/src/Libraries/Nop.Services/Nop.Services.csproj index 9aa328ea8b5..c5ff968b455 100644 --- a/src/Libraries/Nop.Services/Nop.Services.csproj +++ b/src/Libraries/Nop.Services/Nop.Services.csproj @@ -52,6 +52,34 @@ ..\..\packages\ImageResizer.Plugins.PrettyGifs.4.0.5\lib\net45\ImageResizer.Plugins.PrettyGifs.dll True + + ..\..\packages\itext7.7.0.1\lib\net40\itext.barcodes.dll + True + + + ..\..\packages\itext7.7.0.1\lib\net40\itext.forms.dll + True + + + ..\..\packages\itext7.7.0.1\lib\net40\itext.io.dll + True + + + ..\..\packages\itext7.7.0.1\lib\net40\itext.kernel.dll + True + + + ..\..\packages\itext7.7.0.1\lib\net40\itext.layout.dll + True + + + ..\..\packages\itext7.7.0.1\lib\net40\itext.pdfa.dll + True + + + ..\..\packages\itext7.7.0.1\lib\net40\itext.sign.dll + True + ..\..\packages\iTextSharp.5.5.10\lib\itextsharp.dll True @@ -208,6 +236,7 @@ + @@ -277,6 +306,7 @@ + @@ -500,7 +530,9 @@ - + + Designer + SettingsSingleFileGenerator Settings.Designer.cs diff --git a/src/Libraries/Nop.Services/Orders/IOrderProcessingService.cs b/src/Libraries/Nop.Services/Orders/IOrderProcessingService.cs index c5786a34bb0..fec0cfbe7b7 100644 --- a/src/Libraries/Nop.Services/Orders/IOrderProcessingService.cs +++ b/src/Libraries/Nop.Services/Orders/IOrderProcessingService.cs @@ -264,6 +264,11 @@ public partial interface IOrderProcessingService /// Shopping cart /// true - OK; false - minimum order total amount is not reached bool ValidateMinOrderTotalAmount(IList cart); + /// + /// Get next Invoice ID and store settings + /// + /// + string GetInvoiceId(); /// /// Gets a value indicating whether payment workflow is required diff --git a/src/Libraries/Nop.Services/Orders/IOrderTotalCalculationService.cs b/src/Libraries/Nop.Services/Orders/IOrderTotalCalculationService.cs index 55de95480d4..38c2b427dfc 100644 --- a/src/Libraries/Nop.Services/Orders/IOrderTotalCalculationService.cs +++ b/src/Libraries/Nop.Services/Orders/IOrderTotalCalculationService.cs @@ -2,6 +2,7 @@ using Nop.Core.Domain.Customers; using Nop.Core.Domain.Orders; using Nop.Services.Discounts; +using Nop.Services.Tax; namespace Nop.Services.Orders { @@ -14,7 +15,7 @@ public partial interface IOrderTotalCalculationService /// Gets shopping cart subtotal /// /// Cart - /// A value indicating whether calculated price should include tax + /// A value indicating whether submitted prices do include tax /// Applied discount amount /// Applied discounts /// Sub total (without discount) @@ -28,17 +29,17 @@ void GetShoppingCartSubTotal(IList cart, /// Gets shopping cart subtotal /// /// Cart - /// A value indicating whether calculated price should include tax + /// A value indicating whether submitted prices do include tax /// Applied discount amount /// Applied discounts /// Sub total (without discount) /// Sub total (with discount) - /// Tax rates (of order sub total) + /// Tax rates summary (of order sub total) void GetShoppingCartSubTotal(IList cart, bool includingTax, out decimal discountAmount, out List appliedDiscounts, out decimal subTotalWithoutDiscount, out decimal subTotalWithDiscount, - out SortedDictionary taxRates); + out TaxSummary taxSummary); @@ -80,7 +81,7 @@ decimal AdjustShippingRate(decimal shippingRate, /// Gets shopping cart shipping total /// /// Cart - /// A value indicating whether calculated price should include tax + /// A value indicating whether submitted prices do include tax /// Shipping total decimal? GetShoppingCartShippingTotal(IList cart, bool includingTax); @@ -88,7 +89,7 @@ decimal AdjustShippingRate(decimal shippingRate, /// Gets shopping cart shipping total /// /// Cart - /// A value indicating whether calculated price should include tax + /// A value indicating whether submitted prices do include tax /// Applied tax rate /// Shipping total decimal? GetShoppingCartShippingTotal(IList cart, bool includingTax, @@ -98,7 +99,7 @@ decimal AdjustShippingRate(decimal shippingRate, /// Gets shopping cart shipping total /// /// Cart - /// A value indicating whether calculated price should include tax + /// A value indicating whether submitted prices do include tax /// Applied tax rate /// Applied discounts /// Shipping total @@ -114,18 +115,28 @@ decimal AdjustShippingRate(decimal shippingRate, /// Gets tax /// /// Shopping cart + /// Tax Summary /// A value indicating whether we should use payment method additional fee when calculating tax /// Tax total - decimal GetTaxTotal(IList cart, bool usePaymentMethodAdditionalFee = true); + decimal GetTaxTotal(IList cart, out TaxSummary taxSummary, bool usePaymentMethodAdditionalFee = true); /// /// Gets tax /// /// Shopping cart - /// Tax rates + /// A value indicating whether submitted prices do include tax + /// Tax rates summary + /// Applied invoice discounts + /// Applied subtotal discounts + /// Applied shipping discounts /// A value indicating whether we should use payment method additional fee when calculating tax /// Tax total - decimal GetTaxTotal(IList cart, out SortedDictionary taxRates, + decimal GetTaxTotal(IList cart, + bool includingTax, + out TaxSummary taxSummary, + out List appliedDiscounts, + out List subTotalAppliedDiscounts, + out List shippingAppliedDiscounts, bool usePaymentMethodAdditionalFee = true); @@ -146,18 +157,26 @@ decimal GetTaxTotal(IList cart, out SortedDictionary /// Cart - /// Applied gift cards /// Applied discount amount /// Applied discounts + /// Applied subtotal discounts + /// Applied shipping discounts + /// Applied gift cards /// Reward points to redeem /// Reward points amount in primary store currency to redeem + /// Tax summary + /// A value indicating whether submitted prices do include tax /// A value indicating reward points should be used; null to detect current choice of the customer /// A value indicating whether we should use payment method additional fee when calculating order total /// Shopping cart total;Null if shopping cart total couldn't be calculated now decimal? GetShoppingCartTotal(IList cart, out decimal discountAmount, out List appliedDiscounts, + out List subTotalAppliedDiscounts, + out List shippingAppliedDiscounts, out List appliedGiftCards, out int redeemedRewardPoints, out decimal redeemedRewardPointsAmount, + out TaxSummary taxSummary, + bool includingTax, bool? useRewardPoints = null, bool usePaymentMethodAdditionalFee = true); diff --git a/src/Libraries/Nop.Services/Orders/OrderProcessingService.cs b/src/Libraries/Nop.Services/Orders/OrderProcessingService.cs index 97d4a94335b..671be703834 100644 --- a/src/Libraries/Nop.Services/Orders/OrderProcessingService.cs +++ b/src/Libraries/Nop.Services/Orders/OrderProcessingService.cs @@ -30,6 +30,7 @@ using Nop.Services.Shipping; using Nop.Services.Tax; using Nop.Services.Vendors; +using Nop.Services.Configuration; namespace Nop.Services.Orders { @@ -39,7 +40,7 @@ namespace Nop.Services.Orders public partial class OrderProcessingService : IOrderProcessingService { #region Fields - + private readonly IOrderService _orderService; private readonly IWebHelper _webHelper; private readonly ILocalizationService _localizationService; @@ -83,6 +84,8 @@ public partial class OrderProcessingService : IOrderProcessingService private readonly CurrencySettings _currencySettings; private readonly ICustomNumberFormatter _customNumberFormatter; + private readonly ISettingService _settingService; + #endregion #region Ctor @@ -129,6 +132,7 @@ public partial class OrderProcessingService : IOrderProcessingService /// Tax settings /// Localization settings /// Currency settings + /// Setting service /// Custom number formatter public OrderProcessingService(IOrderService orderService, IWebHelper webHelper, @@ -170,6 +174,7 @@ public OrderProcessingService(IOrderService orderService, TaxSettings taxSettings, LocalizationSettings localizationSettings, CurrencySettings currencySettings, + ISettingService settingService, ICustomNumberFormatter customNumberFormatter) { this._orderService = orderService; @@ -213,6 +218,7 @@ public OrderProcessingService(IOrderService orderService, this._taxSettings = taxSettings; this._localizationSettings = localizationSettings; this._currencySettings = currencySettings; + this._settingService = settingService; this._customNumberFormatter = customNumberFormatter; } @@ -270,6 +276,8 @@ public PlaceOrderContainter() public int RedeemedRewardPoints { get; set; } public decimal RedeemedRewardPointsAmount { get; set; } public decimal OrderTotal { get; set; } + public decimal OrderAmount { get; set; } //MF 09.12.16 + public decimal OrderAmountIncl { get; set; } //MF 09.12.16 } #endregion @@ -371,27 +379,42 @@ protected virtual PlaceOrderContainter PreparePlaceOrderDetails(ProcessPaymentRe else details.CustomerTaxDisplayType = _taxSettings.TaxDisplayType; - //sub total (incl tax) - decimal orderSubTotalDiscountAmount; - List orderSubTotalAppliedDiscounts; - decimal subTotalWithoutDiscountBase; - decimal subTotalWithDiscountBase; - _orderTotalCalculationService.GetShoppingCartSubTotal(details.Cart, true, out orderSubTotalDiscountAmount, - out orderSubTotalAppliedDiscounts, out subTotalWithoutDiscountBase, out subTotalWithDiscountBase); - details.OrderSubTotalInclTax = subTotalWithoutDiscountBase; - details.OrderSubTotalDiscountInclTax = orderSubTotalDiscountAmount; + + //order total (and applied discounts, gift cards, reward points) + List appliedGiftCards; + List orderAppliedDiscounts; + List subTotalAppliedDiscounts; + List shippingAppliedDiscounts; + decimal orderDiscountAmount; + int redeemedRewardPoints; + decimal redeemedRewardPointsAmount; + TaxSummary taxSummaryIncl; + TaxSummary taxSummaryExcl; + + //calculate two times to get correct incl./excl. tax + var orderTotalIncl = _orderTotalCalculationService.GetShoppingCartTotal(details.Cart, out orderDiscountAmount, + out orderAppliedDiscounts, out subTotalAppliedDiscounts, out shippingAppliedDiscounts, + out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount, + out taxSummaryIncl, true); + + var orderTotalExcl = _orderTotalCalculationService.GetShoppingCartTotal(details.Cart, out orderDiscountAmount, + out orderAppliedDiscounts, out subTotalAppliedDiscounts, out shippingAppliedDiscounts, + out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount, + out taxSummaryExcl, false); + + //sub total (incl tax) (excl tax) + details.OrderSubTotalInclTax = taxSummaryIncl.TotalSubTotalAmount; + details.OrderSubTotalDiscountInclTax = taxSummaryIncl.TotalSubTotalDiscAmount; + + details.OrderSubTotalExclTax = taxSummaryExcl.TotalSubTotalAmount; + details.OrderSubTotalDiscountExclTax = taxSummaryExcl.TotalSubTotalDiscAmount; + //discount history - foreach (var disc in orderSubTotalAppliedDiscounts) + foreach (var disc in subTotalAppliedDiscounts) if (!details.AppliedDiscounts.ContainsDiscount(disc)) details.AppliedDiscounts.Add(disc); - //sub total (excl tax) - _orderTotalCalculationService.GetShoppingCartSubTotal(details.Cart, false, out orderSubTotalDiscountAmount, - out orderSubTotalAppliedDiscounts, out subTotalWithoutDiscountBase, out subTotalWithDiscountBase); - details.OrderSubTotalExclTax = subTotalWithoutDiscountBase; - details.OrderSubTotalDiscountExclTax = orderSubTotalDiscountAmount; - //shipping info if (details.Cart.RequiresShipping()) { @@ -439,28 +462,20 @@ protected virtual PlaceOrderContainter PreparePlaceOrderDetails(ProcessPaymentRe details.ShippingStatus = ShippingStatus.ShippingNotRequired; //shipping total - decimal tax; - List shippingTotalDiscounts; - var orderShippingTotalInclTax = _orderTotalCalculationService.GetShoppingCartShippingTotal(details.Cart, true, out tax, out shippingTotalDiscounts); - var orderShippingTotalExclTax = _orderTotalCalculationService.GetShoppingCartShippingTotal(details.Cart, false); - if (!orderShippingTotalInclTax.HasValue || !orderShippingTotalExclTax.HasValue) - throw new NopException("Shipping total couldn't be calculated"); - - details.OrderShippingTotalInclTax = orderShippingTotalInclTax.Value; - details.OrderShippingTotalExclTax = orderShippingTotalExclTax.Value; + details.OrderShippingTotalInclTax = taxSummaryIncl.TotalShippingAmount; + details.OrderShippingTotalExclTax = taxSummaryExcl.TotalShippingAmount; - foreach(var disc in shippingTotalDiscounts) + foreach(var disc in shippingAppliedDiscounts) if (!details.AppliedDiscounts.ContainsDiscount(disc)) details.AppliedDiscounts.Add(disc); //payment total - var paymentAdditionalFee = _paymentService.GetAdditionalHandlingFee(details.Cart, processPaymentRequest.PaymentMethodSystemName); - details.PaymentAdditionalFeeInclTax = _taxService.GetPaymentMethodAdditionalFee(paymentAdditionalFee, true, details.Customer); - details.PaymentAdditionalFeeExclTax = _taxService.GetPaymentMethodAdditionalFee(paymentAdditionalFee, false, details.Customer); + details.PaymentAdditionalFeeInclTax = taxSummaryIncl.TotalPaymentFeeAmount; + details.PaymentAdditionalFeeExclTax = taxSummaryExcl.TotalPaymentFeeAmount; //tax amount - SortedDictionary taxRatesDictionary; - details.OrderTaxTotal = _orderTotalCalculationService.GetTaxTotal(details.Cart, out taxRatesDictionary); + bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; + details.OrderTaxTotal = includingTax? taxSummaryIncl.TotalAmountVAT : taxSummaryExcl.TotalAmountVAT; //VAT number var customerVatStatus = (VatNumberStatus)details.Customer.GetAttribute(SystemCustomerAttributeNames.VatNumberStatusId); @@ -468,25 +483,25 @@ protected virtual PlaceOrderContainter PreparePlaceOrderDetails(ProcessPaymentRe details.VatNumber = details.Customer.GetAttribute(SystemCustomerAttributeNames.VatNumber); //tax rates - details.TaxRates = taxRatesDictionary.Aggregate(string.Empty, (current, next) => - string.Format("{0}{1}:{2}; ", current, next.Key.ToString(CultureInfo.InvariantCulture), next.Value.ToString(CultureInfo.InvariantCulture))); + //var taxrates = includingTax ? taxSummaryIncl.GenerateOldTaxrateDict() : taxSummaryExcl.GenerateOldTaxrateDict(); + var taxrates = includingTax ? taxSummaryIncl : taxSummaryExcl; + details.TaxRates = taxrates.TaxRates.Aggregate(string.Empty, (current, next) => + string.Format("{0}{1}:{2}:{3}:{4}:{5}:{6}; ", current, + next.Key.ToString(CultureInfo.InvariantCulture), + (next.Value.SubtotalAmount + next.Value.ShippingAmount + next.Value.PaymentFeeAmount).ToString(CultureInfo.InvariantCulture), + (next.Value.SubTotalDiscAmount + next.Value.InvoiceDiscountAmount).ToString(CultureInfo.InvariantCulture), + next.Value.BaseAmount.ToString(CultureInfo.InvariantCulture), + next.Value.VatAmount.ToString(CultureInfo.InvariantCulture), + next.Value.AmountIncludingVAT.ToString(CultureInfo.InvariantCulture) + ) + ); //order total (and applied discounts, gift cards, reward points) - List appliedGiftCards; - List orderAppliedDiscounts; - decimal orderDiscountAmount; - int redeemedRewardPoints; - decimal redeemedRewardPointsAmount; - var orderTotal = _orderTotalCalculationService.GetShoppingCartTotal(details.Cart, out orderDiscountAmount, - out orderAppliedDiscounts, out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount); - if (!orderTotal.HasValue) - throw new NopException("Order total couldn't be calculated"); - - details.OrderDiscountAmount = orderDiscountAmount; + details.OrderDiscountAmount = includingTax ? taxSummaryIncl.TotalInvDiscAmount : taxSummaryExcl.TotalInvDiscAmount; details.RedeemedRewardPoints = redeemedRewardPoints; details.RedeemedRewardPointsAmount = redeemedRewardPointsAmount; details.AppliedGiftCards = appliedGiftCards; - details.OrderTotal = orderTotal.Value; + details.OrderTotal = includingTax ? orderTotalIncl ?? 0 : orderTotalExcl ?? 0; //discount history foreach (var disc in orderAppliedDiscounts) @@ -615,6 +630,8 @@ protected virtual PlaceOrderContainter PrepareRecurringOrderDetails(ProcessPayme //order total details.OrderDiscountAmount = details.InitialOrder.OrderDiscount; details.OrderTotal = details.InitialOrder.OrderTotal; + details.OrderAmount = details.InitialOrder.OrderAmount; + details.OrderAmountIncl = details.InitialOrder.OrderAmountIncl; processPaymentRequest.OrderTotal = details.OrderTotal; return details; @@ -627,7 +644,7 @@ protected virtual PlaceOrderContainter PrepareRecurringOrderDetails(ProcessPayme /// Process payment result /// Details /// Order - protected virtual Order SaveOrderDetails(ProcessPaymentRequest processPaymentRequest, + protected virtual Order SaveOrderDetails(ProcessPaymentRequest processPaymentRequest, ProcessPaymentResult processPaymentResult, PlaceOrderContainter details) { var order = new Order @@ -649,6 +666,8 @@ protected virtual Order SaveOrderDetails(ProcessPaymentRequest processPaymentReq TaxRates = details.TaxRates, OrderTax = details.OrderTaxTotal, OrderTotal = details.OrderTotal, + OrderAmount = details.OrderAmount, + OrderAmountIncl = details.OrderAmountIncl, RefundedAmount = decimal.Zero, OrderDiscount = details.OrderDiscountAmount, CheckoutAttributeDescription = details.CheckoutAttributeDescription, @@ -684,6 +703,8 @@ protected virtual Order SaveOrderDetails(ProcessPaymentRequest processPaymentReq CustomValuesXml = processPaymentRequest.SerializeCustomValues(), VatNumber = details.VatNumber, CreatedOnUtc = DateTime.UtcNow, + InvoiceId = null, + InvoiceDateUtc = null, CustomOrderNumber = string.Empty }; @@ -1142,6 +1163,14 @@ public virtual void CheckOrderStatus(Order order) _orderService.UpdateOrder(order); } + //set invoice id + if (order.PaymentStatus == PaymentStatus.Paid && order.InvoiceId == null) + { + order.InvoiceDateUtc = DateTime.UtcNow; + order.InvoiceId = GetInvoiceId(); + _orderService.UpdateOrder(order); + } + if (order.OrderStatus == OrderStatus.Pending) { if (order.PaymentStatus == PaymentStatus.Authorized || @@ -1312,7 +1341,8 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR LicenseDownloadId = 0, ItemWeight = itemWeight, RentalStartDateUtc = sc.RentalStartDateUtc, - RentalEndDateUtc = sc.RentalEndDateUtc + RentalEndDateUtc = sc.RentalEndDateUtc, + VatRate = taxRate //MF 25.11.16 }; order.OrderItems.Add(orderItem); _orderService.UpdateOrder(order); @@ -1490,7 +1520,10 @@ public virtual void UpdateOrderTotals(UpdateOrderParameters updateOrderParameter RentalEndDateUtc = orderItem.RentalEndDateUtc, RentalStartDateUtc = orderItem.RentalStartDateUtc, ShoppingCartType = ShoppingCartType.ShoppingCart, - StoreId = updatedOrder.StoreId + StoreId = updatedOrder.StoreId, + VatRate = orderItem.Id == updatedOrderItem.Id ? updateOrderParameters.VatRate : orderItem.VatRate, + SubTotalInclTax = orderItem.Id == updatedOrderItem.Id ? updateOrderParameters.SubTotalInclTax : orderItem.PriceInclTax, + SubTotalExclTax = orderItem.Id == updatedOrderItem.Id ? updateOrderParameters.SubTotalExclTax : orderItem.PriceExclTax }).ToList(); //get shopping cart item which has been updated @@ -1775,7 +1808,8 @@ public virtual IEnumerable ProcessNextRecurringPayment(RecurringPayment LicenseDownloadId = 0, ItemWeight = orderItem.ItemWeight, RentalStartDateUtc = orderItem.RentalStartDateUtc, - RentalEndDateUtc = orderItem.RentalEndDateUtc + RentalEndDateUtc = orderItem.RentalEndDateUtc, + VatRate = orderItem.VatRate }; order.OrderItems.Add(newOrderItem); _orderService.UpdateOrder(order); @@ -3142,6 +3176,31 @@ public virtual bool ValidateMinOrderTotalAmount(IList cart) return true; } + /// + /// Get invoice ID + /// + /// + public virtual string GetInvoiceId() + { + var actYear = DateTime.UtcNow.Year; + int ident = _orderSettings.InvoiceIdent; + if (_orderSettings.InvoiceYear < actYear) + { + // Reset counter if a new year + ident = 1; + _orderSettings.InvoiceYear = actYear; + _settingService.SetSetting("ordersettings.invoiceyear", _orderSettings.InvoiceYear); + } + else + { + ident += 1; + } + _orderSettings.InvoiceIdent = ident; + // Update settings + _settingService.SetSetting("ordersettings.invoiceident", _orderSettings.InvoiceIdent); + + return string.Format("I-{0}.{1}", DateTime.UtcNow.Year, ident.ToString("D5")); + } /// /// Gets a value indicating whether payment workflow is required /// diff --git a/src/Libraries/Nop.Services/Orders/OrderTotalCalculationService.cs b/src/Libraries/Nop.Services/Orders/OrderTotalCalculationService.cs index 5af55fdec13..94ded4673ca 100644 --- a/src/Libraries/Nop.Services/Orders/OrderTotalCalculationService.cs +++ b/src/Libraries/Nop.Services/Orders/OrderTotalCalculationService.cs @@ -1,1429 +1,1156 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using Nop.Core; -using Nop.Core.Domain.Catalog; -using Nop.Core.Domain.Common; -using Nop.Core.Domain.Customers; -using Nop.Core.Domain.Discounts; -using Nop.Core.Domain.Orders; -using Nop.Core.Domain.Shipping; -using Nop.Core.Domain.Tax; -using Nop.Services.Catalog; -using Nop.Services.Common; -using Nop.Services.Discounts; -using Nop.Services.Payments; -using Nop.Services.Shipping; -using Nop.Services.Tax; - -namespace Nop.Services.Orders -{ - /// - /// Order service - /// - public partial class OrderTotalCalculationService : IOrderTotalCalculationService - { - #region Fields - - private readonly IWorkContext _workContext; - private readonly IStoreContext _storeContext; - private readonly IPriceCalculationService _priceCalculationService; - private readonly ITaxService _taxService; - private readonly IShippingService _shippingService; - private readonly IPaymentService _paymentService; - private readonly ICheckoutAttributeParser _checkoutAttributeParser; - private readonly IDiscountService _discountService; - private readonly IGiftCardService _giftCardService; - private readonly IGenericAttributeService _genericAttributeService; - private readonly IRewardPointService _rewardPointService; - private readonly TaxSettings _taxSettings; - private readonly RewardPointsSettings _rewardPointsSettings; - private readonly ShippingSettings _shippingSettings; - private readonly ShoppingCartSettings _shoppingCartSettings; - private readonly CatalogSettings _catalogSettings; - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Work context - /// Store context - /// Price calculation service - /// Tax service - /// Shipping service - /// Payment service - /// Checkout attribute parser - /// Discount service - /// Gift card service - /// Generic attribute service - /// Reward point service - /// Tax settings - /// Reward points settings - /// Shipping settings - /// Shopping cart settings - /// Catalog settings - public OrderTotalCalculationService(IWorkContext workContext, - IStoreContext storeContext, - IPriceCalculationService priceCalculationService, - ITaxService taxService, - IShippingService shippingService, - IPaymentService paymentService, - ICheckoutAttributeParser checkoutAttributeParser, - IDiscountService discountService, - IGiftCardService giftCardService, - IGenericAttributeService genericAttributeService, - IRewardPointService rewardPointService, - TaxSettings taxSettings, - RewardPointsSettings rewardPointsSettings, - ShippingSettings shippingSettings, - ShoppingCartSettings shoppingCartSettings, - CatalogSettings catalogSettings) - { - this._workContext = workContext; - this._storeContext = storeContext; - this._priceCalculationService = priceCalculationService; - this._taxService = taxService; - this._shippingService = shippingService; - this._paymentService = paymentService; - this._checkoutAttributeParser = checkoutAttributeParser; - this._discountService = discountService; - this._giftCardService = giftCardService; - this._genericAttributeService = genericAttributeService; - this._rewardPointService = rewardPointService; - this._taxSettings = taxSettings; - this._rewardPointsSettings = rewardPointsSettings; - this._shippingSettings = shippingSettings; - this._shoppingCartSettings = shoppingCartSettings; - this._catalogSettings = catalogSettings; - } - - #endregion - - #region Utilities - - /// - /// Gets an order discount (applied to order subtotal) - /// - /// Customer - /// Order subtotal - /// Applied discounts - /// Order discount - protected virtual decimal GetOrderSubtotalDiscount(Customer customer, - decimal orderSubTotal, out List appliedDiscounts) - { - appliedDiscounts = new List(); - decimal discountAmount = decimal.Zero; - if (_catalogSettings.IgnoreDiscounts) - return discountAmount; - - var allDiscounts = _discountService.GetAllDiscountsForCaching(DiscountType.AssignedToOrderSubTotal); - var allowedDiscounts = new List(); - if (allDiscounts != null) - foreach (var discount in allDiscounts) - if (_discountService.ValidateDiscount(discount, customer).IsValid && - !allowedDiscounts.ContainsDiscount(discount)) - { - allowedDiscounts.Add(discount); - } - - appliedDiscounts = allowedDiscounts.GetPreferredDiscount(orderSubTotal, out discountAmount); - - if (discountAmount < decimal.Zero) - discountAmount = decimal.Zero; - - return discountAmount; - } - - /// - /// Gets a shipping discount - /// - /// Customer - /// Shipping total - /// Applied discounts - /// Shipping discount - protected virtual decimal GetShippingDiscount(Customer customer, decimal shippingTotal, out List appliedDiscounts) - { - appliedDiscounts = new List(); - decimal shippingDiscountAmount = decimal.Zero; - if (_catalogSettings.IgnoreDiscounts) - return shippingDiscountAmount; - - var allDiscounts = _discountService.GetAllDiscountsForCaching(DiscountType.AssignedToShipping); - var allowedDiscounts = new List(); - if (allDiscounts != null) - foreach (var discount in allDiscounts) - if (_discountService.ValidateDiscount(discount, customer).IsValid && - !allowedDiscounts.ContainsDiscount(discount)) - { - allowedDiscounts.Add(discount); - } - - appliedDiscounts = allowedDiscounts.GetPreferredDiscount(shippingTotal, out shippingDiscountAmount); - - if (shippingDiscountAmount < decimal.Zero) - shippingDiscountAmount = decimal.Zero; - - if (_shoppingCartSettings.RoundPricesDuringCalculation) - shippingDiscountAmount = RoundingHelper.RoundPrice(shippingDiscountAmount); - - return shippingDiscountAmount; - } - - /// - /// Gets an order discount (applied to order total) - /// - /// Customer - /// Order total - /// Applied discounts - /// Order discount - protected virtual decimal GetOrderTotalDiscount(Customer customer, decimal orderTotal, out List appliedDiscounts) - { - appliedDiscounts = new List(); - decimal discountAmount = decimal.Zero; - if (_catalogSettings.IgnoreDiscounts) - return discountAmount; - - var allDiscounts = _discountService.GetAllDiscountsForCaching(DiscountType.AssignedToOrderTotal); - var allowedDiscounts = new List(); - if (allDiscounts != null) - foreach (var discount in allDiscounts) - if (_discountService.ValidateDiscount(discount, customer).IsValid && - !allowedDiscounts.ContainsDiscount(discount)) - { - allowedDiscounts.Add(discount); - } - - appliedDiscounts = allowedDiscounts.GetPreferredDiscount(orderTotal, out discountAmount); - - if (discountAmount < decimal.Zero) - discountAmount = decimal.Zero; - - if (_shoppingCartSettings.RoundPricesDuringCalculation) - discountAmount = RoundingHelper.RoundPrice(discountAmount); - - return discountAmount; - } - - #endregion - - #region Methods - - /// - /// Gets shopping cart subtotal - /// - /// Cart - /// A value indicating whether calculated price should include tax - /// Applied discount amount - /// Applied discounts - /// Sub total (without discount) - /// Sub total (with discount) - public virtual void GetShoppingCartSubTotal(IList cart, - bool includingTax, - out decimal discountAmount, out List appliedDiscounts, - out decimal subTotalWithoutDiscount, out decimal subTotalWithDiscount) - { - SortedDictionary taxRates; - GetShoppingCartSubTotal(cart, includingTax, - out discountAmount, out appliedDiscounts, - out subTotalWithoutDiscount, out subTotalWithDiscount, out taxRates); - } - - /// - /// Gets shopping cart subtotal - /// - /// Cart - /// A value indicating whether calculated price should include tax - /// Applied discount amount - /// Applied discounts - /// Sub total (without discount) - /// Sub total (with discount) - /// Tax rates (of order sub total) - public virtual void GetShoppingCartSubTotal(IList cart, - bool includingTax, - out decimal discountAmount, out List appliedDiscounts, - out decimal subTotalWithoutDiscount, out decimal subTotalWithDiscount, - out SortedDictionary taxRates) - { - discountAmount = decimal.Zero; - appliedDiscounts = new List(); - subTotalWithoutDiscount = decimal.Zero; - subTotalWithDiscount = decimal.Zero; - taxRates = new SortedDictionary(); - - if (!cart.Any()) - return; - - //get the customer - Customer customer = cart.GetCustomer(); - - //sub totals - decimal subTotalExclTaxWithoutDiscount = decimal.Zero; - decimal subTotalInclTaxWithoutDiscount = decimal.Zero; - foreach (var shoppingCartItem in cart) - { - decimal sciSubTotal = _priceCalculationService.GetSubTotal(shoppingCartItem); - - decimal taxRate; - decimal sciExclTax = _taxService.GetProductPrice(shoppingCartItem.Product, sciSubTotal, false, customer, out taxRate); - decimal sciInclTax = _taxService.GetProductPrice(shoppingCartItem.Product, sciSubTotal, true, customer, out taxRate); - subTotalExclTaxWithoutDiscount += sciExclTax; - subTotalInclTaxWithoutDiscount += sciInclTax; - - //tax rates - decimal sciTax = sciInclTax - sciExclTax; - if (taxRate > decimal.Zero && sciTax > decimal.Zero) - { - if (!taxRates.ContainsKey(taxRate)) - { - taxRates.Add(taxRate, sciTax); - } - else - { - taxRates[taxRate] = taxRates[taxRate] + sciTax; - } - } - } - - //checkout attributes - if (customer != null) - { - var checkoutAttributesXml = customer.GetAttribute(SystemCustomerAttributeNames.CheckoutAttributes, _genericAttributeService, _storeContext.CurrentStore.Id); - var attributeValues = _checkoutAttributeParser.ParseCheckoutAttributeValues(checkoutAttributesXml); - if (attributeValues != null) - { - foreach (var attributeValue in attributeValues) - { - decimal taxRate; - - decimal caExclTax = _taxService.GetCheckoutAttributePrice(attributeValue, false, customer, out taxRate); - decimal caInclTax = _taxService.GetCheckoutAttributePrice(attributeValue, true, customer, out taxRate); - subTotalExclTaxWithoutDiscount += caExclTax; - subTotalInclTaxWithoutDiscount += caInclTax; - - //tax rates - decimal caTax = caInclTax - caExclTax; - if (taxRate > decimal.Zero && caTax > decimal.Zero) - { - if (!taxRates.ContainsKey(taxRate)) - { - taxRates.Add(taxRate, caTax); - } - else - { - taxRates[taxRate] = taxRates[taxRate] + caTax; - } - } - } - } - } - - //subtotal without discount - subTotalWithoutDiscount = includingTax ? subTotalInclTaxWithoutDiscount : subTotalExclTaxWithoutDiscount; - if (subTotalWithoutDiscount < decimal.Zero) - subTotalWithoutDiscount = decimal.Zero; - - if (_shoppingCartSettings.RoundPricesDuringCalculation) - subTotalWithoutDiscount = RoundingHelper.RoundPrice(subTotalWithoutDiscount); - - //We calculate discount amount on order subtotal excl tax (discount first) - //calculate discount amount ('Applied to order subtotal' discount) - decimal discountAmountExclTax = GetOrderSubtotalDiscount(customer, subTotalExclTaxWithoutDiscount, out appliedDiscounts); - if (subTotalExclTaxWithoutDiscount < discountAmountExclTax) - discountAmountExclTax = subTotalExclTaxWithoutDiscount; - decimal discountAmountInclTax = discountAmountExclTax; - //subtotal with discount (excl tax) - decimal subTotalExclTaxWithDiscount = subTotalExclTaxWithoutDiscount - discountAmountExclTax; - decimal subTotalInclTaxWithDiscount = subTotalExclTaxWithDiscount; - - //add tax for shopping items & checkout attributes - var tempTaxRates = new Dictionary(taxRates); - foreach (KeyValuePair kvp in tempTaxRates) - { - decimal taxRate = kvp.Key; - decimal taxValue = kvp.Value; - - if (taxValue != decimal.Zero) - { - //discount the tax amount that applies to subtotal items - if (subTotalExclTaxWithoutDiscount > decimal.Zero) - { - decimal discountTax = taxRates[taxRate] * (discountAmountExclTax / subTotalExclTaxWithoutDiscount); - discountAmountInclTax += discountTax; - taxValue = taxRates[taxRate] - discountTax; - if (_shoppingCartSettings.RoundPricesDuringCalculation) - taxValue = RoundingHelper.RoundPrice(taxValue); - taxRates[taxRate] = taxValue; - } - - //subtotal with discount (incl tax) - subTotalInclTaxWithDiscount += taxValue; - } - } - - if (_shoppingCartSettings.RoundPricesDuringCalculation) - { - discountAmountInclTax = RoundingHelper.RoundPrice(discountAmountInclTax); - discountAmountExclTax = RoundingHelper.RoundPrice(discountAmountExclTax); - } - - if (includingTax) - { - subTotalWithDiscount = subTotalInclTaxWithDiscount; - discountAmount = discountAmountInclTax; - } - else - { - subTotalWithDiscount = subTotalExclTaxWithDiscount; - discountAmount = discountAmountExclTax; - } - - if (subTotalWithDiscount < decimal.Zero) - subTotalWithDiscount = decimal.Zero; - - if (_shoppingCartSettings.RoundPricesDuringCalculation) - subTotalWithDiscount = RoundingHelper.RoundPrice(subTotalWithDiscount); - } - - /// - /// Update order totals - /// - /// Parameters for the updating order - /// Shopping cart - public virtual void UpdateOrderTotals(UpdateOrderParameters updateOrderParameters, IList restoredCart) - { - var updatedOrder = updateOrderParameters.UpdatedOrder; - var updatedOrderItem = updateOrderParameters.UpdatedOrderItem; - - //get the customer - var customer = restoredCart.GetCustomer(); - - #region Sub total - - var subTotalExclTax = decimal.Zero; - var subTotalInclTax = decimal.Zero; - var subTotalTaxRates = new SortedDictionary(); - - foreach (var shoppingCartItem in restoredCart) - { - var itemSubTotalExclTax = decimal.Zero; - var itemSubTotalInclTax = decimal.Zero; - var taxRate = decimal.Zero; - var itemDiscounts = new List(); - - //calculate subtotal for the updated order item - if (shoppingCartItem.Id == updatedOrderItem.Id) - { - //update order item - updatedOrderItem.UnitPriceExclTax = updateOrderParameters.PriceExclTax; - updatedOrderItem.UnitPriceInclTax = updateOrderParameters.PriceInclTax; - updatedOrderItem.DiscountAmountExclTax = updateOrderParameters.DiscountAmountExclTax; - updatedOrderItem.DiscountAmountInclTax = updateOrderParameters.DiscountAmountInclTax; - updatedOrderItem.PriceExclTax = itemSubTotalExclTax = updateOrderParameters.SubTotalExclTax; - updatedOrderItem.PriceInclTax = itemSubTotalInclTax = updateOrderParameters.SubTotalInclTax; - updatedOrderItem.Quantity = shoppingCartItem.Quantity; - - taxRate = Math.Round((100 * (itemSubTotalInclTax - itemSubTotalExclTax)) / itemSubTotalExclTax, 3); - } - else - { - //get the already calculated subtotal from the order item - itemSubTotalExclTax = updatedOrder.OrderItems.FirstOrDefault(item => item.Id == shoppingCartItem.Id).PriceExclTax; - itemSubTotalInclTax = updatedOrder.OrderItems.FirstOrDefault(item => item.Id == shoppingCartItem.Id).PriceInclTax; - taxRate = Math.Round((100 * (itemSubTotalInclTax - itemSubTotalExclTax)) / itemSubTotalExclTax, 3); - } - - foreach (var discount in itemDiscounts) - if (!updateOrderParameters.AppliedDiscounts.ContainsDiscount(discount)) - updateOrderParameters.AppliedDiscounts.Add(discount); - - subTotalExclTax += itemSubTotalExclTax; - subTotalInclTax += itemSubTotalInclTax; - - //tax rates - var itemTaxValue = itemSubTotalInclTax - itemSubTotalExclTax; - if (taxRate > decimal.Zero && itemTaxValue > decimal.Zero) - { - if (!subTotalTaxRates.ContainsKey(taxRate)) - subTotalTaxRates.Add(taxRate, itemTaxValue); - else - subTotalTaxRates[taxRate] = subTotalTaxRates[taxRate] + itemTaxValue; - } - } - - if (subTotalExclTax < decimal.Zero) - subTotalExclTax = decimal.Zero; - - if (subTotalInclTax < decimal.Zero) - subTotalInclTax = decimal.Zero; - - //We calculate discount amount on order subtotal excl tax (discount first) - //calculate discount amount ('Applied to order subtotal' discount) - List subTotalDiscounts; - var discountAmountExclTax = GetOrderSubtotalDiscount(customer, subTotalExclTax, out subTotalDiscounts); - if (subTotalExclTax < discountAmountExclTax) - discountAmountExclTax = subTotalExclTax; - var discountAmountInclTax = discountAmountExclTax; - - //add tax for shopping items - var tempTaxRates = new Dictionary(subTotalTaxRates); - foreach (var kvp in tempTaxRates) - { - if (kvp.Value != decimal.Zero && subTotalExclTax > decimal.Zero) - { - var discountTaxValue = kvp.Value * (discountAmountExclTax / subTotalExclTax); - discountAmountInclTax += discountTaxValue; - subTotalTaxRates[kvp.Key] = kvp.Value - discountTaxValue; - } - } - - //rounding - if (_shoppingCartSettings.RoundPricesDuringCalculation) - { - subTotalExclTax = RoundingHelper.RoundPrice(subTotalExclTax); - subTotalInclTax = RoundingHelper.RoundPrice(subTotalInclTax); - discountAmountExclTax = RoundingHelper.RoundPrice(discountAmountExclTax); - discountAmountInclTax = RoundingHelper.RoundPrice(discountAmountInclTax); - } - - updatedOrder.OrderSubtotalExclTax = subTotalExclTax; - updatedOrder.OrderSubtotalInclTax = subTotalInclTax; - updatedOrder.OrderSubTotalDiscountExclTax = discountAmountExclTax; - updatedOrder.OrderSubTotalDiscountInclTax = discountAmountInclTax; - - foreach (var discount in subTotalDiscounts) - if (!updateOrderParameters.AppliedDiscounts.ContainsDiscount(discount)) - updateOrderParameters.AppliedDiscounts.Add(discount); - - #endregion - - #region Shipping - - var shippingTotalExclTax = decimal.Zero; - var shippingTotalInclTax = decimal.Zero; - var shippingTaxRate = decimal.Zero; - - if (restoredCart.RequiresShipping()) - { - if (!IsFreeShipping(restoredCart, _shippingSettings.FreeShippingOverXIncludingTax ? subTotalInclTax : subTotalExclTax)) - { - var shippingTotal = decimal.Zero; - if (!string.IsNullOrEmpty(updatedOrder.ShippingRateComputationMethodSystemName)) - { - //in the updated order were shipping items - if (updatedOrder.PickUpInStore) - { - //customer chose pickup in store method, try to get chosen pickup point - if (_shippingSettings.AllowPickUpInStore) - { - var pickupPointsResponse = _shippingService.GetPickupPoints(updatedOrder.BillingAddress, updatedOrder.Customer, - updatedOrder.ShippingRateComputationMethodSystemName, _storeContext.CurrentStore.Id); - if (pickupPointsResponse.Success) - { - var selectedPickupPoint = pickupPointsResponse.PickupPoints.FirstOrDefault(point => updatedOrder.ShippingMethod.Contains(point.Name)); - if (selectedPickupPoint != null) - shippingTotal = selectedPickupPoint.PickupFee; - else - updateOrderParameters.Warnings.Add(string.Format("Shipping method {0} could not be loaded", updatedOrder.ShippingMethod)); - } - else - updateOrderParameters.Warnings.AddRange(pickupPointsResponse.Errors); - } - else - updateOrderParameters.Warnings.Add("Pick up in store is not available"); - } - else - { - //customer chose shipping to address, try to get chosen shipping option - var shippingOptionsResponse = _shippingService.GetShippingOptions(restoredCart, - updatedOrder.ShippingAddress, updatedOrder.Customer, updatedOrder.ShippingRateComputationMethodSystemName, _storeContext.CurrentStore.Id); - if (shippingOptionsResponse.Success) - { - var shippingOption = shippingOptionsResponse.ShippingOptions.FirstOrDefault(option => updatedOrder.ShippingMethod.Contains(option.Name)); - if (shippingOption != null) - shippingTotal = shippingOption.Rate; - else - updateOrderParameters.Warnings.Add(string.Format("Shipping method {0} could not be loaded", updatedOrder.ShippingMethod)); - } - else - updateOrderParameters.Warnings.AddRange(shippingOptionsResponse.Errors); - } - } - else - { - //before updating order was without shipping - if (_shippingSettings.AllowPickUpInStore) - { - //try to get the cheapest pickup point - var pickupPointsResponse = _shippingService.GetPickupPoints(updatedOrder.BillingAddress, _workContext.CurrentCustomer, storeId: _storeContext.CurrentStore.Id); - if (pickupPointsResponse.Success) - { - updateOrderParameters.PickupPoint = pickupPointsResponse.PickupPoints.OrderBy(point => point.PickupFee).First(); - shippingTotal = updateOrderParameters.PickupPoint.PickupFee; - } - else - updateOrderParameters.Warnings.AddRange(pickupPointsResponse.Errors); - } - else - updateOrderParameters.Warnings.Add("Pick up in store is not available"); - - if (updateOrderParameters.PickupPoint == null) - { - //or try to get the cheapest shipping option for the shipping to the customer address - var shippingRateComputationMethods = _shippingService.LoadActiveShippingRateComputationMethods(_workContext.CurrentCustomer, _storeContext.CurrentStore.Id); - if (shippingRateComputationMethods.Any()) - { - var shippingOptionsResponse = _shippingService.GetShippingOptions(restoredCart, customer.ShippingAddress, _workContext.CurrentCustomer, storeId: _storeContext.CurrentStore.Id); - if (shippingOptionsResponse.Success) - { - var shippingOption = shippingOptionsResponse.ShippingOptions.OrderBy(option => option.Rate).First(); - updatedOrder.ShippingRateComputationMethodSystemName = shippingOption.ShippingRateComputationMethodSystemName; - updatedOrder.ShippingMethod = shippingOption.Name; - updatedOrder.ShippingAddress = (Address)customer.ShippingAddress.Clone(); - shippingTotal = shippingOption.Rate; - } - else - updateOrderParameters.Warnings.AddRange(shippingOptionsResponse.Errors); - } - else - updateOrderParameters.Warnings.Add("Shipping rate computation method could not be loaded"); - } - } - - //additional shipping charge - shippingTotal += restoredCart.Where(item => item.IsShipEnabled && !item.IsFreeShipping).Sum(item => item.Product.AdditionalShippingCharge); - - //shipping discounts - List shippingTotalDiscounts; - shippingTotal -= GetShippingDiscount(customer, shippingTotal, out shippingTotalDiscounts); - if (shippingTotal < decimal.Zero) - shippingTotal = decimal.Zero; - - shippingTotalExclTax = _taxService.GetShippingPrice(shippingTotal, false, customer); - shippingTotalInclTax = _taxService.GetShippingPrice(shippingTotal, true, customer, out shippingTaxRate); - - //rounding - if (_shoppingCartSettings.RoundPricesDuringCalculation) - { - shippingTotalExclTax = RoundingHelper.RoundPrice(shippingTotalExclTax); - shippingTotalInclTax = RoundingHelper.RoundPrice(shippingTotalInclTax); - } - - //change shipping status - if (updatedOrder.ShippingStatus == ShippingStatus.ShippingNotRequired || updatedOrder.ShippingStatus == ShippingStatus.NotYetShipped) - updatedOrder.ShippingStatus = ShippingStatus.NotYetShipped; - else - updatedOrder.ShippingStatus = ShippingStatus.PartiallyShipped; - - foreach (var discount in shippingTotalDiscounts) - if (!updateOrderParameters.AppliedDiscounts.ContainsDiscount(discount)) - updateOrderParameters.AppliedDiscounts.Add(discount); - } - } - else - updatedOrder.ShippingStatus = ShippingStatus.ShippingNotRequired; - - updatedOrder.OrderShippingExclTax = shippingTotalExclTax; - updatedOrder.OrderShippingInclTax = shippingTotalInclTax; - - #endregion - - #region Tax rates - - var taxRates = new SortedDictionary(); - - //order subtotal taxes - var subTotalTax = decimal.Zero; - foreach (var kvp in subTotalTaxRates) - { - subTotalTax += kvp.Value; - if (kvp.Key > decimal.Zero && kvp.Value > decimal.Zero) - { - if (!taxRates.ContainsKey(kvp.Key)) - taxRates.Add(kvp.Key, kvp.Value); - else - taxRates[kvp.Key] = taxRates[kvp.Key] + kvp.Value; - } - } - - //shipping taxes - var shippingTax = decimal.Zero; - if (_taxSettings.ShippingIsTaxable) - { - shippingTax = shippingTotalInclTax - shippingTotalExclTax; - if (shippingTax < decimal.Zero) - shippingTax = decimal.Zero; - - if (shippingTaxRate > decimal.Zero && shippingTax > decimal.Zero) - { - if (!taxRates.ContainsKey(shippingTaxRate)) - taxRates.Add(shippingTaxRate, shippingTax); - else - taxRates[shippingTaxRate] = taxRates[shippingTaxRate] + shippingTax; - } - } - - //payment method additional fee tax - var paymentMethodAdditionalFeeTax = decimal.Zero; - if (_taxSettings.PaymentMethodAdditionalFeeIsTaxable) - { - paymentMethodAdditionalFeeTax = updatedOrder.PaymentMethodAdditionalFeeInclTax - updatedOrder.PaymentMethodAdditionalFeeExclTax; - if (paymentMethodAdditionalFeeTax < decimal.Zero) - paymentMethodAdditionalFeeTax = decimal.Zero; - - if (updatedOrder.PaymentMethodAdditionalFeeExclTax > decimal.Zero) - { - var paymentTaxRate = Math.Round(100 * paymentMethodAdditionalFeeTax / updatedOrder.PaymentMethodAdditionalFeeExclTax, 3); - if (paymentTaxRate > decimal.Zero && paymentMethodAdditionalFeeTax > decimal.Zero) - { - if (!taxRates.ContainsKey(paymentTaxRate)) - taxRates.Add(paymentTaxRate, paymentMethodAdditionalFeeTax); - else - taxRates[paymentTaxRate] = taxRates[paymentTaxRate] + paymentMethodAdditionalFeeTax; - } - } - } - - //add at least one tax rate (0%) - if (!taxRates.Any()) - taxRates.Add(decimal.Zero, decimal.Zero); - - //summarize taxes - var taxTotal = subTotalTax + shippingTax + paymentMethodAdditionalFeeTax; - if (taxTotal < decimal.Zero) - taxTotal = decimal.Zero; - - //round tax - if (_shoppingCartSettings.RoundPricesDuringCalculation) - taxTotal = RoundingHelper.RoundPrice(taxTotal); - - updatedOrder.OrderTax = taxTotal; - updatedOrder.TaxRates = taxRates.Aggregate(string.Empty, (current, next) => - string.Format("{0}{1}:{2}; ", current, next.Key.ToString(CultureInfo.InvariantCulture), next.Value.ToString(CultureInfo.InvariantCulture))); - - #endregion - - #region Total - - var total = (subTotalExclTax - discountAmountExclTax) + shippingTotalExclTax + updatedOrder.PaymentMethodAdditionalFeeExclTax + taxTotal; - - //get discounts for the order total - List orderAppliedDiscounts; - var discountAmountTotal = GetOrderTotalDiscount(customer, total, out orderAppliedDiscounts); - if (total < discountAmountTotal) - discountAmountTotal = total; - total -= discountAmountTotal; - - //applied giftcards - foreach (var giftCard in _giftCardService.GetAllGiftCards(usedWithOrderId: updatedOrder.Id)) - { - if (total > decimal.Zero) - { - var remainingAmount = giftCard.GiftCardUsageHistory.Where(history => history.UsedWithOrderId == updatedOrder.Id).Sum(history => history.UsedValue); - var amountCanBeUsed = total > remainingAmount ? remainingAmount : total; - total -= amountCanBeUsed; - } - } - - //reward points - var rewardPointsOfOrder = _rewardPointService.GetRewardPointsHistory(customer.Id, true).FirstOrDefault(history => history.UsedWithOrder == updatedOrder); - if (rewardPointsOfOrder != null) - { - var rewardPoints = -rewardPointsOfOrder.Points; - var rewardPointsAmount = ConvertRewardPointsToAmount(rewardPoints); - if (total < rewardPointsAmount) - { - rewardPoints = ConvertAmountToRewardPoints(total); - rewardPointsAmount = total; - } - if (total > decimal.Zero) - total -= rewardPointsAmount; - - //uncomment here for the return unused reward points if new order total less redeemed reward points amount - //if (rewardPoints < -rewardPointsOfOrder.Points) - // _rewardPointService.AddRewardPointsHistoryEntry(customer, -rewardPointsOfOrder.Points - rewardPoints, _storeContext.CurrentStore.Id, "Return unused reward points"); - - if (rewardPointsAmount != rewardPointsOfOrder.UsedAmount) - { - rewardPointsOfOrder.UsedAmount = rewardPointsAmount; - rewardPointsOfOrder.Points = -rewardPoints; - _rewardPointService.UpdateRewardPointsHistoryEntry(rewardPointsOfOrder); - } - } - - //rounding - if (total < decimal.Zero) - total = decimal.Zero; - if (_shoppingCartSettings.RoundPricesDuringCalculation) - total = RoundingHelper.RoundPrice(total); - - updatedOrder.OrderDiscount = discountAmountTotal; - updatedOrder.OrderTotal = total; - - foreach (var discount in orderAppliedDiscounts) - if (!updateOrderParameters.AppliedDiscounts.ContainsDiscount(discount)) - updateOrderParameters.AppliedDiscounts.Add(discount); - - #endregion - } - - /// - /// Gets shopping cart additional shipping charge - /// - /// Cart - /// Additional shipping charge - public virtual decimal GetShoppingCartAdditionalShippingCharge(IList cart) - { - decimal additionalShippingCharge = decimal.Zero; - - bool isFreeShipping = IsFreeShipping(cart); - if (isFreeShipping) - return decimal.Zero; - - foreach (var sci in cart) - if (sci.IsShipEnabled && !sci.IsFreeShipping) - additionalShippingCharge += sci.AdditionalShippingCharge; - - return additionalShippingCharge; - } - - /// - /// Gets a value indicating whether shipping is free - /// - /// Cart - /// Subtotal amount; pass null to calculate subtotal - /// A value indicating whether shipping is free - public virtual bool IsFreeShipping(IList cart, decimal? subTotal = null) - { - if (!cart.RequiresShipping()) - return true; - - //check whether customer is in a customer role with free shipping applied - var customer = cart.GetCustomer(); - if (customer != null && customer.CustomerRoles.Where(role => role.Active).Any(role => role.FreeShipping)) - return true; - - //check whether all shopping cart items are marked as free shipping - if (cart.All(item => item.IsShipEnabled && item.IsFreeShipping)) - return true; - - //free shipping over $X - if (_shippingSettings.FreeShippingOverXEnabled) - { - if (!subTotal.HasValue) - { - decimal discountAmount; - List appliedDiscounts; - decimal subTotalWithoutDiscount; - decimal subTotalWithDiscount; - GetShoppingCartSubTotal(cart, _shippingSettings.FreeShippingOverXIncludingTax, out discountAmount, - out appliedDiscounts, out subTotalWithoutDiscount, out subTotalWithDiscount); - subTotal = subTotalWithDiscount; - } - - //check whether we have subtotal enough to have free shipping - if (subTotal.Value > _shippingSettings.FreeShippingOverXValue) - return true; - } - - return false; - } - - /// - /// Adjust shipping rate (free shipping, additional charges, discounts) - /// - /// Shipping rate to adjust - /// Cart - /// Applied discounts - /// Adjusted shipping rate - public virtual decimal AdjustShippingRate(decimal shippingRate, - IList cart, out List appliedDiscounts) - { - appliedDiscounts = new List(); - - //free shipping - if (IsFreeShipping(cart)) - return decimal.Zero; - - //additional shipping charges - decimal additionalShippingCharge = GetShoppingCartAdditionalShippingCharge(cart); - var adjustedRate = shippingRate + additionalShippingCharge; - - //discount - var customer = cart.GetCustomer(); - decimal discountAmount = GetShippingDiscount(customer, adjustedRate, out appliedDiscounts); - adjustedRate = adjustedRate - discountAmount; - - if (adjustedRate < decimal.Zero) - adjustedRate = decimal.Zero; - - if (_shoppingCartSettings.RoundPricesDuringCalculation) - adjustedRate = RoundingHelper.RoundPrice(adjustedRate); - - return adjustedRate; - } - - /// - /// Gets shopping cart shipping total - /// - /// Cart - /// Shipping total - public virtual decimal? GetShoppingCartShippingTotal(IList cart) - { - bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; - return GetShoppingCartShippingTotal(cart, includingTax); - } - - /// - /// Gets shopping cart shipping total - /// - /// Cart - /// A value indicating whether calculated price should include tax - /// Shipping total - public virtual decimal? GetShoppingCartShippingTotal(IList cart, bool includingTax) - { - decimal taxRate; - return GetShoppingCartShippingTotal(cart, includingTax, out taxRate); - } - - /// - /// Gets shopping cart shipping total - /// - /// Cart - /// A value indicating whether calculated price should include tax - /// Applied tax rate - /// Shipping total - public virtual decimal? GetShoppingCartShippingTotal(IList cart, bool includingTax, - out decimal taxRate) - { - List appliedDiscounts; - return GetShoppingCartShippingTotal(cart, includingTax, out taxRate, out appliedDiscounts); - } - - /// - /// Gets shopping cart shipping total - /// - /// Cart - /// A value indicating whether calculated price should include tax - /// Applied tax rate - /// Applied discounts - /// Shipping total - public virtual decimal? GetShoppingCartShippingTotal(IList cart, bool includingTax, - out decimal taxRate, out List appliedDiscounts) - { - decimal? shippingTotal = null; - decimal? shippingTotalTaxed = null; - appliedDiscounts = new List(); - taxRate = decimal.Zero; - - var customer = cart.GetCustomer(); - - bool isFreeShipping = IsFreeShipping(cart); - if (isFreeShipping) - return decimal.Zero; - - ShippingOption shippingOption = null; - if (customer != null) - shippingOption = customer.GetAttribute(SystemCustomerAttributeNames.SelectedShippingOption, _genericAttributeService, _storeContext.CurrentStore.Id); - - if (shippingOption != null) - { - //use last shipping option (get from cache) - shippingTotal = AdjustShippingRate(shippingOption.Rate, cart, out appliedDiscounts); - } - else - { - //use fixed rate (if possible) - Address shippingAddress = null; - if (customer != null) - shippingAddress = customer.ShippingAddress; - - var shippingRateComputationMethods = _shippingService.LoadActiveShippingRateComputationMethods(_workContext.CurrentCustomer, _storeContext.CurrentStore.Id); - if (!shippingRateComputationMethods.Any() && !_shippingSettings.AllowPickUpInStore) - throw new NopException("Shipping rate computation method could not be loaded"); - - if (shippingRateComputationMethods.Count == 1) - { - var shippingRateComputationMethod = shippingRateComputationMethods[0]; - - bool shippingFromMultipleLocations; - var shippingOptionRequests = _shippingService.CreateShippingOptionRequests(cart, - shippingAddress, - _storeContext.CurrentStore.Id, - out shippingFromMultipleLocations); - decimal? fixedRate = null; - foreach (var shippingOptionRequest in shippingOptionRequests) - { - //calculate fixed rates for each request-package - var fixedRateTmp = shippingRateComputationMethod.GetFixedRate(shippingOptionRequest); - if (fixedRateTmp.HasValue) - { - if (!fixedRate.HasValue) - fixedRate = decimal.Zero; - - fixedRate += fixedRateTmp.Value; - } - } - - if (fixedRate.HasValue) - { - //adjust shipping rate - shippingTotal = AdjustShippingRate(fixedRate.Value, cart, out appliedDiscounts); - } - } - } - - if (shippingTotal.HasValue) - { - if (shippingTotal.Value < decimal.Zero) - shippingTotal = decimal.Zero; - - //round - if (_shoppingCartSettings.RoundPricesDuringCalculation) - shippingTotal = RoundingHelper.RoundPrice(shippingTotal.Value); - - shippingTotalTaxed = _taxService.GetShippingPrice(shippingTotal.Value, - includingTax, - customer, - out taxRate); - - //round - if (_shoppingCartSettings.RoundPricesDuringCalculation) - shippingTotalTaxed = RoundingHelper.RoundPrice(shippingTotalTaxed.Value); - } - - return shippingTotalTaxed; - } - - - - - - /// - /// Gets tax - /// - /// Shopping cart - /// A value indicating whether we should use payment method additional fee when calculating tax - /// Tax total - public virtual decimal GetTaxTotal(IList cart, bool usePaymentMethodAdditionalFee = true) - { - if (cart == null) - throw new ArgumentNullException("cart"); - - SortedDictionary taxRates; - return GetTaxTotal(cart, out taxRates, usePaymentMethodAdditionalFee); - } - - /// - /// Gets tax - /// - /// Shopping cart - /// Tax rates - /// A value indicating whether we should use payment method additional fee when calculating tax - /// Tax total - public virtual decimal GetTaxTotal(IList cart, - out SortedDictionary taxRates, bool usePaymentMethodAdditionalFee = true) - { - if (cart == null) - throw new ArgumentNullException("cart"); - - taxRates = new SortedDictionary(); - - var customer = cart.GetCustomer(); - string paymentMethodSystemName = ""; - if (customer != null) - { - paymentMethodSystemName = customer.GetAttribute( - SystemCustomerAttributeNames.SelectedPaymentMethod, - _genericAttributeService, - _storeContext.CurrentStore.Id); - } - - //order sub total (items + checkout attributes) - decimal subTotalTaxTotal = decimal.Zero; - decimal orderSubTotalDiscountAmount; - List orderSubTotalAppliedDiscounts; - decimal subTotalWithoutDiscountBase; - decimal subTotalWithDiscountBase; - SortedDictionary orderSubTotalTaxRates; - GetShoppingCartSubTotal(cart, false, - out orderSubTotalDiscountAmount, out orderSubTotalAppliedDiscounts, - out subTotalWithoutDiscountBase, out subTotalWithDiscountBase, - out orderSubTotalTaxRates); - foreach (KeyValuePair kvp in orderSubTotalTaxRates) - { - decimal taxRate = kvp.Key; - decimal taxValue = kvp.Value; - subTotalTaxTotal += taxValue; - - if (taxRate > decimal.Zero && taxValue > decimal.Zero) - { - if (!taxRates.ContainsKey(taxRate)) - taxRates.Add(taxRate, taxValue); - else - taxRates[taxRate] = taxRates[taxRate] + taxValue; - } - } - - //shipping - decimal shippingTax = decimal.Zero; - if (_taxSettings.ShippingIsTaxable) - { - decimal taxRate; - decimal? shippingExclTax = GetShoppingCartShippingTotal(cart, false, out taxRate); - decimal? shippingInclTax = GetShoppingCartShippingTotal(cart, true, out taxRate); - if (shippingExclTax.HasValue && shippingInclTax.HasValue) - { - shippingTax = shippingInclTax.Value - shippingExclTax.Value; - //ensure that tax is equal or greater than zero - if (shippingTax < decimal.Zero) - shippingTax = decimal.Zero; - - //tax rates - if (taxRate > decimal.Zero && shippingTax > decimal.Zero) - { - if (!taxRates.ContainsKey(taxRate)) - taxRates.Add(taxRate, shippingTax); - else - taxRates[taxRate] = taxRates[taxRate] + shippingTax; - } - } - } - - //payment method additional fee - decimal paymentMethodAdditionalFeeTax = decimal.Zero; - if (usePaymentMethodAdditionalFee && _taxSettings.PaymentMethodAdditionalFeeIsTaxable) - { - decimal taxRate; - decimal paymentMethodAdditionalFee = _paymentService.GetAdditionalHandlingFee(cart, paymentMethodSystemName); - decimal paymentMethodAdditionalFeeExclTax = _taxService.GetPaymentMethodAdditionalFee(paymentMethodAdditionalFee, false, customer, out taxRate); - decimal paymentMethodAdditionalFeeInclTax = _taxService.GetPaymentMethodAdditionalFee(paymentMethodAdditionalFee, true, customer, out taxRate); - - paymentMethodAdditionalFeeTax = paymentMethodAdditionalFeeInclTax - paymentMethodAdditionalFeeExclTax; - //ensure that tax is equal or greater than zero - if (paymentMethodAdditionalFeeTax < decimal.Zero) - paymentMethodAdditionalFeeTax = decimal.Zero; - - //tax rates - if (taxRate > decimal.Zero && paymentMethodAdditionalFeeTax > decimal.Zero) - { - if (!taxRates.ContainsKey(taxRate)) - taxRates.Add(taxRate, paymentMethodAdditionalFeeTax); - else - taxRates[taxRate] = taxRates[taxRate] + paymentMethodAdditionalFeeTax; - } - } - - //add at least one tax rate (0%) - if (!taxRates.Any()) - taxRates.Add(decimal.Zero, decimal.Zero); - - //summarize taxes - decimal taxTotal = subTotalTaxTotal + shippingTax + paymentMethodAdditionalFeeTax; - //ensure that tax is equal or greater than zero - if (taxTotal < decimal.Zero) - taxTotal = decimal.Zero; - //round tax - if (_shoppingCartSettings.RoundPricesDuringCalculation) - taxTotal = RoundingHelper.RoundPrice(taxTotal); - return taxTotal; - } - - - - - - /// - /// Gets shopping cart total - /// - /// Cart - /// A value indicating reward points should be used; null to detect current choice of the customer - /// A value indicating whether we should use payment method additional fee when calculating order total - /// Shopping cart total;Null if shopping cart total couldn't be calculated now - public virtual decimal? GetShoppingCartTotal(IList cart, - bool? useRewardPoints = null, bool usePaymentMethodAdditionalFee = true) - { - decimal discountAmount; - List appliedDiscounts; - int redeemedRewardPoints; - decimal redeemedRewardPointsAmount; - List appliedGiftCards; - return GetShoppingCartTotal(cart, - out discountAmount, - out appliedDiscounts, - out appliedGiftCards, - out redeemedRewardPoints, - out redeemedRewardPointsAmount, - useRewardPoints, - usePaymentMethodAdditionalFee); - } - - /// - /// Gets shopping cart total - /// - /// Cart - /// Applied gift cards - /// Applied discount amount - /// Applied discounts - /// Reward points to redeem - /// Reward points amount in primary store currency to redeem - /// A value indicating reward points should be used; null to detect current choice of the customer - /// A value indicating whether we should use payment method additional fee when calculating order total - /// Shopping cart total;Null if shopping cart total couldn't be calculated now - public virtual decimal? GetShoppingCartTotal(IList cart, - out decimal discountAmount, out List appliedDiscounts, - out List appliedGiftCards, - out int redeemedRewardPoints, out decimal redeemedRewardPointsAmount, - bool? useRewardPoints = null, bool usePaymentMethodAdditionalFee = true) - { - redeemedRewardPoints = 0; - redeemedRewardPointsAmount = decimal.Zero; - - var customer = cart.GetCustomer(); - string paymentMethodSystemName = ""; - if (customer != null) - { - paymentMethodSystemName = customer.GetAttribute( - SystemCustomerAttributeNames.SelectedPaymentMethod, - _genericAttributeService, - _storeContext.CurrentStore.Id); - } - - - //subtotal without tax - decimal orderSubTotalDiscountAmount; - List orderSubTotalAppliedDiscounts; - decimal subTotalWithoutDiscountBase; - decimal subTotalWithDiscountBase; - GetShoppingCartSubTotal(cart, false, - out orderSubTotalDiscountAmount, out orderSubTotalAppliedDiscounts, - out subTotalWithoutDiscountBase, out subTotalWithDiscountBase); - //subtotal with discount - decimal subtotalBase = subTotalWithDiscountBase; - - - - //shipping without tax - decimal? shoppingCartShipping = GetShoppingCartShippingTotal(cart, false); - - - - //payment method additional fee without tax - decimal paymentMethodAdditionalFeeWithoutTax = decimal.Zero; - if (usePaymentMethodAdditionalFee && !String.IsNullOrEmpty(paymentMethodSystemName)) - { - decimal paymentMethodAdditionalFee = _paymentService.GetAdditionalHandlingFee(cart, - paymentMethodSystemName); - paymentMethodAdditionalFeeWithoutTax = - _taxService.GetPaymentMethodAdditionalFee(paymentMethodAdditionalFee, - false, customer); - } - - - - - //tax - decimal shoppingCartTax = GetTaxTotal(cart, usePaymentMethodAdditionalFee); - - - - - //order total - decimal resultTemp = decimal.Zero; - resultTemp += subtotalBase; - if (shoppingCartShipping.HasValue) - { - resultTemp += shoppingCartShipping.Value; - } - resultTemp += paymentMethodAdditionalFeeWithoutTax; - resultTemp += shoppingCartTax; - if (_shoppingCartSettings.RoundPricesDuringCalculation) - resultTemp = RoundingHelper.RoundPrice(resultTemp); - - #region Order total discount - - discountAmount = GetOrderTotalDiscount(customer, resultTemp, out appliedDiscounts); - - //sub totals with discount - if (resultTemp < discountAmount) - discountAmount = resultTemp; - - //reduce subtotal - resultTemp -= discountAmount; - - if (resultTemp < decimal.Zero) - resultTemp = decimal.Zero; - if (_shoppingCartSettings.RoundPricesDuringCalculation) - resultTemp = RoundingHelper.RoundPrice(resultTemp); - - #endregion - - #region Applied gift cards - - //let's apply gift cards now (gift cards that can be used) - appliedGiftCards = new List(); - if (!cart.IsRecurring()) - { - //we don't apply gift cards for recurring products - var giftCards = _giftCardService.GetActiveGiftCardsAppliedByCustomer(customer); - if (giftCards != null) - foreach (var gc in giftCards) - if (resultTemp > decimal.Zero) - { - decimal remainingAmount = gc.GetGiftCardRemainingAmount(); - decimal amountCanBeUsed = resultTemp > remainingAmount ? - remainingAmount : - resultTemp; - - //reduce subtotal - resultTemp -= amountCanBeUsed; - - var appliedGiftCard = new AppliedGiftCard(); - appliedGiftCard.GiftCard = gc; - appliedGiftCard.AmountCanBeUsed = amountCanBeUsed; - appliedGiftCards.Add(appliedGiftCard); - } - } - - #endregion - - if (resultTemp < decimal.Zero) - resultTemp = decimal.Zero; - if (_shoppingCartSettings.RoundPricesDuringCalculation) - resultTemp = RoundingHelper.RoundPrice(resultTemp); - - if (!shoppingCartShipping.HasValue) - { - //we have errors - return null; - } - - decimal orderTotal = resultTemp; - - #region Reward points - - if (_rewardPointsSettings.Enabled) - { - if (!useRewardPoints.HasValue) - useRewardPoints = customer.GetAttribute(SystemCustomerAttributeNames.UseRewardPointsDuringCheckout, _genericAttributeService, _storeContext.CurrentStore.Id); - if (useRewardPoints.Value) - { - - int rewardPointsBalance = _rewardPointService.GetRewardPointsBalance(customer.Id, _storeContext.CurrentStore.Id); - if (CheckMinimumRewardPointsToUseRequirement(rewardPointsBalance)) - { - decimal rewardPointsBalanceAmount = ConvertRewardPointsToAmount(rewardPointsBalance); - if (orderTotal > decimal.Zero) - { - if (orderTotal > rewardPointsBalanceAmount) - { - redeemedRewardPoints = rewardPointsBalance; - redeemedRewardPointsAmount = rewardPointsBalanceAmount; - } - else - { - redeemedRewardPointsAmount = orderTotal; - redeemedRewardPoints = ConvertAmountToRewardPoints(redeemedRewardPointsAmount); - } - } - } - } - } - - #endregion - - orderTotal = orderTotal - redeemedRewardPointsAmount; - if (_shoppingCartSettings.RoundPricesDuringCalculation) - orderTotal = RoundingHelper.RoundPrice(orderTotal); - return orderTotal; - } - - - - - - /// - /// Converts existing reward points to amount - /// - /// Reward points - /// Converted value - public virtual decimal ConvertRewardPointsToAmount(int rewardPoints) - { - if (rewardPoints <= 0) - return decimal.Zero; - - var result = rewardPoints * _rewardPointsSettings.ExchangeRate; - if (_shoppingCartSettings.RoundPricesDuringCalculation) - result = RoundingHelper.RoundPrice(result); - return result; - } - - /// - /// Converts an amount to reward points - /// - /// Amount - /// Converted value - public virtual int ConvertAmountToRewardPoints(decimal amount) - { - int result = 0; - if (amount <= 0) - return 0; - - if (_rewardPointsSettings.ExchangeRate > 0) - result = (int)Math.Ceiling(amount / _rewardPointsSettings.ExchangeRate); - return result; - } - - /// - /// Gets a value indicating whether a customer has minimum amount of reward points to use (if enabled) - /// - /// Reward points to check - /// true - reward points could use; false - cannot be used. - public virtual bool CheckMinimumRewardPointsToUseRequirement(int rewardPoints) - { - if (_rewardPointsSettings.MinimumRewardPointsToUse <= 0) - return true; - - return rewardPoints >= _rewardPointsSettings.MinimumRewardPointsToUse; - } - - /// - /// Calculate how order total (maximum amount) for which reward points could be earned/reduced - /// - /// Order shipping (including tax) - /// Order total - /// Applicable order total - public virtual decimal CalculateApplicableOrderTotalForRewardPoints(decimal orderShippingInclTax, decimal orderTotal) - { - //do you give reward points for order total? or do you exclude shipping? - //since shipping costs vary some of store owners don't give reward points based on shipping total - //you can put your custom logic here - return orderTotal - orderShippingInclTax; - } - /// - /// Calculate how much reward points will be earned/reduced based on certain amount spent - /// - /// Customer - /// Amount (in primary store currency) - /// Number of reward points - public virtual int CalculateRewardPoints(Customer customer, decimal amount) - { - if (!_rewardPointsSettings.Enabled) - return 0; - - if (_rewardPointsSettings.PointsForPurchases_Amount <= decimal.Zero) - return 0; - - //ensure that reward points are applied only to registered users - if (customer == null || customer.IsGuest()) - return 0; - - var points = (int)Math.Truncate(amount / _rewardPointsSettings.PointsForPurchases_Amount * _rewardPointsSettings.PointsForPurchases_Points); - return points; - } - - #endregion - } -} +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Nop.Core; +using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Common; +using Nop.Core.Domain.Customers; +using Nop.Core.Domain.Discounts; +using Nop.Core.Domain.Orders; +using Nop.Core.Domain.Shipping; +using Nop.Core.Domain.Tax; +using Nop.Services.Catalog; +using Nop.Services.Common; +using Nop.Services.Discounts; +using Nop.Services.Payments; +using Nop.Services.Shipping; +using Nop.Services.Tax; +using System.Diagnostics; + +namespace Nop.Services.Orders +{ + /// + /// Order service + /// + public partial class OrderTotalCalculationService : IOrderTotalCalculationService + { + #region Fields + + private readonly IWorkContext _workContext; + private readonly IStoreContext _storeContext; + private readonly IPriceCalculationService _priceCalculationService; + private readonly ITaxService _taxService; + private readonly IShippingService _shippingService; + private readonly IPaymentService _paymentService; + private readonly ICheckoutAttributeParser _checkoutAttributeParser; + private readonly IDiscountService _discountService; + private readonly IGiftCardService _giftCardService; + private readonly IGenericAttributeService _genericAttributeService; + private readonly IRewardPointService _rewardPointService; + private readonly TaxSettings _taxSettings; + private readonly RewardPointsSettings _rewardPointsSettings; + private readonly ShippingSettings _shippingSettings; + private readonly ShoppingCartSettings _shoppingCartSettings; + private readonly CatalogSettings _catalogSettings; + #endregion + + #region Ctor + + /// + /// Ctor + /// + /// Work context + /// Store context + /// Price calculation service + /// Tax service + /// Shipping service + /// Payment service + /// Checkout attribute parser + /// Discount service + /// Gift card service + /// Generic attribute service + /// Reward point service + /// Tax settings + /// Reward points settings + /// Shipping settings + /// Shopping cart settings + /// Catalog settings + public OrderTotalCalculationService(IWorkContext workContext, + IStoreContext storeContext, + IPriceCalculationService priceCalculationService, + ITaxService taxService, + IShippingService shippingService, + IPaymentService paymentService, + ICheckoutAttributeParser checkoutAttributeParser, + IDiscountService discountService, + IGiftCardService giftCardService, + IGenericAttributeService genericAttributeService, + IRewardPointService rewardPointService, + TaxSettings taxSettings, + RewardPointsSettings rewardPointsSettings, + ShippingSettings shippingSettings, + ShoppingCartSettings shoppingCartSettings, + CatalogSettings catalogSettings) + { + this._workContext = workContext; + this._storeContext = storeContext; + this._priceCalculationService = priceCalculationService; + this._taxService = taxService; + this._shippingService = shippingService; + this._paymentService = paymentService; + this._checkoutAttributeParser = checkoutAttributeParser; + this._discountService = discountService; + this._giftCardService = giftCardService; + this._genericAttributeService = genericAttributeService; + this._rewardPointService = rewardPointService; + this._taxSettings = taxSettings; + this._rewardPointsSettings = rewardPointsSettings; + this._shippingSettings = shippingSettings; + this._shoppingCartSettings = shoppingCartSettings; + this._catalogSettings = catalogSettings; + } + + #endregion + + #region Utilities + + /// + /// Gets an order discount (applied to order subtotal) + /// + /// Customer + /// Order subtotal + /// Applied discounts + /// Order discount + protected virtual decimal GetOrderSubtotalDiscount(Customer customer, + decimal orderSubTotal, out List appliedDiscounts) + { + appliedDiscounts = new List(); + decimal discountAmount = decimal.Zero; + if (_catalogSettings.IgnoreDiscounts) + return discountAmount; + + var allDiscounts = _discountService.GetAllDiscountsForCaching(DiscountType.AssignedToOrderSubTotal); + var allowedDiscounts = new List(); + if (allDiscounts != null) + foreach (var discount in allDiscounts) + if (_discountService.ValidateDiscount(discount, customer).IsValid && + !allowedDiscounts.ContainsDiscount(discount)) + { + allowedDiscounts.Add(discount); + } + + appliedDiscounts = allowedDiscounts.GetPreferredDiscount(orderSubTotal, out discountAmount); + + if (discountAmount < decimal.Zero) + discountAmount = decimal.Zero; + + return discountAmount; + } + + /// + /// Gets a shipping discount + /// + /// Customer + /// Shipping total + /// Applied discounts + /// Shipping discount + protected virtual decimal GetShippingDiscount(Customer customer, decimal shippingTotal, out List appliedDiscounts) + { + appliedDiscounts = new List(); + decimal shippingDiscountAmount = decimal.Zero; + if (_catalogSettings.IgnoreDiscounts) + return shippingDiscountAmount; + + var allDiscounts = _discountService.GetAllDiscountsForCaching(DiscountType.AssignedToShipping); + var allowedDiscounts = new List(); + if (allDiscounts != null) + foreach (var discount in allDiscounts) + if (_discountService.ValidateDiscount(discount, customer).IsValid && + !allowedDiscounts.ContainsDiscount(discount)) + { + allowedDiscounts.Add(discount); + } + + appliedDiscounts = allowedDiscounts.GetPreferredDiscount(shippingTotal, out shippingDiscountAmount); + + if (shippingDiscountAmount < decimal.Zero) + shippingDiscountAmount = decimal.Zero; + + if (_shoppingCartSettings.RoundPricesDuringCalculation) + shippingDiscountAmount = RoundingHelper.RoundAmount(shippingDiscountAmount); + + return shippingDiscountAmount; + } + + /// + /// Gets an order discount (applied to order total) + /// + /// Customer + /// Order total + /// Applied discounts + /// Order discount + protected virtual decimal GetOrderTotalDiscount(Customer customer, decimal orderTotal, out List appliedDiscounts) + { + appliedDiscounts = new List(); + decimal discountAmount = decimal.Zero; + if (_catalogSettings.IgnoreDiscounts) + return discountAmount; + + var allDiscounts = _discountService.GetAllDiscountsForCaching(DiscountType.AssignedToOrderTotal); + var allowedDiscounts = new List(); + if (allDiscounts != null) + foreach (var discount in allDiscounts) + if (_discountService.ValidateDiscount(discount, customer).IsValid && + !allowedDiscounts.ContainsDiscount(discount)) + { + allowedDiscounts.Add(discount); + } + + appliedDiscounts = allowedDiscounts.GetPreferredDiscount(orderTotal, out discountAmount); + + if (discountAmount < decimal.Zero) + discountAmount = decimal.Zero; + + if (_shoppingCartSettings.RoundPricesDuringCalculation) + discountAmount = RoundingHelper.RoundAmount(discountAmount); + + return discountAmount; + } + + #endregion + + #region Methods + + /// + /// Gets shopping cart subtotal + /// + /// Cart + /// A value indicating whether submitted prices do include tax + /// Applied discount amount + /// Applied discounts + /// Sub total (without discount) + /// Sub total (with discount) + public virtual void GetShoppingCartSubTotal(IList cart, + bool includingTax, + out decimal discountAmount, out List appliedDiscounts, + out decimal subTotalWithoutDiscount, out decimal subTotalWithDiscount) + { + TaxSummary taxSummary; + GetShoppingCartSubTotal(cart, includingTax, + out discountAmount, out appliedDiscounts, + out subTotalWithoutDiscount, out subTotalWithDiscount, out taxSummary); + } + + /// + /// Gets shopping cart subtotal + /// + /// Cart + /// A value indicating whether submitted prices do include tax + /// Applied discount amount + /// Applied discounts + /// Sub total (without discount) + /// Sub total (with discount) + /// Tax rates summary (of order sub total) + public virtual void GetShoppingCartSubTotal(IList cart, + bool includingTax, + out decimal discountAmount, out List appliedDiscounts, + out decimal subTotalWithoutDiscount, out decimal subTotalWithDiscount, + out TaxSummary taxSummary) + { + taxSummary = new TaxSummary(includingTax); + discountAmount = decimal.Zero; + appliedDiscounts = new List(); + subTotalWithoutDiscount = decimal.Zero; + subTotalWithDiscount = decimal.Zero; + + decimal subTotalAmount = decimal.Zero; + + if (!cart.Any()) + return; + + //get the customer + Customer customer = cart.GetCustomer(); + + //sub totals + foreach (var shoppingCartItem in cart) + { + decimal itemAmount = decimal.Zero; + decimal taxRate; + + //restored cartitem + if (shoppingCartItem.VatRate.HasValue) + { + itemAmount = includingTax ? shoppingCartItem.SubTotalInclTax ?? 0 : shoppingCartItem.SubTotalExclTax ?? 0; + taxRate = shoppingCartItem.VatRate ?? 0; + subTotalAmount += itemAmount; + } + //normal item + else + { + decimal sciSubTotal = _priceCalculationService.GetSubTotal(shoppingCartItem); + itemAmount = _taxService.GetProductPrice(shoppingCartItem.Product, sciSubTotal, includingTax, customer, out taxRate); + subTotalAmount += itemAmount; + } + //tax rates + if (itemAmount > decimal.Zero) //MF 22.11.16 taxRate > decimal.Zero &&: VatBase is need also when tax is 0 to show up in summary + taxSummary.AddRate(taxRate, itemAmount); + } + + //checkout attributes + if (customer != null) + { + var checkoutAttributesXml = customer.GetAttribute(SystemCustomerAttributeNames.CheckoutAttributes, _genericAttributeService, _storeContext.CurrentStore.Id); + var attributeValues = _checkoutAttributeParser.ParseCheckoutAttributeValues(checkoutAttributesXml); + if (attributeValues != null && attributeValues.Any()) //MF 16.11.16 any + { + foreach (var attributeValue in attributeValues) + { + decimal taxRate; + decimal itemAmount = _taxService.GetCheckoutAttributePrice(attributeValue, includingTax, customer, out taxRate); + subTotalAmount += itemAmount; + //tax rates + if (itemAmount > decimal.Zero) //taxRate > decimal.Zero && + taxSummary.AddRate(taxRate, itemAmount); + } + } + } + + //subtotal discount + var subDisc = GetOrderSubtotalDiscount(customer, subTotalAmount, out appliedDiscounts); + if (subDisc != decimal.Zero) + taxSummary.SetSubtotalDiscAmount(subDisc); + + //overall totals + taxSummary.CalculateAmounts(); + discountAmount = taxSummary.TotalSubTotalDiscAmount; + subTotalWithoutDiscount = taxSummary.TotalSubTotalAmount; + subTotalWithDiscount = taxSummary.TotalAmountIncludingVAT; + + if (subTotalWithDiscount < decimal.Zero) + subTotalWithDiscount = decimal.Zero; + + if (subTotalWithoutDiscount < decimal.Zero) + subTotalWithoutDiscount = decimal.Zero; + + } + + /// + /// Update order totals + /// + /// Parameters for the updating order + /// Shopping cart + public virtual void UpdateOrderTotals(UpdateOrderParameters updateOrderParameters, IList restoredCart) + { + var updatedOrder = updateOrderParameters.UpdatedOrder; + var updatedOrderItem = updateOrderParameters.UpdatedOrderItem; + + //get the customer + var customer = restoredCart.GetCustomer(); + + + bool includingTax = updatedOrder.CustomerTaxDisplayType == TaxDisplayType.IncludingTax; + List orderAppliedDiscounts; + List subTotalAppliedDiscounts; + List shippingAppliedDiscounts; + TaxSummary taxSummaryIncl; + TaxSummary taxSummaryExcl; + + decimal shoppingCartTax = GetTaxTotal(restoredCart, false, out taxSummaryExcl, out orderAppliedDiscounts, out subTotalAppliedDiscounts, out shippingAppliedDiscounts); + shoppingCartTax = GetTaxTotal(restoredCart, true, out taxSummaryIncl, out orderAppliedDiscounts, out subTotalAppliedDiscounts, out shippingAppliedDiscounts); + + #region discounts + foreach (var discount in orderAppliedDiscounts) + if (!updateOrderParameters.AppliedDiscounts.ContainsDiscount(discount)) + updateOrderParameters.AppliedDiscounts.Add(discount); + + foreach (var discount in subTotalAppliedDiscounts) + if (!updateOrderParameters.AppliedDiscounts.ContainsDiscount(discount)) + updateOrderParameters.AppliedDiscounts.Add(discount); + + foreach (var discount in shippingAppliedDiscounts) + if (!updateOrderParameters.AppliedDiscounts.ContainsDiscount(discount)) + updateOrderParameters.AppliedDiscounts.Add(discount); + + #endregion + + #region order + + updatedOrder.OrderSubtotalExclTax = taxSummaryExcl.TotalSubTotalAmount; + updatedOrder.OrderSubtotalInclTax = taxSummaryIncl.TotalSubTotalAmount; + updatedOrder.OrderSubTotalDiscountExclTax = taxSummaryExcl.TotalSubTotalDiscAmount; + updatedOrder.OrderSubTotalDiscountInclTax = taxSummaryIncl.TotalSubTotalDiscAmount; + updatedOrder.OrderShippingExclTax = taxSummaryExcl.TotalShippingAmount; + updatedOrder.OrderShippingInclTax = taxSummaryIncl.TotalShippingAmount; + updatedOrder.OrderTax = shoppingCartTax; + updatedOrder.OrderDiscount = includingTax ? taxSummaryIncl.TotalInvDiscAmount : taxSummaryExcl.TotalInvDiscAmount; + updatedOrder.OrderAmount = includingTax ? taxSummaryIncl.TotalAmount : taxSummaryExcl.TotalAmount; + updatedOrder.OrderAmountIncl = includingTax ? taxSummaryIncl.TotalAmountIncludingVAT : taxSummaryExcl.TotalAmountIncludingVAT; + + var total = includingTax ? taxSummaryIncl.TotalAmountIncludingVAT : taxSummaryExcl.TotalAmountIncludingVAT; //gets updated later on + + //get shopping cart item which has been updated + var updatedShoppingCartItem = restoredCart.FirstOrDefault(shoppingCartItem => shoppingCartItem.Id == updatedOrderItem.Id); + var itemDeleted = updatedShoppingCartItem == null; + if (!itemDeleted) + { + //update order item + updatedOrderItem.UnitPriceExclTax = updateOrderParameters.PriceExclTax; + updatedOrderItem.UnitPriceInclTax = updateOrderParameters.PriceInclTax; + updatedOrderItem.DiscountAmountExclTax = updateOrderParameters.DiscountAmountExclTax; + updatedOrderItem.DiscountAmountInclTax = updateOrderParameters.DiscountAmountInclTax; + updatedOrderItem.PriceExclTax = updateOrderParameters.SubTotalExclTax; + updatedOrderItem.PriceInclTax = updateOrderParameters.SubTotalInclTax; + updatedOrderItem.Quantity = restoredCart.FirstOrDefault(item => item.Id == updatedOrderItem.Id).Quantity; + updatedOrderItem.VatRate = restoredCart.FirstOrDefault(item => item.Id == updatedOrderItem.Id).VatRate ?? 0; + } + #endregion + + #region Shipping + + if (restoredCart.RequiresShipping()) + { + if (!IsFreeShipping(restoredCart, _shippingSettings.FreeShippingOverXIncludingTax ? taxSummaryIncl.TotalSubTotalAmount : taxSummaryExcl.TotalSubTotalAmount)) + { + if (!string.IsNullOrEmpty(updatedOrder.ShippingRateComputationMethodSystemName)) + { + //in the updated order were shipping items + if (updatedOrder.PickUpInStore) + { + //customer chose pickup in store method, try to get chosen pickup point + if (_shippingSettings.AllowPickUpInStore) + { + var pickupPointsResponse = _shippingService.GetPickupPoints(updatedOrder.BillingAddress, updatedOrder.Customer, + updatedOrder.ShippingRateComputationMethodSystemName, _storeContext.CurrentStore.Id); + if (pickupPointsResponse.Success) + { + var selectedPickupPoint = pickupPointsResponse.PickupPoints.FirstOrDefault(point => updatedOrder.ShippingMethod.Contains(point.Name)); + if (selectedPickupPoint == null) + updateOrderParameters.Warnings.Add(string.Format("Shipping method {0} could not be loaded", updatedOrder.ShippingMethod)); + } + else + updateOrderParameters.Warnings.AddRange(pickupPointsResponse.Errors); + } + else + updateOrderParameters.Warnings.Add("Pick up in store is not available"); + } + else + { + //customer chose shipping to address, try to get chosen shipping option + var shippingOptionsResponse = _shippingService.GetShippingOptions(restoredCart, + updatedOrder.ShippingAddress, updatedOrder.Customer, updatedOrder.ShippingRateComputationMethodSystemName, _storeContext.CurrentStore.Id); + if (shippingOptionsResponse.Success) + { + var shippingOption = shippingOptionsResponse.ShippingOptions.FirstOrDefault(option => updatedOrder.ShippingMethod.Contains(option.Name)); + if (shippingOption == null) + updateOrderParameters.Warnings.Add(string.Format("Shipping method {0} could not be loaded", updatedOrder.ShippingMethod)); + } + else + updateOrderParameters.Warnings.AddRange(shippingOptionsResponse.Errors); + } + } + else + { + //before updating order was without shipping + if (_shippingSettings.AllowPickUpInStore) + { + //try to get the cheapest pickup point + var pickupPointsResponse = _shippingService.GetPickupPoints(updatedOrder.BillingAddress, _workContext.CurrentCustomer, storeId: _storeContext.CurrentStore.Id); + if (pickupPointsResponse.Success) + { + updateOrderParameters.PickupPoint = pickupPointsResponse.PickupPoints.OrderBy(point => point.PickupFee).First(); + } + else + updateOrderParameters.Warnings.AddRange(pickupPointsResponse.Errors); + } + else + updateOrderParameters.Warnings.Add("Pick up in store is not available"); + + if (updateOrderParameters.PickupPoint == null) + { + //or try to get the cheapest shipping option for the shipping to the customer address + var shippingRateComputationMethods = _shippingService.LoadActiveShippingRateComputationMethods(_workContext.CurrentCustomer, _storeContext.CurrentStore.Id); + if (shippingRateComputationMethods.Any()) + { + var shippingOptionsResponse = _shippingService.GetShippingOptions(restoredCart, customer.ShippingAddress, _workContext.CurrentCustomer, storeId: _storeContext.CurrentStore.Id); + if (shippingOptionsResponse.Success) + { + var shippingOption = shippingOptionsResponse.ShippingOptions.OrderBy(option => option.Rate).First(); + updatedOrder.ShippingRateComputationMethodSystemName = shippingOption.ShippingRateComputationMethodSystemName; + updatedOrder.ShippingMethod = shippingOption.Name; + updatedOrder.ShippingAddress = (Address)customer.ShippingAddress.Clone(); + } + else + updateOrderParameters.Warnings.AddRange(shippingOptionsResponse.Errors); + } + else + updateOrderParameters.Warnings.Add("Shipping rate computation method could not be loaded"); + } + } + + //change shipping status + if (updatedOrder.ShippingStatus == ShippingStatus.ShippingNotRequired || updatedOrder.ShippingStatus == ShippingStatus.NotYetShipped) + updatedOrder.ShippingStatus = ShippingStatus.NotYetShipped; + else + updatedOrder.ShippingStatus = ShippingStatus.PartiallyShipped; + } + } + else + updatedOrder.ShippingStatus = ShippingStatus.ShippingNotRequired; + + + + #endregion + + #region Tax rates + //tax rates + var taxrates = includingTax ? taxSummaryIncl : taxSummaryExcl; + updatedOrder.TaxRates = taxrates.TaxRates.Aggregate(string.Empty, (current, next) => + string.Format("{0}{1}:{2}:{3}:{4}:{5}:{6}; ", current, + next.Key.ToString(CultureInfo.InvariantCulture), + (next.Value.SubtotalAmount + next.Value.ShippingAmount + next.Value.PaymentFeeAmount).ToString(CultureInfo.InvariantCulture), + (next.Value.SubTotalDiscAmount + next.Value.InvoiceDiscountAmount).ToString(CultureInfo.InvariantCulture), + next.Value.BaseAmount.ToString(CultureInfo.InvariantCulture), + next.Value.VatAmount.ToString(CultureInfo.InvariantCulture), + next.Value.AmountIncludingVAT.ToString(CultureInfo.InvariantCulture) + ) + ); + + #endregion + + #region total + + //applied giftcards + foreach (var giftCard in _giftCardService.GetAllGiftCards(usedWithOrderId: updatedOrder.Id)) + { + if (total > decimal.Zero) + { + var remainingAmount = giftCard.GiftCardUsageHistory.Where(history => history.UsedWithOrderId == updatedOrder.Id).Sum(history => history.UsedValue); + var amountCanBeUsed = total > remainingAmount ? remainingAmount : total; + total -= amountCanBeUsed; + } + } + + //reward points + var rewardPointsOfOrder = _rewardPointService.GetRewardPointsHistory(customer.Id, true).FirstOrDefault(history => history.UsedWithOrder == updatedOrder); + if (rewardPointsOfOrder != null) + { + var rewardPoints = -rewardPointsOfOrder.Points; + var rewardPointsAmount = ConvertRewardPointsToAmount(rewardPoints); + if (total < rewardPointsAmount) + { + rewardPoints = ConvertAmountToRewardPoints(total); + rewardPointsAmount = total; + } + if (total > decimal.Zero) + total -= rewardPointsAmount; + + //uncomment here for the return unused reward points if new order total less redeemed reward points amount + if (rewardPoints < -rewardPointsOfOrder.Points) + _rewardPointService.AddRewardPointsHistoryEntry(customer, -rewardPointsOfOrder.Points - rewardPoints, _storeContext.CurrentStore.Id, "Return unused reward points"); + //comment end + + if (rewardPointsAmount != rewardPointsOfOrder.UsedAmount) + { + rewardPointsOfOrder.UsedAmount = rewardPointsAmount; + rewardPointsOfOrder.Points = -rewardPoints; + _rewardPointService.UpdateRewardPointsHistoryEntry(rewardPointsOfOrder); + } + } + + updatedOrder.OrderTotal = total; + #endregion + } + + /// + /// Gets shopping cart additional shipping charge + /// + /// Cart + /// Additional shipping charge + public virtual decimal GetShoppingCartAdditionalShippingCharge(IList cart) + { + decimal additionalShippingCharge = decimal.Zero; + + bool isFreeShipping = IsFreeShipping(cart); + if (isFreeShipping) + return decimal.Zero; + + foreach (var sci in cart) + if (sci.IsShipEnabled && !sci.IsFreeShipping) + additionalShippingCharge += sci.AdditionalShippingCharge; + + return additionalShippingCharge; + } + + /// + /// Gets a value indicating whether shipping is free + /// + /// Cart + /// Subtotal amount; pass null to calculate subtotal + /// A value indicating whether shipping is free + public virtual bool IsFreeShipping(IList cart, decimal? subTotal = null) + { + if (!cart.RequiresShipping()) + return true; + + //check whether customer is in a customer role with free shipping applied + var customer = cart.GetCustomer(); + if (customer != null && customer.CustomerRoles.Where(role => role.Active).Any(role => role.FreeShipping)) + return true; + + //check whether all shopping cart items are marked as free shipping + if (cart.All(item => item.IsShipEnabled && item.IsFreeShipping)) + return true; + + //free shipping over $X + if (_shippingSettings.FreeShippingOverXEnabled) + { + if (!subTotal.HasValue) + { + decimal discountAmount; + List appliedDiscounts; + decimal subTotalWithoutDiscount; + decimal subTotalWithDiscount; + GetShoppingCartSubTotal(cart, _shippingSettings.FreeShippingOverXIncludingTax, out discountAmount, + out appliedDiscounts, out subTotalWithoutDiscount, out subTotalWithDiscount); + subTotal = subTotalWithDiscount; + } + + //check whether we have subtotal enough to have free shipping + if (subTotal.Value > _shippingSettings.FreeShippingOverXValue) + return true; + } + + return false; + } + + /// + /// Adjust shipping rate (free shipping, additional charges, discounts) + /// + /// Shipping rate to adjust + /// Cart + /// Applied discounts + /// Adjusted shipping rate + public virtual decimal AdjustShippingRate(decimal shippingRate, + IList cart, out List appliedDiscounts) + { + appliedDiscounts = new List(); + + //free shipping + if (IsFreeShipping(cart)) + return decimal.Zero; + + //additional shipping charges + decimal additionalShippingCharge = GetShoppingCartAdditionalShippingCharge(cart); + var adjustedRate = shippingRate + additionalShippingCharge; + + //discount + var customer = cart.GetCustomer(); + decimal discountAmount = GetShippingDiscount(customer, adjustedRate, out appliedDiscounts); + adjustedRate = adjustedRate - discountAmount; + + if (adjustedRate < decimal.Zero) + adjustedRate = decimal.Zero; + + if (_shoppingCartSettings.RoundPricesDuringCalculation) + adjustedRate = RoundingHelper.RoundAmount(adjustedRate); + + return adjustedRate; + } + + /// + /// Gets shopping cart shipping total + /// + /// Cart + /// Shipping total + public virtual decimal? GetShoppingCartShippingTotal(IList cart) + { + bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; + return GetShoppingCartShippingTotal(cart, includingTax); + } + + /// + /// Gets shopping cart shipping total + /// + /// Cart + /// A value indicating whether submitted prices do include tax + /// Shipping total + public virtual decimal? GetShoppingCartShippingTotal(IList cart, bool includingTax) + { + decimal taxRate; + return GetShoppingCartShippingTotal(cart, includingTax, out taxRate); + } + + /// + /// Gets shopping cart shipping total + /// + /// Cart + /// A value indicating whether submitted prices do include tax + /// Applied tax rate + /// Shipping total + public virtual decimal? GetShoppingCartShippingTotal(IList cart, bool includingTax, + out decimal taxRate) + { + List appliedDiscounts; + return GetShoppingCartShippingTotal(cart, includingTax, out taxRate, out appliedDiscounts); + } + + /// + /// Gets shopping cart shipping total + /// + /// Cart + /// A value indicating whether submitted prices do include tax + /// Applied tax rate + /// Applied discounts + /// Shipping total + public virtual decimal? GetShoppingCartShippingTotal(IList cart, bool includingTax, + out decimal taxRate, out List appliedDiscounts) + { + decimal? shippingTotal = null; + decimal? shippingTotalTaxed = null; + appliedDiscounts = new List(); + taxRate = decimal.Zero; + + var customer = cart.GetCustomer(); + + bool isFreeShipping = IsFreeShipping(cart); + if (isFreeShipping) + return decimal.Zero; + + ShippingOption shippingOption = null; + if (customer != null) + shippingOption = customer.GetAttribute(SystemCustomerAttributeNames.SelectedShippingOption, _genericAttributeService, _storeContext.CurrentStore.Id); + + if (shippingOption != null) + { + //use last shipping option (get from cache) + shippingTotal = AdjustShippingRate(shippingOption.Rate, cart, out appliedDiscounts); + } + else + { + //use fixed rate (if possible) + Address shippingAddress = null; + if (customer != null) + shippingAddress = customer.ShippingAddress; + + var shippingRateComputationMethods = _shippingService.LoadActiveShippingRateComputationMethods(_workContext.CurrentCustomer, _storeContext.CurrentStore.Id); + if (!shippingRateComputationMethods.Any() && !_shippingSettings.AllowPickUpInStore) + throw new NopException("Shipping rate computation method could not be loaded"); + + if (shippingRateComputationMethods.Count == 1) + { + var shippingRateComputationMethod = shippingRateComputationMethods[0]; + + bool shippingFromMultipleLocations; + var shippingOptionRequests = _shippingService.CreateShippingOptionRequests(cart, + shippingAddress, + _storeContext.CurrentStore.Id, + out shippingFromMultipleLocations); + decimal? fixedRate = null; + foreach (var shippingOptionRequest in shippingOptionRequests) + { + //calculate fixed rates for each request-package + var fixedRateTmp = shippingRateComputationMethod.GetFixedRate(shippingOptionRequest); + if (fixedRateTmp.HasValue) + { + if (!fixedRate.HasValue) + fixedRate = decimal.Zero; + + fixedRate += fixedRateTmp.Value; + } + } + + if (fixedRate.HasValue) + { + //adjust shipping rate + shippingTotal = AdjustShippingRate(fixedRate.Value, cart, out appliedDiscounts); + } + } + } + + if (shippingTotal.HasValue) + { + if (shippingTotal.Value < decimal.Zero) + shippingTotal = decimal.Zero; + + //round + if (_shoppingCartSettings.RoundPricesDuringCalculation) + shippingTotal = RoundingHelper.RoundAmount(shippingTotal.Value); + + shippingTotalTaxed = _taxService.GetShippingPrice(shippingTotal.Value, + includingTax, + customer, + out taxRate); + + //round + if (_shoppingCartSettings.RoundPricesDuringCalculation) + shippingTotalTaxed = RoundingHelper.RoundAmount(shippingTotalTaxed.Value); + } + + return shippingTotalTaxed; + } + + + + + + /// + /// Gets tax + /// + /// Shopping cart + /// Tax Summary + /// A value indicating whether we should use payment method additional fee when calculating tax + /// Tax total + public virtual decimal GetTaxTotal(IList cart, out TaxSummary taxSummary, bool usePaymentMethodAdditionalFee = true) + { + if (cart == null) + throw new ArgumentNullException("cart"); + + List appliedDiscounts; + List subTotalAppliedDiscounts; + List shippingAppliedDiscounts; + bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; + return GetTaxTotal(cart, includingTax, out taxSummary, out appliedDiscounts, out subTotalAppliedDiscounts, out shippingAppliedDiscounts, usePaymentMethodAdditionalFee); + } + /// + /// Gets tax + /// + /// Shopping cart + /// A value indicating whether submitted prices do include tax + /// Tax rates summary + /// Applied invoice discounts + /// Applied subtotal discounts + /// Applied shipping discounts + /// A value indicating whether we should use payment method additional fee when calculating tax + /// Tax total + public virtual decimal GetTaxTotal(IList cart, + bool includingTax, + out TaxSummary taxSummary, + out List appliedDiscounts, + out List subTotalAppliedDiscounts, + out List shippingAppliedDiscounts, + bool usePaymentMethodAdditionalFee = true) + + { + if (cart == null) + throw new ArgumentNullException("cart"); + + var customer = cart.GetCustomer(); + string paymentMethodSystemName = ""; + if (customer != null) + { + paymentMethodSystemName = customer.GetAttribute( + SystemCustomerAttributeNames.SelectedPaymentMethod, + _genericAttributeService, + _storeContext.CurrentStore.Id); + } + + //order sub total (items + checkout attributes) + decimal orderSubTotalDiscountAmount; + decimal subTotalWithoutDiscountBase; + decimal subTotalWithDiscountBase; + + GetShoppingCartSubTotal(cart, includingTax, + out orderSubTotalDiscountAmount, out subTotalAppliedDiscounts, + out subTotalWithoutDiscountBase, out subTotalWithDiscountBase, + out taxSummary); + + decimal taxRate; + + //shipping + decimal? shippingAmount = GetShoppingCartShippingTotal(cart, includingTax, out taxRate, out shippingAppliedDiscounts); + if ((shippingAmount ?? 0) > decimal.Zero && _taxSettings.ShippingIsTaxable) + taxSummary.AddRate(taxRate, shippingAmount: shippingAmount ?? 0); + + //payment method additional fee + taxSummary.CalculateAmounts(); + decimal discountbaseFee = includingTax ? taxSummary.TotalAmountIncludingVAT : taxSummary.TotalAmount; + + if (usePaymentMethodAdditionalFee && !String.IsNullOrEmpty(paymentMethodSystemName)) + { + decimal paymentMethodAdditionalFee = _taxService.GetPaymentMethodAdditionalFee(_paymentService.GetAdditionalHandlingFee(cart, paymentMethodSystemName), includingTax, customer, out taxRate); + + if (paymentMethodAdditionalFee != decimal.Zero ) + //tfc case of taxable fee > 0 + if (paymentMethodAdditionalFee > decimal.Zero && _taxSettings.PaymentMethodAdditionalFeeIsTaxable) + taxSummary.AddRate(taxRate, paymentfeeAmount: paymentMethodAdditionalFee); + //tfc case fee < 0 is considered as discount or as surcharge when not taxable + else + taxSummary.SetPaymentFeeDiscAmount(paymentMethodAdditionalFee, discountbaseFee); + } + + //Order total invoice discount should be considered in tax calculation + taxSummary.CalculateAmounts(); + decimal discountbase = includingTax ? taxSummary.TotalAmountIncludingVAT : taxSummary.TotalAmount; + var ivDiscount = GetOrderTotalDiscount(customer, discountbase, out appliedDiscounts); + if (ivDiscount != decimal.Zero) + taxSummary.SetTotalInvDiscAmount(ivDiscount, discountbase); + + + //add at least one tax rate (0%) + if (!taxSummary.TaxRates.Any()) + taxSummary.AddRate(0, 0); + + taxSummary.CalculateAmounts(); + + //summarize taxes + decimal taxTotal = taxSummary.TotalAmountVAT; + //ensure that tax is equal or greater than zero + if (taxTotal < decimal.Zero) + taxTotal = decimal.Zero; + + return taxTotal; + } + + + + + + /// + /// Gets shopping cart total + /// + /// Cart + /// A value indicating reward points should be used; null to detect current choice of the customer + /// A value indicating whether we should use payment method additional fee when calculating order total + /// Shopping cart total;Null if shopping cart total couldn't be calculated now + public virtual decimal? GetShoppingCartTotal(IList cart, + bool? useRewardPoints = null, bool usePaymentMethodAdditionalFee = true) + { + decimal discountAmount; + List appliedDiscounts; + List subTotalAppliedDiscounts; + List shippingAppliedDiscounts; + int redeemedRewardPoints; + decimal redeemedRewardPointsAmount; + List appliedGiftCards; + TaxSummary taxSummary; + bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; + + return GetShoppingCartTotal(cart, + out discountAmount, + out appliedDiscounts, + out subTotalAppliedDiscounts, + out shippingAppliedDiscounts, + out appliedGiftCards, + out redeemedRewardPoints, + out redeemedRewardPointsAmount, + out taxSummary, + includingTax, + useRewardPoints, + usePaymentMethodAdditionalFee); + } + + /// + /// Gets shopping cart total + /// + /// Cart + /// Applied discount amount + /// Applied discounts + /// Applied subtotal discounts + /// Applied shipping discounts + /// Applied gift cards + /// Reward points to redeem + /// Reward points amount in primary store currency to redeem + /// Tax summary + /// A value indicating whether submitted prices do include tax + /// A value indicating reward points should be used; null to detect current choice of the customer + /// A value indicating whether we should use payment method additional fee when calculating order total + /// Shopping cart total;Null if shopping cart total couldn't be calculated now + public virtual decimal? GetShoppingCartTotal(IList cart, + out decimal discountAmount, out List appliedDiscounts, + out List subTotalAppliedDiscounts, + out List shippingAppliedDiscounts, + out List appliedGiftCards, + out int redeemedRewardPoints, out decimal redeemedRewardPointsAmount, + out TaxSummary taxSummary, + bool includingTax, + bool? useRewardPoints = null, bool usePaymentMethodAdditionalFee = true) + { + + redeemedRewardPoints = 0; + redeemedRewardPointsAmount = decimal.Zero; + + #region order totals and tax + //order totals and tax + decimal shoppingCartTax = GetTaxTotal(cart, includingTax, out taxSummary, out appliedDiscounts, out subTotalAppliedDiscounts, out shippingAppliedDiscounts, usePaymentMethodAdditionalFee); + decimal orderSubTotalDiscountAmount = taxSummary.TotalSubTotalDiscAmount; + decimal subTotalWithoutDiscountBase = taxSummary.TotalSubTotalAmount; + decimal subTotalWithDiscountBase = taxSummary.TotalAmountIncludingVAT; + + //shipping + decimal? shoppingCartShipping = taxSummary.TotalShippingAmount; + + //payment method additional fee + decimal paymentMethodAdditionalFee = taxSummary.TotalPaymentFeeAmount; + + //order total + decimal resultTemp = taxSummary.TotalAmountIncludingVAT; + if (resultTemp < decimal.Zero) + resultTemp = decimal.Zero; + if (_shoppingCartSettings.RoundPricesDuringCalculation) + resultTemp = RoundingHelper.RoundAmount(resultTemp); + + //Order total discount + //it has reduced vat + discountAmount = taxSummary.TotalInvDiscAmount; + + #endregion + + #region Applied gift cards + + var customer = cart.GetCustomer(); + //let's apply gift cards now (gift cards that can be used) + appliedGiftCards = new List(); + if (!cart.IsRecurring()) + { + //we don't apply gift cards for recurring products + var giftCards = _giftCardService.GetActiveGiftCardsAppliedByCustomer(customer); + if (giftCards != null) + foreach (var gc in giftCards) + if (resultTemp > decimal.Zero) + { + decimal remainingAmount = gc.GetGiftCardRemainingAmount(); + decimal amountCanBeUsed = resultTemp > remainingAmount ? + remainingAmount : + resultTemp; + + //reduce subtotal + resultTemp -= amountCanBeUsed; + + var appliedGiftCard = new AppliedGiftCard(); + appliedGiftCard.GiftCard = gc; + appliedGiftCard.AmountCanBeUsed = amountCanBeUsed; + appliedGiftCards.Add(appliedGiftCard); + } + } + + #endregion + + if (resultTemp < decimal.Zero) + resultTemp = decimal.Zero; + if (_shoppingCartSettings.RoundPricesDuringCalculation) + resultTemp = RoundingHelper.RoundAmount(resultTemp); + + if (!shoppingCartShipping.HasValue) + { + //we have errors + return null; + } + + decimal orderTotal = resultTemp; + + #region Reward points + + if (_rewardPointsSettings.Enabled) + { + if (!useRewardPoints.HasValue) + useRewardPoints = customer.GetAttribute(SystemCustomerAttributeNames.UseRewardPointsDuringCheckout, _genericAttributeService, _storeContext.CurrentStore.Id); + if (useRewardPoints.Value) + { + + int rewardPointsBalance = _rewardPointService.GetRewardPointsBalance(customer.Id, _storeContext.CurrentStore.Id); + if (CheckMinimumRewardPointsToUseRequirement(rewardPointsBalance)) + { + decimal rewardPointsBalanceAmount = ConvertRewardPointsToAmount(rewardPointsBalance); + if (orderTotal > decimal.Zero) + { + if (orderTotal > rewardPointsBalanceAmount) + { + redeemedRewardPoints = rewardPointsBalance; + redeemedRewardPointsAmount = rewardPointsBalanceAmount; + } + else + { + redeemedRewardPointsAmount = orderTotal; + redeemedRewardPoints = ConvertAmountToRewardPoints(redeemedRewardPointsAmount); + } + } + } + } + } + + #endregion + + orderTotal = orderTotal - redeemedRewardPointsAmount; + if (_shoppingCartSettings.RoundPricesDuringCalculation) + orderTotal = RoundingHelper.RoundAmount(orderTotal); + return orderTotal; + } + + + + + + /// + /// Converts existing reward points to amount + /// + /// Reward points + /// Converted value + public virtual decimal ConvertRewardPointsToAmount(int rewardPoints) + { + if (rewardPoints <= 0) + return decimal.Zero; + + var result = rewardPoints * _rewardPointsSettings.ExchangeRate; + if (_shoppingCartSettings.RoundPricesDuringCalculation) + result = RoundingHelper.RoundAmount(result); + return result; + } + + /// + /// Converts an amount to reward points + /// + /// Amount + /// Converted value + public virtual int ConvertAmountToRewardPoints(decimal amount) + { + int result = 0; + if (amount <= 0) + return 0; + + if (_rewardPointsSettings.ExchangeRate > 0) + result = (int)Math.Ceiling(amount / _rewardPointsSettings.ExchangeRate); + return result; + } + + /// + /// Gets a value indicating whether a customer has minimum amount of reward points to use (if enabled) + /// + /// Reward points to check + /// true - reward points could use; false - cannot be used. + public virtual bool CheckMinimumRewardPointsToUseRequirement(int rewardPoints) + { + if (_rewardPointsSettings.MinimumRewardPointsToUse <= 0) + return true; + + return rewardPoints >= _rewardPointsSettings.MinimumRewardPointsToUse; + } + + /// + /// Calculate how order total (maximum amount) for which reward points could be earned/reduced + /// + /// Order shipping (including tax) + /// Order total + /// Applicable order total + public virtual decimal CalculateApplicableOrderTotalForRewardPoints(decimal orderShippingInclTax, decimal orderTotal) + { + //do you give reward points for order total? or do you exclude shipping? + //since shipping costs vary some of store owners don't give reward points based on shipping total + //you can put your custom logic here + return orderTotal - orderShippingInclTax; + } + /// + /// Calculate how much reward points will be earned/reduced based on certain amount spent + /// + /// Customer + /// Amount (in primary store currency) + /// Number of reward points + public virtual int CalculateRewardPoints(Customer customer, decimal amount) + { + if (!_rewardPointsSettings.Enabled) + return 0; + + if (_rewardPointsSettings.PointsForPurchases_Amount <= decimal.Zero) + return 0; + + //ensure that reward points are applied only to registered users + if (customer == null || customer.IsGuest()) + return 0; + + var points = (int)Math.Truncate(amount / _rewardPointsSettings.PointsForPurchases_Amount * _rewardPointsSettings.PointsForPurchases_Points); + return points; + } + + #endregion + } +} diff --git a/src/Libraries/Nop.Services/Orders/UpdateOrderParameters.cs b/src/Libraries/Nop.Services/Orders/UpdateOrderParameters.cs index d4af994fb77..897b1266ad6 100644 --- a/src/Libraries/Nop.Services/Orders/UpdateOrderParameters.cs +++ b/src/Libraries/Nop.Services/Orders/UpdateOrderParameters.cs @@ -75,5 +75,7 @@ public UpdateOrderParameters() /// Pickup point /// public PickupPoint PickupPoint { get; set; } + + public decimal VatRate { get; set; } } } diff --git a/src/Libraries/Nop.Services/Payments/PaymentExtensions.cs b/src/Libraries/Nop.Services/Payments/PaymentExtensions.cs index abf7bb42c29..2ce5d878d38 100644 --- a/src/Libraries/Nop.Services/Payments/PaymentExtensions.cs +++ b/src/Libraries/Nop.Services/Payments/PaymentExtensions.cs @@ -1,208 +1,208 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Xml; -using System.Xml.Schema; -using System.Xml.Serialization; -using Nop.Core.Domain.Orders; -using Nop.Core.Domain.Payments; -using Nop.Services.Orders; - -namespace Nop.Services.Payments -{ - /// - /// Payment extensions - /// - public static class PaymentExtensions - { - /// - /// Is payment method active? - /// - /// Payment method - /// Payment settings - /// Result - public static bool IsPaymentMethodActive(this IPaymentMethod paymentMethod, - PaymentSettings paymentSettings) - { - if (paymentMethod == null) - throw new ArgumentNullException("paymentMethod"); - - if (paymentSettings == null) - throw new ArgumentNullException("paymentSettings"); - - if (paymentSettings.ActivePaymentMethodSystemNames == null) - return false; - foreach (string activeMethodSystemName in paymentSettings.ActivePaymentMethodSystemNames) - if (paymentMethod.PluginDescriptor.SystemName.Equals(activeMethodSystemName, StringComparison.InvariantCultureIgnoreCase)) - return true; - return false; - } - - /// - /// Calculate payment method fee - /// - /// Payment method - /// Order total calculation service - /// Shopping cart - /// Fee value - /// Is fee amount specified as percentage or fixed value? - /// Result - public static decimal CalculateAdditionalFee(this IPaymentMethod paymentMethod, - IOrderTotalCalculationService orderTotalCalculationService, IList cart, - decimal fee, bool usePercentage) - { - if (paymentMethod == null) - throw new ArgumentNullException("paymentMethod"); - if (fee <= 0) - return fee; - - decimal result; - if (usePercentage) - { - //percentage - var orderTotalWithoutPaymentFee = orderTotalCalculationService.GetShoppingCartTotal(cart, usePaymentMethodAdditionalFee: false); - result = (decimal)((((float)orderTotalWithoutPaymentFee) * ((float)fee)) / 100f); - } - else - { - //fixed value - result = fee; - } - return result; - } - - - /// - /// Serialize CustomValues of ProcessPaymentRequest - /// - /// Request - /// Serialized CustomValues - public static string SerializeCustomValues(this ProcessPaymentRequest request) - { - if (request == null) - throw new ArgumentNullException("request"); - - if (!request.CustomValues.Any()) - return null; - - //XmlSerializer won't serialize objects that implement IDictionary by default. - //http://msdn.microsoft.com/en-us/magazine/cc164135.aspx - - //also see http://ropox.ru/tag/ixmlserializable/ (Russian language) - - var ds = new DictionarySerializer(request.CustomValues); - var xs = new XmlSerializer(typeof(DictionarySerializer)); - - using (var textWriter = new StringWriter()) - { - using (var xmlWriter = XmlWriter.Create(textWriter)) - { - xs.Serialize(xmlWriter, ds); - } - var result = textWriter.ToString(); - return result; - } - } - /// - /// Deerialize CustomValues of Order - /// - /// Order - /// Serialized CustomValues CustomValues - public static Dictionary DeserializeCustomValues(this Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - var request = new ProcessPaymentRequest(); - return request.DeserializeCustomValues(order.CustomValuesXml); - } - /// - /// Deerialize CustomValues of ProcessPaymentRequest - /// - /// Request - /// Serialized CustomValues - /// Serialized CustomValues CustomValues - public static Dictionary DeserializeCustomValues(this ProcessPaymentRequest request, string customValuesXml) - { - if (string.IsNullOrWhiteSpace(customValuesXml)) - { - return new Dictionary(); - } - - var serializer = new XmlSerializer(typeof(DictionarySerializer)); - - using (var textReader = new StringReader(customValuesXml)) - { - using (var xmlReader = XmlReader.Create(textReader)) - { - var ds = serializer.Deserialize(xmlReader) as DictionarySerializer; - if (ds != null) - return ds.Dictionary; - return new Dictionary(); - } - } - } - /// - /// Dictonary serializer - /// - public class DictionarySerializer : IXmlSerializable - { - public Dictionary Dictionary; - - public DictionarySerializer() - { - this.Dictionary = new Dictionary(); - } - - public DictionarySerializer(Dictionary dictionary) - { - this.Dictionary = dictionary; - } - - public void WriteXml(XmlWriter writer) - { - if (!Dictionary.Any()) - return; - - foreach (var key in this.Dictionary.Keys) - { - writer.WriteStartElement("item"); - writer.WriteElementString("key", key); - var value = this.Dictionary[key]; - //please note that we use ToString() for objects here - //of course, we can Serialize them - //but let's keep it simple and leave it for developers to handle it - //just put required serialization into ToString method of your object(s) - //because some objects don't implement ISerializable - //the question is how should we deserialize null values? - writer.WriteElementString("value", value != null ? value.ToString() : null); - writer.WriteEndElement(); - } - } - - public void ReadXml(XmlReader reader) - { - bool wasEmpty = reader.IsEmptyElement; - reader.Read(); - if (wasEmpty) - return; - while (reader.NodeType != XmlNodeType.EndElement) - { - reader.ReadStartElement("item"); - string key = reader.ReadElementString("key"); - string value = reader.ReadElementString("value"); - this.Dictionary.Add(key, value); - reader.ReadEndElement(); - reader.MoveToContent(); - } - reader.ReadEndElement(); - } - - public XmlSchema GetSchema() - { - return null; - } - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml; +using System.Xml.Schema; +using System.Xml.Serialization; +using Nop.Core.Domain.Orders; +using Nop.Core.Domain.Payments; +using Nop.Services.Orders; + +namespace Nop.Services.Payments +{ + /// + /// Payment extensions + /// + public static class PaymentExtensions + { + /// + /// Is payment method active? + /// + /// Payment method + /// Payment settings + /// Result + public static bool IsPaymentMethodActive(this IPaymentMethod paymentMethod, + PaymentSettings paymentSettings) + { + if (paymentMethod == null) + throw new ArgumentNullException("paymentMethod"); + + if (paymentSettings == null) + throw new ArgumentNullException("paymentSettings"); + + if (paymentSettings.ActivePaymentMethodSystemNames == null) + return false; + foreach (string activeMethodSystemName in paymentSettings.ActivePaymentMethodSystemNames) + if (paymentMethod.PluginDescriptor.SystemName.Equals(activeMethodSystemName, StringComparison.InvariantCultureIgnoreCase)) + return true; + return false; + } + + /// + /// Calculate payment method fee + /// + /// Payment method + /// Order total calculation service + /// Shopping cart + /// Fee value + /// Is fee amount specified as percentage or fixed value? + /// Result + public static decimal CalculateAdditionalFee(this IPaymentMethod paymentMethod, + IOrderTotalCalculationService orderTotalCalculationService, IList cart, + decimal fee, bool usePercentage) + { + if (paymentMethod == null) + throw new ArgumentNullException("paymentMethod"); + if (fee == decimal.Zero) //tfc allow negative fee + return fee; + + decimal result; + if (usePercentage) + { + //percentage + var orderTotalWithoutPaymentFee = orderTotalCalculationService.GetShoppingCartTotal(cart, usePaymentMethodAdditionalFee: false); + result = (decimal)((((float)orderTotalWithoutPaymentFee) * ((float)fee)) / 100f); + } + else + { + //fixed value + result = fee; + } + return result; + } + + + /// + /// Serialize CustomValues of ProcessPaymentRequest + /// + /// Request + /// Serialized CustomValues + public static string SerializeCustomValues(this ProcessPaymentRequest request) + { + if (request == null) + throw new ArgumentNullException("request"); + + if (!request.CustomValues.Any()) + return null; + + //XmlSerializer won't serialize objects that implement IDictionary by default. + //http://msdn.microsoft.com/en-us/magazine/cc164135.aspx + + //also see http://ropox.ru/tag/ixmlserializable/ (Russian language) + + var ds = new DictionarySerializer(request.CustomValues); + var xs = new XmlSerializer(typeof(DictionarySerializer)); + + using (var textWriter = new StringWriter()) + { + using (var xmlWriter = XmlWriter.Create(textWriter)) + { + xs.Serialize(xmlWriter, ds); + } + var result = textWriter.ToString(); + return result; + } + } + /// + /// Deerialize CustomValues of Order + /// + /// Order + /// Serialized CustomValues CustomValues + public static Dictionary DeserializeCustomValues(this Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + var request = new ProcessPaymentRequest(); + return request.DeserializeCustomValues(order.CustomValuesXml); + } + /// + /// Deerialize CustomValues of ProcessPaymentRequest + /// + /// Request + /// Serialized CustomValues + /// Serialized CustomValues CustomValues + public static Dictionary DeserializeCustomValues(this ProcessPaymentRequest request, string customValuesXml) + { + if (string.IsNullOrWhiteSpace(customValuesXml)) + { + return new Dictionary(); + } + + var serializer = new XmlSerializer(typeof(DictionarySerializer)); + + using (var textReader = new StringReader(customValuesXml)) + { + using (var xmlReader = XmlReader.Create(textReader)) + { + var ds = serializer.Deserialize(xmlReader) as DictionarySerializer; + if (ds != null) + return ds.Dictionary; + return new Dictionary(); + } + } + } + /// + /// Dictonary serializer + /// + public class DictionarySerializer : IXmlSerializable + { + public Dictionary Dictionary; + + public DictionarySerializer() + { + this.Dictionary = new Dictionary(); + } + + public DictionarySerializer(Dictionary dictionary) + { + this.Dictionary = dictionary; + } + + public void WriteXml(XmlWriter writer) + { + if (!Dictionary.Any()) + return; + + foreach (var key in this.Dictionary.Keys) + { + writer.WriteStartElement("item"); + writer.WriteElementString("key", key); + var value = this.Dictionary[key]; + //please note that we use ToString() for objects here + //of course, we can Serialize them + //but let's keep it simple and leave it for developers to handle it + //just put required serialization into ToString method of your object(s) + //because some objects don't implement ISerializable + //the question is how should we deserialize null values? + writer.WriteElementString("value", value != null ? value.ToString() : null); + writer.WriteEndElement(); + } + } + + public void ReadXml(XmlReader reader) + { + bool wasEmpty = reader.IsEmptyElement; + reader.Read(); + if (wasEmpty) + return; + while (reader.NodeType != XmlNodeType.EndElement) + { + reader.ReadStartElement("item"); + string key = reader.ReadElementString("key"); + string value = reader.ReadElementString("value"); + this.Dictionary.Add(key, value); + reader.ReadEndElement(); + reader.MoveToContent(); + } + reader.ReadEndElement(); + } + + public XmlSchema GetSchema() + { + return null; + } + } + } +} diff --git a/src/Libraries/Nop.Services/Payments/PaymentService.cs b/src/Libraries/Nop.Services/Payments/PaymentService.cs index 6fe9c008895..d69362838f7 100644 --- a/src/Libraries/Nop.Services/Payments/PaymentService.cs +++ b/src/Libraries/Nop.Services/Payments/PaymentService.cs @@ -1,418 +1,419 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Nop.Core; -using Nop.Core.Domain.Customers; -using Nop.Core.Domain.Orders; -using Nop.Core.Domain.Payments; -using Nop.Core.Plugins; -using Nop.Services.Catalog; -using Nop.Services.Configuration; - -namespace Nop.Services.Payments -{ - /// - /// Payment service - /// - public partial class PaymentService : IPaymentService - { - #region Fields - - private readonly PaymentSettings _paymentSettings; - private readonly IPluginFinder _pluginFinder; - private readonly ISettingService _settingService; - private readonly ShoppingCartSettings _shoppingCartSettings; - - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Payment settings - /// Plugin finder - /// Setting service - /// Shopping cart settings - public PaymentService(PaymentSettings paymentSettings, - IPluginFinder pluginFinder, - ISettingService settingService, - ShoppingCartSettings shoppingCartSettings) - { - this._paymentSettings = paymentSettings; - this._pluginFinder = pluginFinder; - this._settingService = settingService; - this._shoppingCartSettings = shoppingCartSettings; - } - - #endregion - - #region Methods - - #region Payment methods - - /// - /// Load active payment methods - /// - /// Load records allowed only to a specified customer; pass null to ignore ACL permissions - /// Load records allowed only in a specified store; pass 0 to load all records - /// Load records allowed only in a specified country; pass 0 to load all records - /// Payment methods - public virtual IList LoadActivePaymentMethods(Customer customer = null, int storeId = 0, int filterByCountryId = 0) - { - return LoadAllPaymentMethods(customer, storeId, filterByCountryId) - .Where(provider => _paymentSettings.ActivePaymentMethodSystemNames - .Contains(provider.PluginDescriptor.SystemName, StringComparer.InvariantCultureIgnoreCase)).ToList(); - } - - /// - /// Load payment provider by system name - /// - /// System name - /// Found payment provider - public virtual IPaymentMethod LoadPaymentMethodBySystemName(string systemName) - { - var descriptor = _pluginFinder.GetPluginDescriptorBySystemName(systemName); - if (descriptor != null) - return descriptor.Instance(); - - return null; - } - - /// - /// Load all payment providers - /// - /// Load records allowed only to a specified customer; pass null to ignore ACL permissions - /// Load records allowed only in a specified store; pass 0 to load all records - /// Load records allowed only in a specified country; pass 0 to load all records - /// Payment providers - public virtual IList LoadAllPaymentMethods(Customer customer = null, int storeId = 0, int filterByCountryId = 0) - { - var paymentMethods = _pluginFinder.GetPlugins(customer: customer, storeId: storeId).ToList(); - if (filterByCountryId == 0) - return paymentMethods; - - //filter by country - var paymentMetodsByCountry = new List(); - foreach (var pm in paymentMethods) - { - var restictedCountryIds = GetRestictedCountryIds(pm); - if (!restictedCountryIds.Contains(filterByCountryId)) - { - paymentMetodsByCountry.Add(pm); - } - } - - return paymentMetodsByCountry; - } - - #endregion - - #region Restrictions - - /// - /// Gets a list of coutnry identifiers in which a certain payment method is now allowed - /// - /// Payment method - /// A list of country identifiers - public virtual IList GetRestictedCountryIds(IPaymentMethod paymentMethod) - { - if (paymentMethod == null) - throw new ArgumentNullException("paymentMethod"); - - var settingKey = string.Format("PaymentMethodRestictions.{0}", paymentMethod.PluginDescriptor.SystemName); - var restictedCountryIds = _settingService.GetSettingByKey>(settingKey); - if (restictedCountryIds == null) - restictedCountryIds = new List(); - return restictedCountryIds; - } - - /// - /// Saves a list of coutnry identifiers in which a certain payment method is now allowed - /// - /// Payment method - /// A list of country identifiers - public virtual void SaveRestictedCountryIds(IPaymentMethod paymentMethod, List countryIds) - { - if (paymentMethod == null) - throw new ArgumentNullException("paymentMethod"); - - //we should be sure that countryIds is of type List (not IList) - var settingKey = string.Format("PaymentMethodRestictions.{0}", paymentMethod.PluginDescriptor.SystemName); - _settingService.SetSetting(settingKey, countryIds); - } - - #endregion - - #region Processing - - /// - /// Process a payment - /// - /// Payment info required for an order processing - /// Process payment result - public virtual ProcessPaymentResult ProcessPayment(ProcessPaymentRequest processPaymentRequest) - { - if (processPaymentRequest.OrderTotal == decimal.Zero) - { - var result = new ProcessPaymentResult - { - NewPaymentStatus = PaymentStatus.Paid - }; - return result; - } - - //We should strip out any white space or dash in the CC number entered. - if (!String.IsNullOrWhiteSpace(processPaymentRequest.CreditCardNumber)) - { - processPaymentRequest.CreditCardNumber = processPaymentRequest.CreditCardNumber.Replace(" ", ""); - processPaymentRequest.CreditCardNumber = processPaymentRequest.CreditCardNumber.Replace("-", ""); - } - var paymentMethod = LoadPaymentMethodBySystemName(processPaymentRequest.PaymentMethodSystemName); - if (paymentMethod == null) - throw new NopException("Payment method couldn't be loaded"); - return paymentMethod.ProcessPayment(processPaymentRequest); - } - - /// - /// Post process payment (used by payment gateways that require redirecting to a third-party URL) - /// - /// Payment info required for an order processing - public virtual void PostProcessPayment(PostProcessPaymentRequest postProcessPaymentRequest) - { - //already paid or order.OrderTotal == decimal.Zero - if (postProcessPaymentRequest.Order.PaymentStatus == PaymentStatus.Paid) - return; - - var paymentMethod = LoadPaymentMethodBySystemName(postProcessPaymentRequest.Order.PaymentMethodSystemName); - if (paymentMethod == null) - throw new NopException("Payment method couldn't be loaded"); - paymentMethod.PostProcessPayment(postProcessPaymentRequest); - } - - /// - /// Gets a value indicating whether customers can complete a payment after order is placed but not completed (for redirection payment methods) - /// - /// Order - /// Result - public virtual bool CanRePostProcessPayment(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (!_paymentSettings.AllowRePostingPayments) - return false; - - var paymentMethod = LoadPaymentMethodBySystemName(order.PaymentMethodSystemName); - if (paymentMethod == null) - return false; //Payment method couldn't be loaded (for example, was uninstalled) - - if (paymentMethod.PaymentMethodType != PaymentMethodType.Redirection) - return false; //this option is available only for redirection payment methods - - if (order.Deleted) - return false; //do not allow for deleted orders - - if (order.OrderStatus == OrderStatus.Cancelled) - return false; //do not allow for cancelled orders - - if (order.PaymentStatus != PaymentStatus.Pending) - return false; //payment status should be Pending - - return paymentMethod.CanRePostProcessPayment(order); - } - - /// - /// Gets an additional handling fee of a payment method - /// - /// Shoping cart - /// Payment method system name - /// Additional handling fee - public virtual decimal GetAdditionalHandlingFee(IList cart, string paymentMethodSystemName) - { - if (String.IsNullOrEmpty(paymentMethodSystemName)) - return decimal.Zero; - - var paymentMethod = LoadPaymentMethodBySystemName(paymentMethodSystemName); - if (paymentMethod == null) - return decimal.Zero; - - decimal result = paymentMethod.GetAdditionalHandlingFee(cart); - if (result < decimal.Zero) - result = decimal.Zero; - if (_shoppingCartSettings.RoundPricesDuringCalculation) - { - result = RoundingHelper.RoundPrice(result); - } - return result; - } - - /// - /// Gets a value indicating whether capture is supported by payment method - /// - /// Payment method system name - /// A value indicating whether capture is supported - public virtual bool SupportCapture(string paymentMethodSystemName) - { - var paymentMethod = LoadPaymentMethodBySystemName(paymentMethodSystemName); - if (paymentMethod == null) - return false; - return paymentMethod.SupportCapture; - } - - /// - /// Captures payment - /// - /// Capture payment request - /// Capture payment result - public virtual CapturePaymentResult Capture(CapturePaymentRequest capturePaymentRequest) - { - var paymentMethod = LoadPaymentMethodBySystemName(capturePaymentRequest.Order.PaymentMethodSystemName); - if (paymentMethod == null) - throw new NopException("Payment method couldn't be loaded"); - return paymentMethod.Capture(capturePaymentRequest); - } - - /// - /// Gets a value indicating whether partial refund is supported by payment method - /// - /// Payment method system name - /// A value indicating whether partial refund is supported - public virtual bool SupportPartiallyRefund(string paymentMethodSystemName) - { - var paymentMethod = LoadPaymentMethodBySystemName(paymentMethodSystemName); - if (paymentMethod == null) - return false; - return paymentMethod.SupportPartiallyRefund; - } - - /// - /// Gets a value indicating whether refund is supported by payment method - /// - /// Payment method system name - /// A value indicating whether refund is supported - public virtual bool SupportRefund(string paymentMethodSystemName) - { - var paymentMethod = LoadPaymentMethodBySystemName(paymentMethodSystemName); - if (paymentMethod == null) - return false; - return paymentMethod.SupportRefund; - } - - /// - /// Refunds a payment - /// - /// Request - /// Result - public virtual RefundPaymentResult Refund(RefundPaymentRequest refundPaymentRequest) - { - var paymentMethod = LoadPaymentMethodBySystemName(refundPaymentRequest.Order.PaymentMethodSystemName); - if (paymentMethod == null) - throw new NopException("Payment method couldn't be loaded"); - return paymentMethod.Refund(refundPaymentRequest); - } - - /// - /// Gets a value indicating whether void is supported by payment method - /// - /// Payment method system name - /// A value indicating whether void is supported - public virtual bool SupportVoid(string paymentMethodSystemName) - { - var paymentMethod = LoadPaymentMethodBySystemName(paymentMethodSystemName); - if (paymentMethod == null) - return false; - return paymentMethod.SupportVoid; - } - - /// - /// Voids a payment - /// - /// Request - /// Result - public virtual VoidPaymentResult Void(VoidPaymentRequest voidPaymentRequest) - { - var paymentMethod = LoadPaymentMethodBySystemName(voidPaymentRequest.Order.PaymentMethodSystemName); - if (paymentMethod == null) - throw new NopException("Payment method couldn't be loaded"); - return paymentMethod.Void(voidPaymentRequest); - } - - /// - /// Gets a recurring payment type of payment method - /// - /// Payment method system name - /// A recurring payment type of payment method - public virtual RecurringPaymentType GetRecurringPaymentType(string paymentMethodSystemName) - { - var paymentMethod = LoadPaymentMethodBySystemName(paymentMethodSystemName); - if (paymentMethod == null) - return RecurringPaymentType.NotSupported; - return paymentMethod.RecurringPaymentType; - } - - /// - /// Process recurring payment - /// - /// Payment info required for an order processing - /// Process payment result - public virtual ProcessPaymentResult ProcessRecurringPayment(ProcessPaymentRequest processPaymentRequest) - { - if (processPaymentRequest.OrderTotal == decimal.Zero) - { - var result = new ProcessPaymentResult - { - NewPaymentStatus = PaymentStatus.Paid - }; - return result; - } - - var paymentMethod = LoadPaymentMethodBySystemName(processPaymentRequest.PaymentMethodSystemName); - if (paymentMethod == null) - throw new NopException("Payment method couldn't be loaded"); - return paymentMethod.ProcessRecurringPayment(processPaymentRequest); - } - - /// - /// Cancels a recurring payment - /// - /// Request - /// Result - public virtual CancelRecurringPaymentResult CancelRecurringPayment(CancelRecurringPaymentRequest cancelPaymentRequest) - { - if (cancelPaymentRequest.Order.OrderTotal == decimal.Zero) - return new CancelRecurringPaymentResult(); - - var paymentMethod = LoadPaymentMethodBySystemName(cancelPaymentRequest.Order.PaymentMethodSystemName); - if (paymentMethod == null) - throw new NopException("Payment method couldn't be loaded"); - return paymentMethod.CancelRecurringPayment(cancelPaymentRequest); - } - - /// - /// Gets masked credit card number - /// - /// Credit card number - /// Masked credit card number - public virtual string GetMaskedCreditCardNumber(string creditCardNumber) - { - if (String.IsNullOrEmpty(creditCardNumber)) - return string.Empty; - - if (creditCardNumber.Length <= 4) - return creditCardNumber; - - string last4 = creditCardNumber.Substring(creditCardNumber.Length - 4, 4); - string maskedChars = string.Empty; - for (int i = 0; i < creditCardNumber.Length - 4; i++) - { - maskedChars += "*"; - } - return maskedChars + last4; - } - - #endregion - - #endregion - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using Nop.Core; +using Nop.Core.Domain.Customers; +using Nop.Core.Domain.Orders; +using Nop.Core.Domain.Payments; +using Nop.Core.Plugins; +using Nop.Services.Catalog; +using Nop.Services.Configuration; + +namespace Nop.Services.Payments +{ + /// + /// Payment service + /// + public partial class PaymentService : IPaymentService + { + #region Fields + + private readonly PaymentSettings _paymentSettings; + private readonly IPluginFinder _pluginFinder; + private readonly ISettingService _settingService; + private readonly ShoppingCartSettings _shoppingCartSettings; + + #endregion + + #region Ctor + + /// + /// Ctor + /// + /// Payment settings + /// Plugin finder + /// Setting service + /// Shopping cart settings + public PaymentService(PaymentSettings paymentSettings, + IPluginFinder pluginFinder, + ISettingService settingService, + ShoppingCartSettings shoppingCartSettings) + { + this._paymentSettings = paymentSettings; + this._pluginFinder = pluginFinder; + this._settingService = settingService; + this._shoppingCartSettings = shoppingCartSettings; + } + + #endregion + + #region Methods + + #region Payment methods + + /// + /// Load active payment methods + /// + /// Load records allowed only to a specified customer; pass null to ignore ACL permissions + /// Load records allowed only in a specified store; pass 0 to load all records + /// Load records allowed only in a specified country; pass 0 to load all records + /// Payment methods + public virtual IList LoadActivePaymentMethods(Customer customer = null, int storeId = 0, int filterByCountryId = 0) + { + return LoadAllPaymentMethods(customer, storeId, filterByCountryId) + .Where(provider => _paymentSettings.ActivePaymentMethodSystemNames + .Contains(provider.PluginDescriptor.SystemName, StringComparer.InvariantCultureIgnoreCase)).ToList(); + } + + /// + /// Load payment provider by system name + /// + /// System name + /// Found payment provider + public virtual IPaymentMethod LoadPaymentMethodBySystemName(string systemName) + { + var descriptor = _pluginFinder.GetPluginDescriptorBySystemName(systemName); + if (descriptor != null) + return descriptor.Instance(); + + return null; + } + + /// + /// Load all payment providers + /// + /// Load records allowed only to a specified customer; pass null to ignore ACL permissions + /// Load records allowed only in a specified store; pass 0 to load all records + /// Load records allowed only in a specified country; pass 0 to load all records + /// Payment providers + public virtual IList LoadAllPaymentMethods(Customer customer = null, int storeId = 0, int filterByCountryId = 0) + { + var paymentMethods = _pluginFinder.GetPlugins(customer: customer, storeId: storeId).ToList(); + if (filterByCountryId == 0) + return paymentMethods; + + //filter by country + var paymentMetodsByCountry = new List(); + foreach (var pm in paymentMethods) + { + var restictedCountryIds = GetRestictedCountryIds(pm); + if (!restictedCountryIds.Contains(filterByCountryId)) + { + paymentMetodsByCountry.Add(pm); + } + } + + return paymentMetodsByCountry; + } + + #endregion + + #region Restrictions + + /// + /// Gets a list of coutnry identifiers in which a certain payment method is now allowed + /// + /// Payment method + /// A list of country identifiers + public virtual IList GetRestictedCountryIds(IPaymentMethod paymentMethod) + { + if (paymentMethod == null) + throw new ArgumentNullException("paymentMethod"); + + var settingKey = string.Format("PaymentMethodRestictions.{0}", paymentMethod.PluginDescriptor.SystemName); + var restictedCountryIds = _settingService.GetSettingByKey>(settingKey); + if (restictedCountryIds == null) + restictedCountryIds = new List(); + return restictedCountryIds; + } + + /// + /// Saves a list of coutnry identifiers in which a certain payment method is now allowed + /// + /// Payment method + /// A list of country identifiers + public virtual void SaveRestictedCountryIds(IPaymentMethod paymentMethod, List countryIds) + { + if (paymentMethod == null) + throw new ArgumentNullException("paymentMethod"); + + //we should be sure that countryIds is of type List (not IList) + var settingKey = string.Format("PaymentMethodRestictions.{0}", paymentMethod.PluginDescriptor.SystemName); + _settingService.SetSetting(settingKey, countryIds); + } + + #endregion + + #region Processing + + /// + /// Process a payment + /// + /// Payment info required for an order processing + /// Process payment result + public virtual ProcessPaymentResult ProcessPayment(ProcessPaymentRequest processPaymentRequest) + { + if (processPaymentRequest.OrderTotal == decimal.Zero) + { + var result = new ProcessPaymentResult + { + NewPaymentStatus = PaymentStatus.Paid + }; + return result; + } + + //We should strip out any white space or dash in the CC number entered. + if (!String.IsNullOrWhiteSpace(processPaymentRequest.CreditCardNumber)) + { + processPaymentRequest.CreditCardNumber = processPaymentRequest.CreditCardNumber.Replace(" ", ""); + processPaymentRequest.CreditCardNumber = processPaymentRequest.CreditCardNumber.Replace("-", ""); + } + var paymentMethod = LoadPaymentMethodBySystemName(processPaymentRequest.PaymentMethodSystemName); + if (paymentMethod == null) + throw new NopException("Payment method couldn't be loaded"); + return paymentMethod.ProcessPayment(processPaymentRequest); + } + + /// + /// Post process payment (used by payment gateways that require redirecting to a third-party URL) + /// + /// Payment info required for an order processing + public virtual void PostProcessPayment(PostProcessPaymentRequest postProcessPaymentRequest) + { + //already paid or order.OrderTotal == decimal.Zero + if (postProcessPaymentRequest.Order.PaymentStatus == PaymentStatus.Paid) + return; + + var paymentMethod = LoadPaymentMethodBySystemName(postProcessPaymentRequest.Order.PaymentMethodSystemName); + if (paymentMethod == null) + throw new NopException("Payment method couldn't be loaded"); + paymentMethod.PostProcessPayment(postProcessPaymentRequest); + } + + /// + /// Gets a value indicating whether customers can complete a payment after order is placed but not completed (for redirection payment methods) + /// + /// Order + /// Result + public virtual bool CanRePostProcessPayment(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (!_paymentSettings.AllowRePostingPayments) + return false; + + var paymentMethod = LoadPaymentMethodBySystemName(order.PaymentMethodSystemName); + if (paymentMethod == null) + return false; //Payment method couldn't be loaded (for example, was uninstalled) + + if (paymentMethod.PaymentMethodType != PaymentMethodType.Redirection) + return false; //this option is available only for redirection payment methods + + if (order.Deleted) + return false; //do not allow for deleted orders + + if (order.OrderStatus == OrderStatus.Cancelled) + return false; //do not allow for cancelled orders + + if (order.PaymentStatus != PaymentStatus.Pending) + return false; //payment status should be Pending + + return paymentMethod.CanRePostProcessPayment(order); + } + + /// + /// Gets an additional handling fee of a payment method + /// + /// Shoping cart + /// Payment method system name + /// Additional handling fee + public virtual decimal GetAdditionalHandlingFee(IList cart, string paymentMethodSystemName) + { + if (String.IsNullOrEmpty(paymentMethodSystemName)) + return decimal.Zero; + + var paymentMethod = LoadPaymentMethodBySystemName(paymentMethodSystemName); + if (paymentMethod == null) + return decimal.Zero; + + decimal result = paymentMethod.GetAdditionalHandlingFee(cart); + //tfc allow negative fee + //if (result < decimal.Zero) + // result = decimal.Zero; + if (_shoppingCartSettings.RoundPricesDuringCalculation) + { + result = RoundingHelper.RoundAmount(result); + } + return result; + } + + /// + /// Gets a value indicating whether capture is supported by payment method + /// + /// Payment method system name + /// A value indicating whether capture is supported + public virtual bool SupportCapture(string paymentMethodSystemName) + { + var paymentMethod = LoadPaymentMethodBySystemName(paymentMethodSystemName); + if (paymentMethod == null) + return false; + return paymentMethod.SupportCapture; + } + + /// + /// Captures payment + /// + /// Capture payment request + /// Capture payment result + public virtual CapturePaymentResult Capture(CapturePaymentRequest capturePaymentRequest) + { + var paymentMethod = LoadPaymentMethodBySystemName(capturePaymentRequest.Order.PaymentMethodSystemName); + if (paymentMethod == null) + throw new NopException("Payment method couldn't be loaded"); + return paymentMethod.Capture(capturePaymentRequest); + } + + /// + /// Gets a value indicating whether partial refund is supported by payment method + /// + /// Payment method system name + /// A value indicating whether partial refund is supported + public virtual bool SupportPartiallyRefund(string paymentMethodSystemName) + { + var paymentMethod = LoadPaymentMethodBySystemName(paymentMethodSystemName); + if (paymentMethod == null) + return false; + return paymentMethod.SupportPartiallyRefund; + } + + /// + /// Gets a value indicating whether refund is supported by payment method + /// + /// Payment method system name + /// A value indicating whether refund is supported + public virtual bool SupportRefund(string paymentMethodSystemName) + { + var paymentMethod = LoadPaymentMethodBySystemName(paymentMethodSystemName); + if (paymentMethod == null) + return false; + return paymentMethod.SupportRefund; + } + + /// + /// Refunds a payment + /// + /// Request + /// Result + public virtual RefundPaymentResult Refund(RefundPaymentRequest refundPaymentRequest) + { + var paymentMethod = LoadPaymentMethodBySystemName(refundPaymentRequest.Order.PaymentMethodSystemName); + if (paymentMethod == null) + throw new NopException("Payment method couldn't be loaded"); + return paymentMethod.Refund(refundPaymentRequest); + } + + /// + /// Gets a value indicating whether void is supported by payment method + /// + /// Payment method system name + /// A value indicating whether void is supported + public virtual bool SupportVoid(string paymentMethodSystemName) + { + var paymentMethod = LoadPaymentMethodBySystemName(paymentMethodSystemName); + if (paymentMethod == null) + return false; + return paymentMethod.SupportVoid; + } + + /// + /// Voids a payment + /// + /// Request + /// Result + public virtual VoidPaymentResult Void(VoidPaymentRequest voidPaymentRequest) + { + var paymentMethod = LoadPaymentMethodBySystemName(voidPaymentRequest.Order.PaymentMethodSystemName); + if (paymentMethod == null) + throw new NopException("Payment method couldn't be loaded"); + return paymentMethod.Void(voidPaymentRequest); + } + + /// + /// Gets a recurring payment type of payment method + /// + /// Payment method system name + /// A recurring payment type of payment method + public virtual RecurringPaymentType GetRecurringPaymentType(string paymentMethodSystemName) + { + var paymentMethod = LoadPaymentMethodBySystemName(paymentMethodSystemName); + if (paymentMethod == null) + return RecurringPaymentType.NotSupported; + return paymentMethod.RecurringPaymentType; + } + + /// + /// Process recurring payment + /// + /// Payment info required for an order processing + /// Process payment result + public virtual ProcessPaymentResult ProcessRecurringPayment(ProcessPaymentRequest processPaymentRequest) + { + if (processPaymentRequest.OrderTotal == decimal.Zero) + { + var result = new ProcessPaymentResult + { + NewPaymentStatus = PaymentStatus.Paid + }; + return result; + } + + var paymentMethod = LoadPaymentMethodBySystemName(processPaymentRequest.PaymentMethodSystemName); + if (paymentMethod == null) + throw new NopException("Payment method couldn't be loaded"); + return paymentMethod.ProcessRecurringPayment(processPaymentRequest); + } + + /// + /// Cancels a recurring payment + /// + /// Request + /// Result + public virtual CancelRecurringPaymentResult CancelRecurringPayment(CancelRecurringPaymentRequest cancelPaymentRequest) + { + if (cancelPaymentRequest.Order.OrderTotal == decimal.Zero) + return new CancelRecurringPaymentResult(); + + var paymentMethod = LoadPaymentMethodBySystemName(cancelPaymentRequest.Order.PaymentMethodSystemName); + if (paymentMethod == null) + throw new NopException("Payment method couldn't be loaded"); + return paymentMethod.CancelRecurringPayment(cancelPaymentRequest); + } + + /// + /// Gets masked credit card number + /// + /// Credit card number + /// Masked credit card number + public virtual string GetMaskedCreditCardNumber(string creditCardNumber) + { + if (String.IsNullOrEmpty(creditCardNumber)) + return string.Empty; + + if (creditCardNumber.Length <= 4) + return creditCardNumber; + + string last4 = creditCardNumber.Substring(creditCardNumber.Length - 4, 4); + string maskedChars = string.Empty; + for (int i = 0; i < creditCardNumber.Length - 4; i++) + { + maskedChars += "*"; + } + return maskedChars + last4; + } + + #endregion + + #endregion + } +} diff --git a/src/Libraries/Nop.Services/Tax/TaxService.cs b/src/Libraries/Nop.Services/Tax/TaxService.cs index 656859122c8..f883203b342 100644 --- a/src/Libraries/Nop.Services/Tax/TaxService.cs +++ b/src/Libraries/Nop.Services/Tax/TaxService.cs @@ -241,7 +241,7 @@ protected virtual decimal CalculatePrice(decimal price, decimal percent, bool in } else { - result = price - (price) / (100 + percent) * percent; + result = price / (1 + percent / 100); //MF 13.11.16: is the same but written in a complicated way: price - (price) / (100 + percent) * percent; } return result; } @@ -255,7 +255,7 @@ protected virtual decimal CalculatePrice(decimal price, decimal percent, bool in /// Price (taxable value) /// Calculated tax rate /// A value indicating whether a request is taxable - protected virtual void GetTaxRate(Product product, int taxCategoryId, + protected virtual void GetTaxRate(Product product, int taxCategoryId, Customer customer, decimal price, out decimal taxRate, out bool isTaxable) { taxRate = decimal.Zero; @@ -275,7 +275,7 @@ protected virtual void GetTaxRate(Product product, int taxCategoryId, isTaxable = false; } //make EU VAT exempt validation (the European Union Value Added Tax) - if (isTaxable && + if (isTaxable && _taxSettings.EuVatEnabled && IsVatExempt(calculateTaxRequest.Address, calculateTaxRequest.Customer)) { @@ -290,17 +290,17 @@ protected virtual void GetTaxRate(Product product, int taxCategoryId, //ensure that tax is equal or greater than zero if (calculateTaxResult.TaxRate < decimal.Zero) calculateTaxResult.TaxRate = decimal.Zero; - + taxRate = calculateTaxResult.TaxRate; } - else + else if (_taxSettings.LogErrors) { foreach (var error in calculateTaxResult.Errors) { _logger.Error(string.Format("{0} - {1}", activeTaxProvider.PluginDescriptor.FriendlyName, error), null, customer); } - } + } } #endregion @@ -357,13 +357,13 @@ public virtual IList LoadAllTaxProviders(Customer customer = null) /// Price /// Tax rate /// Price - public virtual decimal GetProductPrice(Product product, decimal price, + public virtual decimal GetProductPrice(Product product, decimal price, out decimal taxRate) { var customer = _workContext.CurrentCustomer; return GetProductPrice(product, price, customer, out taxRate); } - + /// /// Gets price /// @@ -393,10 +393,10 @@ public virtual decimal GetProductPrice(Product product, decimal price, { bool priceIncludesTax = _taxSettings.PricesIncludeTax; int taxCategoryId = 0; - return GetProductPrice(product, taxCategoryId, price, includingTax, + return GetProductPrice(product, taxCategoryId, price, includingTax, customer, priceIncludesTax, out taxRate); } - + /// /// Gets price /// @@ -412,16 +412,18 @@ public virtual decimal GetProductPrice(Product product, int taxCategoryId, decimal price, bool includingTax, Customer customer, bool priceIncludesTax, out decimal taxRate) { + bool isTaxable; + //taxrate should be always passed + GetTaxRate(product, taxCategoryId, customer, price, out taxRate, out isTaxable); + //no need to calculate tax rate if passed "price" is 0 if (price == decimal.Zero) { - taxRate = decimal.Zero; - return taxRate; + return price; } - bool isTaxable; - GetTaxRate(product, taxCategoryId, customer, price, out taxRate, out isTaxable); + if (priceIncludesTax) { @@ -462,7 +464,7 @@ public virtual decimal GetProductPrice(Product product, int taxCategoryId, //we return 0% tax rate in case a request is not taxable taxRate = decimal.Zero; } - + //allowed to support negative price adjustments //if (price < decimal.Zero) // price = decimal.Zero; @@ -673,7 +675,7 @@ public virtual VatNumberStatus GetVatNumberStatus(string fullVatNumber, if (String.IsNullOrWhiteSpace(fullVatNumber)) return VatNumberStatus.Empty; fullVatNumber = fullVatNumber.Trim(); - + //GB 111 1111 111 or GB 1111111111 //more advanced regex - http://codeigniter.com/wiki/European_Vat_Checker var r = new Regex(@"^(\w{2})(.*)"); @@ -685,7 +687,7 @@ public virtual VatNumberStatus GetVatNumberStatus(string fullVatNumber, return GetVatNumberStatus(twoLetterIsoCode, vatNumber, out name, out address); } - + /// /// Gets VAT Number status /// @@ -697,7 +699,7 @@ public virtual VatNumberStatus GetVatNumberStatus(string twoLetterIsoCode, strin string name, address; return GetVatNumberStatus(twoLetterIsoCode, vatNumber, out name, out address); } - + /// /// Gets VAT Number status /// diff --git a/src/Libraries/Nop.Services/Tax/TaxSummary.cs b/src/Libraries/Nop.Services/Tax/TaxSummary.cs new file mode 100644 index 00000000000..87213341666 --- /dev/null +++ b/src/Libraries/Nop.Services/Tax/TaxSummary.cs @@ -0,0 +1,229 @@ + +using Nop.Services.Catalog; +using System.Collections.Generic; +using System.Linq; + +namespace Nop.Services.Tax +{ + /// + /// TaxRate entry to store amounts per vatrate + /// all value are per rate and most of them calculated + /// only VatRate, ItemAmount, ShippingAmount and PaymentFeeAmount can be set + /// + public partial class TaxRateEntry + { + public decimal VatRate { get; set; } + public decimal SubtotalAmount { get; set; } + public decimal ShippingAmount { get; set; } + public decimal PaymentFeeAmount { get; set; } //can be negative when discounted + public decimal SubTotalDiscAmount { get; internal set; } + public decimal InvoiceDiscountAmount { get; internal set; } + public decimal BaseAmount { get; internal set; } + public decimal VatAmount { get; internal set; } + public decimal AmountIncludingVAT { get; internal set; } + } + /// + /// TaxSummary to store TaxRates and overall totals + /// + public partial class TaxSummary + { + #region Ctor + /// + /// Ctor + /// + /// Indicates if given amounts do include tax + public TaxSummary(bool includingTax) + { + TaxRates = new SortedDictionary(); + PricesIncludeTax = includingTax; + PercentageInvDiscount = decimal.Zero; + PercentageSubTotalDiscount = decimal.Zero; + PercentagePaymentFeeDiscount = decimal.Zero; + + TotalSubTotalAmount = decimal.Zero; + TotalShippingAmount = decimal.Zero; + TotalPaymentFeeAmount = decimal.Zero; + TotalAmount = decimal.Zero; + TotalAmountVAT = decimal.Zero; + TotalAmountIncludingVAT = decimal.Zero; + TotalSubTotalDiscAmount = decimal.Zero; + TotalInvDiscAmount = decimal.Zero; + } + #endregion + + public SortedDictionary TaxRates { get; set; } + public bool PricesIncludeTax { get; } + public decimal PercentageInvDiscount { get; private set; } + public decimal PercentageSubTotalDiscount { get; private set; } + public decimal PercentagePaymentFeeDiscount { get; private set; } + public decimal TotalSubTotalAmount { get; private set; } + public decimal TotalShippingAmount { get; private set; } + public decimal TotalPaymentFeeAmount { get; private set; } + public decimal TotalAmount { get; private set; } + public decimal TotalAmountVAT { get; private set; } + public decimal TotalAmountIncludingVAT { get; private set; } + public decimal TotalSubTotalDiscAmount { get; private set; } + public decimal TotalInvDiscAmount { get; private set; } + + private bool HasChanged = false; + + #region utilities + public SortedDictionary GenerateOldTaxrateDict() + { + return new SortedDictionary(TaxRates.ToDictionary(x => x.Key, x => x.Value.VatAmount)); + + } + #endregion + + #region methods + /// + /// Add amounts to taxrate + /// + /// Vat % + /// Item amount + /// Shipping amount + /// Payment method fee amount + public void AddRate(decimal vatRate, decimal itemAmount = 0, decimal shippingAmount = 0, decimal paymentfeeAmount = 0) + { + if (!TaxRates.ContainsKey(vatRate)) + TaxRates.Add(vatRate, new TaxRateEntry() + { + VatRate = vatRate, + SubtotalAmount = itemAmount, + ShippingAmount = shippingAmount, + PaymentFeeAmount = paymentfeeAmount + }); + else + { + TaxRates[vatRate].SubtotalAmount += itemAmount; + TaxRates[vatRate].ShippingAmount += shippingAmount; + TaxRates[vatRate].PaymentFeeAmount += paymentfeeAmount; + } + HasChanged = true; + } + + public void SetSubtotalDiscAmount(decimal totalSubTotalDiscAmount, decimal totalAmount = 0) + { + if (totalAmount == 0) + totalAmount = TaxRates.Sum(x => x.Value.SubtotalAmount); + if (totalSubTotalDiscAmount > totalAmount) + totalSubTotalDiscAmount = totalAmount; + + if (totalAmount != decimal.Zero) + PercentageSubTotalDiscount = totalSubTotalDiscAmount / totalAmount * 100; + HasChanged = true; + } + /// + /// set PaymentFee as discount (used for taxable fee as discount or surcharge) + /// + /// Fee Amount + /// Base amount to which apply fee + public void SetPaymentFeeDiscAmount(decimal totalPaymentFeeDiscAmount, decimal totalAmount) + { + PercentagePaymentFeeDiscount = totalPaymentFeeDiscAmount / totalAmount * 100; + HasChanged = true; + } + + /// + /// Set total invoice discount amount, it will be converted to an internal discount percentage + /// + /// invoice discount amount + public void SetTotalInvDiscAmount(decimal totalInvDiscAmount, decimal totalAmount) + { + if (totalInvDiscAmount > totalAmount) + totalInvDiscAmount = totalAmount; + + PercentageInvDiscount = totalInvDiscAmount / totalAmount * 100; + HasChanged = true; + } + /// + /// calculate amounts and VAT + /// + public void CalculateAmounts() + { + if (!HasChanged) + return; + + //reset totals + TotalShippingAmount = decimal.Zero; + TotalPaymentFeeAmount = decimal.Zero; + TotalAmount = decimal.Zero; + TotalAmountVAT = decimal.Zero; + TotalAmountIncludingVAT = decimal.Zero; + TotalSubTotalAmount = decimal.Zero; + TotalSubTotalDiscAmount = decimal.Zero; + TotalInvDiscAmount = decimal.Zero; + HasChanged = false; + + //init remainder + decimal remainderSubTotalDisc = decimal.Zero; + decimal remainderPaymentFeeDisc = decimal.Zero; + decimal remainderInvDisc = decimal.Zero; + + //calc and sum up tax + foreach (KeyValuePair kvp in TaxRates) + { + decimal vatpercentage = kvp.Key; + TaxRateEntry taxrate = kvp.Value; + + //discounts + if (PercentageSubTotalDiscount != 0) + { + remainderSubTotalDisc += taxrate.SubtotalAmount * PercentageSubTotalDiscount / 100; + taxrate.SubTotalDiscAmount = RoundingHelper.RoundAmount(remainderSubTotalDisc); + remainderSubTotalDisc -= taxrate.SubTotalDiscAmount; + } + + if (PercentagePaymentFeeDiscount != 0) + { + remainderPaymentFeeDisc += (taxrate.SubtotalAmount + taxrate.ShippingAmount - taxrate.SubTotalDiscAmount) * PercentagePaymentFeeDiscount / 100; + taxrate.PaymentFeeAmount = RoundingHelper.RoundAmount(remainderPaymentFeeDisc); + remainderPaymentFeeDisc -= taxrate.PaymentFeeAmount; + } + + //Invoice discount is in sequence to other discounts, i.e. applied to already discounted amounts + if (PercentageInvDiscount != 0) + { + remainderInvDisc += (taxrate.SubtotalAmount + taxrate.ShippingAmount + taxrate.PaymentFeeAmount - taxrate.SubTotalDiscAmount) + * PercentageInvDiscount / 100; + taxrate.InvoiceDiscountAmount = RoundingHelper.RoundAmount(remainderInvDisc); + remainderInvDisc -= taxrate.InvoiceDiscountAmount; + } + + //last remainder get's lost as it can't be considered anywhere else. This has no implication and only lowers or highers discount. + + //VAT: always round VAT first + decimal rateamount = taxrate.SubtotalAmount + taxrate.ShippingAmount + taxrate.PaymentFeeAmount - taxrate.SubTotalDiscAmount - taxrate.InvoiceDiscountAmount; + if (PricesIncludeTax) + { + taxrate.AmountIncludingVAT = rateamount; + taxrate.VatAmount = RoundingHelper.RoundAmount(taxrate.AmountIncludingVAT / (100 + vatpercentage) * vatpercentage); // this is (1+p/100) * p/100 + taxrate.BaseAmount = taxrate.AmountIncludingVAT - taxrate.VatAmount; + } + else + { + taxrate.BaseAmount = rateamount; + taxrate.VatAmount = RoundingHelper.RoundAmount(taxrate.BaseAmount * vatpercentage / 100); + taxrate.AmountIncludingVAT = taxrate.BaseAmount + taxrate.VatAmount; + } + + + //totals + TotalSubTotalAmount += taxrate.SubtotalAmount; + TotalShippingAmount += taxrate.ShippingAmount; + TotalPaymentFeeAmount += taxrate.PaymentFeeAmount; + + if (PercentageSubTotalDiscount != 0) + TotalSubTotalDiscAmount += taxrate.SubTotalDiscAmount; + if (PercentageInvDiscount != 0) + TotalInvDiscAmount += taxrate.InvoiceDiscountAmount; + + TotalAmount += taxrate.BaseAmount; + TotalAmountVAT += taxrate.VatAmount; + TotalAmountIncludingVAT += taxrate.AmountIncludingVAT; + + } + } + #endregion + } +} diff --git a/src/Libraries/Nop.Services/packages.config b/src/Libraries/Nop.Services/packages.config index fc47eb76dab..f5b4865d37c 100644 --- a/src/Libraries/Nop.Services/packages.config +++ b/src/Libraries/Nop.Services/packages.config @@ -4,6 +4,7 @@ + diff --git a/src/Presentation/Nop.Web/Administration/Controllers/OrderController.cs b/src/Presentation/Nop.Web/Administration/Controllers/OrderController.cs index 474ba14d068..719d22dfe95 100644 --- a/src/Presentation/Nop.Web/Administration/Controllers/OrderController.cs +++ b/src/Presentation/Nop.Web/Administration/Controllers/OrderController.cs @@ -101,8 +101,8 @@ public partial class OrderController : BaseAdminController #region Ctor - public OrderController(IOrderService orderService, - IOrderReportService orderReportService, + public OrderController(IOrderService orderService, + IOrderReportService orderReportService, IOrderProcessingService orderProcessingService, IReturnRequestService returnRequestService, IPriceCalculationService priceCalculationService, @@ -124,15 +124,15 @@ public OrderController(IOrderService orderService, IExportManager exportManager, IPermissionService permissionService, IWorkflowMessageService workflowMessageService, - ICategoryService categoryService, + ICategoryService categoryService, IManufacturerService manufacturerService, - IProductAttributeService productAttributeService, + IProductAttributeService productAttributeService, IProductAttributeParser productAttributeParser, - IProductAttributeFormatter productAttributeFormatter, + IProductAttributeFormatter productAttributeFormatter, IShoppingCartService shoppingCartService, - IGiftCardService giftCardService, + IGiftCardService giftCardService, IDownloadService downloadService, - IShipmentService shipmentService, + IShipmentService shipmentService, IShippingService shippingService, IStoreService storeService, IVendorService vendorService, @@ -144,7 +144,7 @@ public OrderController(IOrderService orderService, ICustomerActivityService customerActivityService, ICacheManager cacheManager, OrderSettings orderSettings, - CurrencySettings currencySettings, + CurrencySettings currencySettings, TaxSettings taxSettings, MeasureSettings measureSettings, AddressSettings addressSettings, @@ -199,7 +199,7 @@ public OrderController(IOrderService orderService, this._addressSettings = addressSettings; this._shippingSettings = shippingSettings; } - + #endregion #region Utilities @@ -457,6 +457,8 @@ protected virtual void PrepareOrderDetailsModel(OrderModel model, Order order) throw new ArgumentNullException("model"); model.Id = order.Id; + model.InvoiceId = order.InvoiceId; + model.InvoiceDateUtc = order.InvoiceDateUtc; model.OrderStatus = order.OrderStatus.GetLocalizedEnum(_localizationService, _workContext); model.OrderStatusId = order.OrderStatusId; model.OrderGuid = order.OrderGuid; @@ -483,7 +485,7 @@ protected virtual void PrepareOrderDetailsModel(OrderModel model, Order order) model.IsLoggedInAsVendor = _workContext.CurrentVendor != null; //custom values model.CustomValues = order.DeserializeCustomValues(); - + #region Order totals var primaryStoreCurrency = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId); @@ -523,7 +525,7 @@ protected virtual void PrepareOrderDetailsModel(OrderModel model, Order order) //tax model.Tax = _priceFormatter.FormatPrice(order.OrderTax, true, false); - SortedDictionary taxRates = order.TaxRatesDictionary; + SortedDictionary taxRates = order.TaxRatesDictionary; bool displayTaxRates = _taxSettings.DisplayTaxRates && taxRates.Any(); bool displayTax = !displayTaxRates; foreach (var tr in order.TaxRatesDictionary) @@ -531,7 +533,10 @@ protected virtual void PrepareOrderDetailsModel(OrderModel model, Order order) model.TaxRates.Add(new OrderModel.TaxRate { Rate = _priceFormatter.FormatTaxRate(tr.Key), - Value = _priceFormatter.FormatPrice(tr.Value, true, false), + Amount = _priceFormatter.FormatPrice(tr.Value.Amount, true, false), + DiscountAmount = _priceFormatter.FormatPrice(tr.Value.DiscountAmount, true, false), + BaseAmount = _priceFormatter.FormatPrice(tr.Value.BaseAmount, true, false), + VatAmount = _priceFormatter.FormatPrice(tr.Value.VatAmount, true, false), }); } model.DisplayTaxRates = displayTaxRates; @@ -564,6 +569,10 @@ protected virtual void PrepareOrderDetailsModel(OrderModel model, Order order) //total model.OrderTotal = _priceFormatter.FormatPrice(order.OrderTotal, true, false); model.OrderTotalValue = order.OrderTotal; + model.OrderAmount = _priceFormatter.FormatPrice(order.OrderAmount, true, false); + model.OrderAmountValue = order.OrderAmount; + model.OrderAmountIncl = _priceFormatter.FormatPrice(order.OrderAmountIncl, true, false); + model.OrderAmountInclValue = order.OrderAmountIncl; //refunded amount if (order.RefundedAmount > decimal.Zero) @@ -639,7 +648,7 @@ protected virtual void PrepareOrderDetailsModel(OrderModel model, Order order) model.CanPartiallyRefundOffline = _orderProcessingService.CanPartiallyRefundOffline(order, decimal.Zero); model.CanVoid = _orderProcessingService.CanVoid(order); model.CanVoidOffline = _orderProcessingService.CanVoidOffline(order); - + model.PrimaryStoreCurrencyCode = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId).CurrencyCode; model.MaxAmountToRefund = order.OrderTotal - order.RefundedAmount; @@ -759,7 +768,8 @@ protected virtual void PrepareOrderDetailsModel(OrderModel model, Order order) IsDownload = orderItem.Product.IsDownload, DownloadCount = orderItem.DownloadCount, DownloadActivationType = orderItem.Product.DownloadActivationType, - IsDownloadActivated = orderItem.IsDownloadActivated + IsDownloadActivated = orderItem.IsDownloadActivated, + VatRate = orderItem.VatRate }; //picture var orderItemPicture = orderItem.Product.GetProductPicture(orderItem.AttributesXml, _pictureService, _productAttributeParser); @@ -853,7 +863,8 @@ protected virtual OrderModel.AddOrderProductModel.ProductDetailsModel PrepareAdd Quantity = presetQty, SubTotalExclTax = presetPriceExclTax, SubTotalInclTax = presetPriceInclTax, - AutoUpdateOrderTotals = _orderSettings.AutoUpdateOrderTotalsOnEditingOrder + AutoUpdateOrderTotals = _orderSettings.AutoUpdateOrderTotalsOnEditingOrder, + VatRate = taxRate }; //attributes @@ -1114,7 +1125,7 @@ public virtual ActionResult OrderList(DataSourceRequest command, OrderListModel DateTime? startDateValue = (model.StartDate == null) ? null : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); - DateTime? endDateValue = (model.EndDate == null) ? null + DateTime? endDateValue = (model.EndDate == null) ? null :(DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); var orderStatusIds = !model.OrderStatusIds.Contains(0) ? model.OrderStatusIds : null; @@ -1132,7 +1143,7 @@ public virtual ActionResult OrderList(DataSourceRequest command, OrderListModel productId: filterByProductId, warehouseId: model.WarehouseId, paymentMethodSystemName: model.PaymentMethodSystemName, - createdFromUtc: startDateValue, + createdFromUtc: startDateValue, createdToUtc: endDateValue, osIds: orderStatusIds, psIds: paymentStatusIds, @@ -1141,7 +1152,7 @@ public virtual ActionResult OrderList(DataSourceRequest command, OrderListModel billingLastName: model.BillingLastName, billingCountryId: model.BillingCountryId, orderNotes: model.OrderNotes, - pageIndex: command.Page - 1, + pageIndex: command.Page - 1, pageSize: command.PageSize); var gridModel = new DataSourceResult { @@ -1192,7 +1203,7 @@ public virtual ActionResult OrderList(DataSourceRequest command, OrderListModel osIds: orderStatusIds, psIds: paymentStatusIds, ssIds: shippingStatusIds, - startTimeUtc: startDateValue, + startTimeUtc: startDateValue, endTimeUtc: endDateValue, billingEmail: model.BillingEmail, billingLastName: model.BillingLastName, @@ -1735,7 +1746,7 @@ public virtual ActionResult PartiallyRefundOrderPopup(string btnId, string formI PrepareOrderDetailsModel(model, order); return View(model); } - + //error PrepareOrderDetailsModel(model, order); foreach (var error in errors) @@ -1947,7 +1958,7 @@ public virtual ActionResult PdfInvoiceSelected(string selectedIds) orders = orders.Where(HasAccessToOrder).ToList(); vendorId = _workContext.CurrentVendor.Id; } - + //ensure that we at least one order selected if (!orders.Any()) { @@ -2081,6 +2092,8 @@ public virtual ActionResult EditOrderTotals(int id, OrderModel model) order.OrderTax = model.TaxValue; order.OrderDiscount = model.OrderTotalDiscountValue; order.OrderTotal = model.OrderTotalValue; + order.OrderAmount = model.OrderAmountValue; + order.OrderAmountIncl = model.OrderAmountInclValue; _orderService.UpdateOrder(order); //add a note @@ -2096,7 +2109,60 @@ public virtual ActionResult EditOrderTotals(int id, OrderModel model) PrepareOrderDetailsModel(model, order); return View(model); } + [HttpPost, ActionName("Edit")] + [FormValueRequired("btnSaveIV")] + public ActionResult EditInvoiceInfo(int id, OrderModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return AccessDeniedView(); + + if (ModelState.IsValid) + { + var order = _orderService.GetOrderById(id); + if (order == null) + //No order found with the specified id + return RedirectToAction("List"); + //a vendor does not have access to this functionality + if (_workContext.CurrentVendor != null) + return RedirectToAction("Edit", "Order", new { id = id }); + + //assign invoice id when null + if (model.InvoiceId == null) + { + order.InvoiceId = _orderProcessingService.GetInvoiceId(); + order.InvoiceDateUtc = DateTime.UtcNow; + //not needed, we use redirection now + //ModelState.SetModelValue("InvoiceId", new ValueProviderResult(order.InvoiceId, "", ModelState["InvoiceId"].Value.Culture)); + //ModelState.SetModelValue("InvoiceDateUtc", new ValueProviderResult(order.InvoiceId, "", ModelState["InvoiceDateUtc"].Value.Culture)); + } + else + { + order.InvoiceId = model.InvoiceId; + order.InvoiceDateUtc = model.InvoiceDateUtc; + } + + _orderService.UpdateOrder(order); + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = "Invoice data has been edited", + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + LogEditOrder(order.Id); + + //use redirect instead of passing the new model to have correct browser refresh + //PrepareOrderDetailsModel(model, order); + //return View(model); + return RedirectToAction("Edit", "Order", new { id = id }); + } + + //something failed, redisplay form + return View(model); + } [HttpPost, ActionName("Edit")] [FormValueRequired("save-shipping-method")] public virtual ActionResult EditShippingMethod(int id, OrderModel model) @@ -2133,7 +2199,7 @@ public virtual ActionResult EditShippingMethod(int id, OrderModel model) return View(model); } - + [HttpPost, ActionName("Edit")] [FormValueRequired(FormValueRequirement.StartsWith, "btnSaveOrderItem")] [ValidateInput(false)] @@ -2163,7 +2229,7 @@ public virtual ActionResult EditOrderItem(int id, FormCollection form) decimal unitPriceInclTax, unitPriceExclTax, discountInclTax, discountExclTax,priceInclTax,priceExclTax; - int quantity; + int quantity; decimal vatrate; if (!decimal.TryParse(form["pvUnitPriceInclTax" + orderItemId], out unitPriceInclTax)) unitPriceInclTax = orderItem.UnitPriceInclTax; if (!decimal.TryParse(form["pvUnitPriceExclTax" + orderItemId], out unitPriceExclTax)) @@ -2178,6 +2244,8 @@ public virtual ActionResult EditOrderItem(int id, FormCollection form) priceInclTax = orderItem.PriceInclTax; if (!decimal.TryParse(form["pvPriceExclTax" + orderItemId], out priceExclTax)) priceExclTax = orderItem.PriceExclTax; + if (!decimal.TryParse(form["pvVatRate" + orderItemId], out vatrate)) + vatrate = orderItem.VatRate; if (quantity > 0) { @@ -2192,6 +2260,7 @@ public virtual ActionResult EditOrderItem(int id, FormCollection form) orderItem.DiscountAmountExclTax = discountExclTax; orderItem.PriceInclTax = priceInclTax; orderItem.PriceExclTax = priceExclTax; + orderItem.VatRate = vatrate; _orderService.UpdateOrder(order); } @@ -2221,7 +2290,8 @@ public virtual ActionResult EditOrderItem(int id, FormCollection form) DiscountAmountExclTax = discountExclTax, SubTotalInclTax = priceInclTax, SubTotalExclTax = priceExclTax, - Quantity = quantity + Quantity = quantity, + VatRate = vatrate }; _orderProcessingService.UpdateOrderTotals(updateOrderParameters); @@ -2351,7 +2421,7 @@ public virtual ActionResult ResetDownloadCount(int id, FormCollection form) if (orderItem == null) throw new ArgumentException("No order item found with the specified id"); - //ensure a vendor has access only to his products + //ensure a vendor has access only to his products if (_workContext.CurrentVendor != null && !HasAccessToOrderItem(orderItem)) return RedirectToAction("List"); @@ -2391,7 +2461,7 @@ public virtual ActionResult ActivateDownloadItem(int id, FormCollection form) if (orderItem == null) throw new ArgumentException("No order item found with the specified id"); - //ensure a vendor has access only to his products + //ensure a vendor has access only to his products if (_workContext.CurrentVendor != null && !HasAccessToOrderItem(orderItem)) return RedirectToAction("List"); @@ -2425,7 +2495,7 @@ public virtual ActionResult UploadLicenseFilePopup(int id, int orderItemId) if (!orderItem.Product.IsDownload) throw new ArgumentException("Product is not downloadable"); - //ensure a vendor has access only to his products + //ensure a vendor has access only to his products if (_workContext.CurrentVendor != null && !HasAccessToOrderItem(orderItem)) return RedirectToAction("List"); @@ -2455,7 +2525,7 @@ public virtual ActionResult UploadLicenseFilePopup(string btnId, string formId, if (orderItem == null) throw new ArgumentException("No order item found with the specified id"); - //ensure a vendor has access only to his products + //ensure a vendor has access only to his products if (_workContext.CurrentVendor != null && !HasAccessToOrderItem(orderItem)) return RedirectToAction("List"); @@ -2491,7 +2561,7 @@ public virtual ActionResult DeleteLicenseFilePopup(string btnId, string formId, if (orderItem == null) throw new ArgumentException("No order item found with the specified id"); - //ensure a vendor has access only to his products + //ensure a vendor has access only to his products if (_workContext.CurrentVendor != null && !HasAccessToOrderItem(orderItem)) return RedirectToAction("List"); @@ -2552,8 +2622,8 @@ public virtual ActionResult AddProductToOrder(DataSourceRequest command, OrderMo var products = _productService.SearchProducts(categoryIds: new List {model.SearchCategoryId}, manufacturerId: model.SearchManufacturerId, productType: model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null, - keywords: model.SearchProductName, - pageIndex: command.Page - 1, + keywords: model.SearchProductName, + pageIndex: command.Page - 1, pageSize: command.PageSize, showHidden: true); gridModel.Data = products.Select(x => @@ -2610,6 +2680,8 @@ public virtual ActionResult AddProductToOrderDetails(int orderId, int productId, decimal.TryParse(form["SubTotalInclTax"], out priceInclTax); decimal priceExclTax; decimal.TryParse(form["SubTotalExclTax"], out priceExclTax); + decimal vatRate; + decimal.TryParse(form["VatRate"], out vatRate); //warnings var warnings = new List(); @@ -2703,7 +2775,8 @@ public virtual ActionResult AddProductToOrderDetails(int orderId, int productId, IsDownloadActivated = false, LicenseDownloadId = 0, RentalStartDateUtc = rentalStartDate, - RentalEndDateUtc = rentalEndDate + RentalEndDateUtc = rentalEndDate, + VatRate = vatRate }; order.OrderItems.Add(orderItem); _orderService.UpdateOrder(order); @@ -2721,7 +2794,8 @@ public virtual ActionResult AddProductToOrderDetails(int orderId, int productId, PriceExclTax = unitPriceExclTax, SubTotalInclTax = priceInclTax, SubTotalExclTax = priceExclTax, - Quantity = quantity + Quantity = quantity, + VatRate = vatRate }; _orderProcessingService.UpdateOrderTotals(updateOrderParameters); @@ -2763,7 +2837,7 @@ public virtual ActionResult AddProductToOrderDetails(int orderId, int productId, TempData["nop.admin.order.warnings"] = updateOrderParameters.Warnings; return RedirectToAction("Edit", "Order", new { id = order.Id }); } - + //errors var model = PrepareAddProductToOrderModel(order.Id, product.Id); model.Warnings.AddRange(warnings); @@ -2933,7 +3007,7 @@ public virtual ActionResult AddressEdit(OrderAddressModel model, FormCollection } #endregion - + #region Shipments public virtual ActionResult ShipmentList() @@ -2966,7 +3040,7 @@ public virtual ActionResult ShipmentListSelect(DataSourceRequest command, Shipme DateTime? startDateValue = (model.StartDate == null) ? null : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); - DateTime? endDateValue = (model.EndDate == null) ? null + DateTime? endDateValue = (model.EndDate == null) ? null :(DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); //a vendor should have access only to his products @@ -2976,15 +3050,15 @@ public virtual ActionResult ShipmentListSelect(DataSourceRequest command, Shipme //load shipments var shipments = _shipmentService.GetAllShipments(vendorId: vendorId, - warehouseId: model.WarehouseId, - shippingCountryId: model.CountryId, - shippingStateId: model.StateProvinceId, + warehouseId: model.WarehouseId, + shippingCountryId: model.CountryId, + shippingStateId: model.StateProvinceId, shippingCity: model.City, - trackingNumber: model.TrackingNumber, + trackingNumber: model.TrackingNumber, loadNotShipped: model.LoadNotShipped, - createdFromUtc: startDateValue, - createdToUtc: endDateValue, - pageIndex: command.Page - 1, + createdFromUtc: startDateValue, + createdToUtc: endDateValue, + pageIndex: command.Page - 1, pageSize: command.PageSize); var gridModel = new DataSourceResult { @@ -3171,7 +3245,7 @@ public virtual ActionResult AddShipment(int orderId) }); } } - + model.Items.Add(shipmentItemModel); } @@ -3208,7 +3282,7 @@ public virtual ActionResult AddShipment(int orderId, FormCollection form, bool c //is shippable if (!orderItem.Product.IsShipEnabled) continue; - + //ensure that this product can be shipped (have at least one item to ship) var maxQtyToAdd = orderItem.GetTotalNumberOfItemsCanBeAddedToShipment(); if (maxQtyToAdd <= 0) @@ -3253,7 +3327,7 @@ public virtual ActionResult AddShipment(int orderId, FormCollection form, bool c continue; if (qtyToAdd > maxQtyToAdd) qtyToAdd = maxQtyToAdd; - + //ok. we have at least one item. let's create a shipment (if it does not exist) var orderItemTotalWeight = orderItem.ItemWeight.HasValue ? orderItem.ItemWeight * qtyToAdd : null; @@ -3309,7 +3383,7 @@ public virtual ActionResult AddShipment(int orderId, FormCollection form, bool c ? RedirectToAction("ShipmentDetails", new {id = shipment.Id}) : RedirectToAction("Edit", new { id = orderId }); } - + ErrorNotification(_localizationService.GetResource("Admin.Orders.Shipments.NoProductsSelected")); return RedirectToAction("AddShipment", new { orderId = orderId }); } @@ -3564,7 +3638,7 @@ public virtual ActionResult PdfPackagingSlip(int shipmentId) var shipments = new List(); shipments.Add(shipment); - + byte[] bytes; using (var stream = new MemoryStream()) { @@ -3672,7 +3746,7 @@ public virtual ActionResult SetAsShippedSelected(ICollection selectedIds) { shipments = shipments.Where(HasAccessToShipment).ToList(); } - + foreach (var shipment in shipments) { try @@ -3719,11 +3793,11 @@ public virtual ActionResult SetAsDeliveredSelected(ICollection selectedIds) return Json(new { Result = true }); } - + #endregion #region Order notes - + [HttpPost] public virtual ActionResult OrderNotesSelect(int orderId, DataSourceRequest command) { @@ -3764,7 +3838,7 @@ public virtual ActionResult OrderNotesSelect(int orderId, DataSourceRequest comm return Json(gridModel); } - + [ValidateInput(false)] public virtual ActionResult OrderNoteAdd(int orderId, int downloadId, bool displayToCustomer, string message) { @@ -3883,7 +3957,7 @@ public virtual ActionResult BestsellersBriefReportByAmount() { if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) return Content(""); - + return PartialView(); } [HttpPost] @@ -4058,7 +4132,7 @@ public virtual ActionResult NeverSoldReportList(DataSourceRequest command, Never DateTime? endDateValue = (model.EndDate == null) ? null : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); - + var items = _orderReportService.ProductsNeverSold(vendorId: model.SearchVendorId, storeId: model.SearchStoreId, categoryId: model.SearchCategoryId, diff --git a/src/Presentation/Nop.Web/Administration/Infrastructure/Mapper/AdminMapperConfiguration.cs b/src/Presentation/Nop.Web/Administration/Infrastructure/Mapper/AdminMapperConfiguration.cs index 353da6c47be..058da925e2e 100644 --- a/src/Presentation/Nop.Web/Administration/Infrastructure/Mapper/AdminMapperConfiguration.cs +++ b/src/Presentation/Nop.Web/Administration/Infrastructure/Mapper/AdminMapperConfiguration.cs @@ -898,6 +898,7 @@ public Action GetConfiguration() cfg.CreateMap() .ForMember(dest => dest.PrimaryStoreCurrencyCode, mo => mo.Ignore()) .ForMember(dest => dest.OrderIdent, mo => mo.Ignore()) + .ForMember(dest => dest.InvoiceIdent, mo => mo.Ignore()) .ForMember(dest => dest.ActiveStoreScopeConfiguration, mo => mo.Ignore()) .ForMember(dest => dest.IsReOrderAllowed_OverrideForStore, mo => mo.Ignore()) .ForMember(dest => dest.MinOrderSubtotalAmount_OverrideForStore, mo => mo.Ignore()) diff --git a/src/Presentation/Nop.Web/Administration/Models/Orders/OrderModel.cs b/src/Presentation/Nop.Web/Administration/Models/Orders/OrderModel.cs index 4dbf33fa43f..9c6a493b48e 100644 --- a/src/Presentation/Nop.Web/Administration/Models/Orders/OrderModel.cs +++ b/src/Presentation/Nop.Web/Administration/Models/Orders/OrderModel.cs @@ -28,6 +28,12 @@ public OrderModel() public override int Id { get; set; } [NopResourceDisplayName("Admin.Orders.Fields.OrderGuid")] public Guid OrderGuid { get; set; } + + [NopResourceDisplayName("Admin.Orders.Fields.InvoiceId")] + public string InvoiceId { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.InvoiceDateUtc")] + [UIHint("DateTimeNullable")] + public DateTime? InvoiceDateUtc { get; set; } [NopResourceDisplayName("Admin.Orders.Fields.CustomOrderNumber")] public string CustomOrderNumber { get; set; } @@ -94,6 +100,10 @@ public OrderModel() public string RefundedAmount { get; set; } [NopResourceDisplayName("Admin.Orders.Fields.Profit")] public string Profit { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.OrderAmount")] + public string OrderAmount { get; set; } //MF 09.12.16 + [NopResourceDisplayName("Admin.Orders.Fields.OrderAmountIncl")] + public string OrderAmountIncl { get; set; } //MF 09.12.16 //edit totals [NopResourceDisplayName("Admin.Orders.Fields.Edit.OrderSubtotal")] @@ -120,6 +130,10 @@ public OrderModel() public decimal OrderTotalDiscountValue { get; set; } [NopResourceDisplayName("Admin.Orders.Fields.Edit.OrderTotal")] public decimal OrderTotalValue { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.Edit.OrderAmount")] + public decimal OrderAmountValue { get; set; } //MF 09.12.16 + [NopResourceDisplayName("Admin.Orders.Fields.Edit.OrderAmountIncl")] + public decimal OrderAmountInclValue { get; set; } //MF 09.12.16 //associated recurring payment id [NopResourceDisplayName("Admin.Orders.Fields.RecurringPayment")] @@ -190,7 +204,7 @@ public OrderModel() public AddressModel BillingAddress { get; set; } [NopResourceDisplayName("Admin.Orders.Fields.VatNumber")] public string VatNumber { get; set; } - + //gift cards public IList GiftCards { get; set; } @@ -281,6 +295,7 @@ public OrderItemModel() public DownloadActivationType DownloadActivationType { get; set; } public bool IsDownloadActivated { get; set; } public Guid LicenseDownloadGuid { get; set; } + public decimal VatRate { get; set; } #region Nested Classes @@ -294,8 +309,16 @@ public partial class ReturnRequestBriefModel : BaseNopEntityModel public partial class TaxRate : BaseNopModel { + [NopResourceDisplayName("Order.TaxRateLine.VatRate")] public string Rate { get; set; } - public string Value { get; set; } + [NopResourceDisplayName("Order.TaxRateLine.Amount")] + public string Amount { get; set; } // includes subtotal, shipping and payment fee + [NopResourceDisplayName("Order.TaxRateLine.DiscountAmount")] + public string DiscountAmount { get; set; } + [NopResourceDisplayName("Order.TaxRateLine.BaseAmount")] + public string BaseAmount { get; set; } + [NopResourceDisplayName("Order.TaxRateLine.VatAmount")] + public string VatAmount { get; set; } } public partial class GiftCard : BaseNopModel @@ -357,7 +380,7 @@ public AddOrderProductModel() public int OrderId { get; set; } #region Nested classes - + public partial class ProductModel : BaseNopEntityModel { [NopResourceDisplayName("Admin.Orders.Products.AddNew.Name")] @@ -398,6 +421,8 @@ public ProductDetailsModel() public decimal SubTotalInclTax { get; set; } [NopResourceDisplayName("Admin.Orders.Products.AddNew.SubTotalExclTax")] public decimal SubTotalExclTax { get; set; } + [NopResourceDisplayName("ShoppingCart.VatRate")] + public decimal VatRate { get; set; } //product attributes public IList ProductAttributes { get; set; } diff --git a/src/Presentation/Nop.Web/Administration/Models/Settings/OrderSettingsModel.cs b/src/Presentation/Nop.Web/Administration/Models/Settings/OrderSettingsModel.cs index bc3c8f4b536..e7410dc2d6f 100644 --- a/src/Presentation/Nop.Web/Administration/Models/Settings/OrderSettingsModel.cs +++ b/src/Presentation/Nop.Web/Administration/Models/Settings/OrderSettingsModel.cs @@ -98,6 +98,8 @@ public partial class OrderSettingsModel : BaseNopModel [NopResourceDisplayName("Admin.Configuration.Settings.Order.OrderIdent")] public int? OrderIdent { get; set; } + [NopResourceDisplayName("Admin.Configuration.Settings.Order.InvoiceIdent")] + public int InvoiceIdent { get; set; } [NopResourceDisplayName("Admin.Configuration.Settings.Order.CustomOrderNumberMask")] public string CustomOrderNumberMask { get; set; } diff --git a/src/Presentation/Nop.Web/Administration/Views/Order/AddProductToOrderDetails.cshtml b/src/Presentation/Nop.Web/Administration/Views/Order/AddProductToOrderDetails.cshtml index 17ef294bc0f..db6b807f9f8 100644 --- a/src/Presentation/Nop.Web/Administration/Views/Order/AddProductToOrderDetails.cshtml +++ b/src/Presentation/Nop.Web/Administration/Views/Order/AddProductToOrderDetails.cshtml @@ -86,6 +86,14 @@ @Html.NopEditorFor(model => model.SubTotalExclTax) +
+
+ @Html.NopLabelFor(model => model.VatRate) +
+
+ @Html.NopEditorFor(model => model.VatRate) +
+
diff --git a/src/Presentation/Nop.Web/Administration/Views/Order/_OrderDetails.Info.cshtml b/src/Presentation/Nop.Web/Administration/Views/Order/_OrderDetails.Info.cshtml index 71e69d98983..4e2e379c0d4 100644 --- a/src/Presentation/Nop.Web/Administration/Views/Order/_OrderDetails.Info.cshtml +++ b/src/Presentation/Nop.Web/Administration/Views/Order/_OrderDetails.Info.cshtml @@ -10,6 +10,7 @@ toggleChangeOrderStatus(false); toggleOrderTotals(false); toggleCC(false); + toggleIV(false); }); function toggleChangeOrderStatus(editmode) { @@ -71,6 +72,25 @@ $('#btnCancelCC').hide(); } } + function toggleIV(editmode) { + if (editmode) { + $('#lblIvId').hide(); + $('#lblIvEdit').show(); + $('#lblIvDate').hide(); + $('#lblIvEditDate').show(); + $('#btnEditIV').hide(); + $('#btnSaveIV').show(); + $('#btnCancelIV').show(); + } else { + $('#lblIvId').show(); + $('#lblIvEdit').hide(); + $('#lblIvDate').show(); + $('#lblIvEditDate').hide(); + $('#btnEditIV').show(); + $('#btnSaveIV').hide(); + $('#btnCancelIV').hide(); + } + } }
@@ -154,6 +174,57 @@
} + @if (!Model.IsLoggedInAsVendor) + { +
+
+ @Html.NopLabelFor(model => model.InvoiceId) +
+
+
+ @Html.NopDisplayFor(model => model.InvoiceId) +
+
+ @Html.NopEditorFor(model => model.InvoiceId) +
+
+
+ } + @if (!Model.IsLoggedInAsVendor) + { +
+
+ @Html.NopLabelFor(model => model.InvoiceDateUtc) +
+
+
+ @Html.NopDisplayFor(model => model.InvoiceDateUtc) +
+
+ @Html.NopEditorFor(model => model.InvoiceDateUtc) +
+
+
+ } + @if (!Model.IsLoggedInAsVendor) + { +
+
+ + + + @Html.ActionConfirmation("btnSaveIV") + + +
+
+ }
@Html.NopLabelFor(model => model.StoreName) @@ -167,8 +238,8 @@ @if (!Model.IsLoggedInAsVendor) { -
-
+
+
@Html.NopLabelFor(model => model.CustomerId) @@ -233,7 +304,7 @@ {
- @Html.DisplayFor(model => model.OrderSubtotalInclTax) @T("Admin.Orders.Fields.OrderSubtotalInclTax") + @Html.DisplayFor(model => model.OrderSubtotalInclTax) @*@T("Admin.Orders.Fields.OrderSubtotalInclTax")*@
} @@ -241,7 +312,7 @@ {
- @Html.DisplayFor(model => model.OrderSubtotalExclTax) @T("Admin.Orders.Fields.OrderSubtotalExclTax") + @Html.DisplayFor(model => model.OrderSubtotalExclTax) @*@T("Admin.Orders.Fields.OrderSubtotalExclTax")*@
} @@ -266,7 +337,7 @@ {
- @Html.DisplayFor(model => model.OrderSubTotalDiscountInclTax) @T("Admin.Orders.Fields.OrderSubTotalDiscountInclTax") + @Html.DisplayFor(model => model.OrderSubTotalDiscountInclTax) @*@T("Admin.Orders.Fields.OrderSubTotalDiscountInclTax")*@
} @@ -274,7 +345,7 @@ {
- @Html.DisplayFor(model => model.OrderSubTotalDiscountExclTax) @T("Admin.Orders.Fields.OrderSubTotalDiscountExclTax") + @Html.DisplayFor(model => model.OrderSubTotalDiscountExclTax) @*@T("Admin.Orders.Fields.OrderSubTotalDiscountExclTax")*@
} @@ -297,7 +368,7 @@ {
- @Html.DisplayFor(model => model.OrderShippingInclTax) @T("Admin.Orders.Fields.OrderShippingInclTax") + @Html.DisplayFor(model => model.OrderShippingInclTax) @*@T("Admin.Orders.Fields.OrderShippingInclTax")*@
} @@ -305,7 +376,7 @@ {
- @Html.DisplayFor(model => model.OrderShippingExclTax) @T("Admin.Orders.Fields.OrderShippingExclTax") + @Html.DisplayFor(model => model.OrderShippingExclTax) @*@T("Admin.Orders.Fields.OrderShippingExclTax")*@
} @@ -330,7 +401,7 @@ {
- @Html.DisplayFor(model => model.PaymentMethodAdditionalFeeInclTax) @T("Admin.Orders.Fields.PaymentMethodAdditionalFeeInclTax") + @Html.DisplayFor(model => model.PaymentMethodAdditionalFeeInclTax) @*@T("Admin.Orders.Fields.PaymentMethodAdditionalFeeInclTax")*@
} @@ -338,7 +409,7 @@ {
- @Html.DisplayFor(model => model.PaymentMethodAdditionalFeeExclTax) @T("Admin.Orders.Fields.PaymentMethodAdditionalFeeExclTax") + @Html.DisplayFor(model => model.PaymentMethodAdditionalFeeExclTax) @*@T("Admin.Orders.Fields.PaymentMethodAdditionalFeeExclTax")*@
} @@ -355,7 +426,7 @@ @Html.NopLabelFor(model => model.Tax) @tr.Rate%
- @tr.Value + @Html.NopDisplayFor(model => tr.VatAmount)
} @@ -382,6 +453,22 @@
} +
+
+ @Html.NopLabelFor(model => model.OrderAmount) +
+
+ @Html.NopDisplayFor(model => model.OrderAmount) +
+
+
+
+ @Html.NopLabelFor(model => model.OrderAmountIncl) +
+
+ @Html.NopDisplayFor(model => model.OrderAmountIncl) +
+
@foreach (var gc in Model.GiftCards) {
@@ -536,6 +623,22 @@ @Html.NopEditorFor(model => model.OrderTotalDiscountValue)
+
+
+ @Html.NopLabelFor(model => model.OrderAmountValue) +
+
+ @Html.NopEditorFor(model => model.OrderAmountValue) +
+
+
+
+ @Html.NopLabelFor(model => model.OrderAmountInclValue) +
+
+ @Html.NopEditorFor(model => model.OrderAmountInclValue) +
+
@Html.NopLabelFor(model => model.OrderTotalValue) @@ -705,8 +808,8 @@ @Html.NopDisplayFor(model => model.PaymentMethod)
+
-
}
diff --git a/src/Presentation/Nop.Web/Administration/Views/Order/_OrderDetails.Products.cshtml b/src/Presentation/Nop.Web/Administration/Views/Order/_OrderDetails.Products.cshtml index ccedc9379bb..83ca5a0ba76 100644 --- a/src/Presentation/Nop.Web/Administration/Views/Order/_OrderDetails.Products.cshtml +++ b/src/Presentation/Nop.Web/Administration/Views/Order/_OrderDetails.Products.cshtml @@ -22,6 +22,7 @@ $('#pnlEditPvQuantity@(item.Id)').show(); $('#pnlEditPvDiscount@(item.Id)').show(); $('#pnlEditPvPrice@(item.Id)').show(); + $('#pnlEditPvVatRate@(item.Id)').show(); $('#btnEditOrderItem@(item.Id)').hide(); $('#btnDeleteOrderItem@(item.Id)').hide(); $('#btnSaveOrderItem@(item.Id)').show(); @@ -31,6 +32,7 @@ $('#pnlEditPvQuantity@(item.Id)').hide(); $('#pnlEditPvDiscount@(item.Id)').hide(); $('#pnlEditPvPrice@(item.Id)').hide(); + $('#pnlEditPvVatRate@(item.Id)').hide() $('#btnEditOrderItem@(item.Id)').show(); $('#btnDeleteOrderItem@(item.Id)').show(); $('#btnSaveOrderItem@(item.Id)').hide(); @@ -81,6 +83,9 @@ @T("Admin.Orders.Products.Total") + + @T("Order.TaxRateLine.VatRate") + @if (!Model.IsLoggedInAsVendor) { @@ -339,6 +344,16 @@
+ +
@item.VatRate.ToString("G29")
+
+
+
+ +
+
+
+ @if (!Model.IsLoggedInAsVendor) { diff --git a/src/Presentation/Nop.Web/App_Data/Install/SqlServer.Indexes.sql b/src/Presentation/Nop.Web/App_Data/Install/SqlServer.Indexes.sql index 5457f1f81d3..fa8e42a87d2 100644 --- a/src/Presentation/Nop.Web/App_Data/Install/SqlServer.Indexes.sql +++ b/src/Presentation/Nop.Web/App_Data/Install/SqlServer.Indexes.sql @@ -37,6 +37,9 @@ GO CREATE NONCLUSTERED INDEX [IX_Order_CustomerId] ON [Order] ([CustomerId] ASC) GO +CREATE UNIQUE NONCLUSTERED INDEX UI_Order_InvoiceId ON dbo.[Order](InvoiceId) +WHERE InvoiceId IS NOT NULL; + CREATE NONCLUSTERED INDEX [IX_Language_DisplayOrder] ON [Language] ([DisplayOrder] ASC) GO diff --git a/src/Presentation/Nop.Web/App_Data/Localization/defaultResources.nopres.xml b/src/Presentation/Nop.Web/App_Data/Localization/defaultResources.nopres.xml index ef2f59ad92d..ba88290eb55 100644 --- a/src/Presentation/Nop.Web/App_Data/Localization/defaultResources.nopres.xml +++ b/src/Presentation/Nop.Web/App_Data/Localization/defaultResources.nopres.xml @@ -2507,7 +2507,7 @@ The product has been copied successfully - + Copy product @@ -4595,7 +4595,7 @@ At least one published language is required - + String resources @@ -6107,7 +6107,7 @@ Captcha is enabled but the appropriate keys are not entered - + CAPTCHA enabled @@ -7011,6 +7011,12 @@ Require customers to accept or decline terms of service before processing the order (on the shopping cart page). + + Last used Invoice No. + + + Invoice ID counter. This is useful if you want your invoices to start from certain number. This only affects invoices henceforward. The value must be greater than the current maximum invoice ID. + Customer roles @@ -8495,7 +8501,7 @@ The message template has been copied successfully - + Copy template @@ -9467,7 +9473,7 @@ Add the customer to 'Guests' or 'Registered' customer role - + The new customer has been added successfully. @@ -9809,7 +9815,7 @@ The customer cannot be in both 'Guests' and 'Registered' customer roles - + Place order (impersonate) @@ -9893,7 +9899,7 @@ A non-admin user cannot impersonate as an administrator - + Orders @@ -10031,7 +10037,7 @@ Valid Email is required for customer to be in 'Registered' role - + Note: if you have a vendor associated with this customer, then also ensure it is in "Vendors" customer role. @@ -10599,6 +10605,24 @@ Edit order totals + + Amount + + + Edit Amount + + + Amount incl. Tax + + + Edit Amount incl. Tax + + + Edit Invoice data. + + + Save Invoice Data. + Gift card @@ -10827,6 +10851,30 @@ Void (Offline) + + Amount + + + Total order base amount. + + + Amount incl. Tax + + + Total order amount incl. tax + + + Invoice No. + + + Invoice No. + + + Invoice Date + + + Invoice Date + Info @@ -10922,7 +10970,7 @@ This order item has an associated gift card record. Please delete it first - + Order notes @@ -12053,6 +12101,7 @@ Order item is deleted + ID @@ -14706,6 +14755,15 @@ Discount: + + Enthaltene USt.: + + + Betrag: + + + Betrag inkl. USt.: + News @@ -15006,6 +15064,42 @@ VAT number + + Included Tax + + + Amount + + + Amount incl. Tax + + + Invoice No. + + + Invoice Date + + + Tax % + + + Amount + + + Amount incl. Tax + + + Discount + + + Discount incl. tax + + + Base Amount + + + VAT Amount + Page {0} of {1} ({2} total) @@ -15222,6 +15316,12 @@ Order# {0} + + ORDER + + + VatID: {0} + Date: {0} @@ -15291,6 +15391,54 @@ VAT number: {0} + + Tax % + + + Invoice + + + Invoice Date + + + Amount + + + Amount incl. tax + + + Discount + + + Discount incl. tax + + + VAT Amount + + + Amount to pay + + + Continued on next page ... + + + Additional items + + + Invoice discount + + + Currency + + + Page + + + Base Ammount + + + Total Base + Address: {0} @@ -16414,10 +16562,10 @@ {0} remaining - Total + Total to pay - Discount + Invoice Discount Payment method additional fee @@ -16449,6 +16597,18 @@ Tax + + Included Tax + + + Total Amount + + + Total Amount incl. Tax + + + Tax % + Tax {0}% diff --git a/src/Presentation/Nop.Web/Factories/CheckoutModelFactory.cs b/src/Presentation/Nop.Web/Factories/CheckoutModelFactory.cs index aa58e4d9c67..4950e9d283e 100644 --- a/src/Presentation/Nop.Web/Factories/CheckoutModelFactory.cs +++ b/src/Presentation/Nop.Web/Factories/CheckoutModelFactory.cs @@ -369,7 +369,7 @@ public virtual CheckoutPaymentMethodModel PreparePaymentMethodModel(IList decimal.Zero) + if (rate != decimal.Zero) //tfc allow negative fee pmModel.Fee = _priceFormatter.FormatPaymentMethodAdditionalFee(rate, true); model.PaymentMethods.Add(pmModel); diff --git a/src/Presentation/Nop.Web/Factories/OrderModelFactory.cs b/src/Presentation/Nop.Web/Factories/OrderModelFactory.cs index f83f1501a1c..fc52043cbb2 100644 --- a/src/Presentation/Nop.Web/Factories/OrderModelFactory.cs +++ b/src/Presentation/Nop.Web/Factories/OrderModelFactory.cs @@ -55,17 +55,17 @@ public partial class OrderModelFactory : IOrderModelFactory #region Constructors - public OrderModelFactory(IAddressModelFactory addressModelFactory, + public OrderModelFactory(IAddressModelFactory addressModelFactory, IOrderService orderService, IWorkContext workContext, ICurrencyService currencyService, IPriceFormatter priceFormatter, - IOrderProcessingService orderProcessingService, + IOrderProcessingService orderProcessingService, IDateTimeHelper dateTimeHelper, - IPaymentService paymentService, + IPaymentService paymentService, ILocalizationService localizationService, IShippingService shippingService, - ICountryService countryService, + ICountryService countryService, IProductAttributeParser productAttributeParser, IDownloadService downloadService, IStoreContext storeContext, @@ -74,7 +74,7 @@ public OrderModelFactory(IAddressModelFactory addressModelFactory, CatalogSettings catalogSettings, OrderSettings orderSettings, TaxSettings taxSettings, - ShippingSettings shippingSettings, + ShippingSettings shippingSettings, AddressSettings addressSettings, RewardPointsSettings rewardPointsSettings, PdfSettings pdfSettings) @@ -169,6 +169,7 @@ public virtual OrderDetailsModel PrepareOrderDetailsModel(Order order) model.IsReOrderAllowed = _orderSettings.IsReOrderAllowed; model.IsReturnRequestAllowed = _orderProcessingService.IsReturnRequestAllowed(order); model.PdfInvoiceDisabled = _pdfSettings.DisablePdfInvoicesForPendingOrders && order.OrderStatus == OrderStatus.Pending; + model.includingTax = order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax; //MF 26.11.2016 model.CustomOrderNumber = order.CustomOrderNumber; //shipping info @@ -194,7 +195,7 @@ public virtual OrderDetailsModel PrepareOrderDetailsModel(Order order) ZipPostalCode = order.PickupAddress.ZipPostalCode }; model.ShippingMethod = order.ShippingMethod; - + //shipments (only already shipped) var shipments = order.Shipments.Where(x => x.ShippedDateUtc.HasValue).OrderBy(x => x.CreatedOnUtc).ToList(); @@ -312,7 +313,10 @@ public virtual OrderDetailsModel PrepareOrderDetailsModel(Order order) { Rate = _priceFormatter.FormatTaxRate(tr.Key), //TODO pass languageId to _priceFormatter.FormatPrice - Value = _priceFormatter.FormatPrice(_currencyService.ConvertCurrency(tr.Value, order.CurrencyRate), true, order.CustomerCurrencyCode, false, _workContext.WorkingLanguage), + Amount = _priceFormatter.FormatPrice(_currencyService.ConvertCurrency(tr.Value.Amount, order.CurrencyRate), true, order.CustomerCurrencyCode, false, _workContext.WorkingLanguage), + DiscountAmount = _priceFormatter.FormatPrice(_currencyService.ConvertCurrency(tr.Value.DiscountAmount, order.CurrencyRate), true, order.CustomerCurrencyCode, false, _workContext.WorkingLanguage), + BaseAmount = _priceFormatter.FormatPrice(_currencyService.ConvertCurrency(tr.Value.BaseAmount, order.CurrencyRate), true, order.CustomerCurrencyCode, false, _workContext.WorkingLanguage), + VatAmount = _priceFormatter.FormatPrice(_currencyService.ConvertCurrency(tr.Value.VatAmount, order.CurrencyRate), true, order.CustomerCurrencyCode, false, _workContext.WorkingLanguage) }); } } @@ -322,10 +326,15 @@ public virtual OrderDetailsModel PrepareOrderDetailsModel(Order order) model.DisplayTaxShippingInfo = _catalogSettings.DisplayTaxShippingInfoOrderDetailsPage; model.PricesIncludeTax = order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax; + var orderAmountInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderAmount, order.CurrencyRate); + model.OrderAmount = _priceFormatter.FormatPrice(orderAmountInCustomerCurrency, true, order.CustomerCurrencyCode, false, _workContext.WorkingLanguage); + var orderAmountInclInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderAmountIncl, order.CurrencyRate); + model.OrderAmountIncl = _priceFormatter.FormatPrice(orderAmountInclInCustomerCurrency, true, order.CustomerCurrencyCode, true, _workContext.WorkingLanguage); + //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, _workContext.WorkingLanguage); + model.OrderTotalDiscount = _priceFormatter.FormatPrice(-orderDiscountInCustomerCurrency, true, order.CustomerCurrencyCode, model.includingTax, _workContext.WorkingLanguage); //gift cards @@ -338,7 +347,7 @@ public virtual OrderDetailsModel PrepareOrderDetailsModel(Order order) }); } - //reward points + //reward points if (order.RedeemedRewardPointsEntry != null) { model.RedeemedRewardPoints = -order.RedeemedRewardPointsEntry.Points; @@ -383,6 +392,7 @@ public virtual OrderDetailsModel PrepareOrderDetailsModel(Order order) ProductSeName = orderItem.Product.GetSeName(), Quantity = orderItem.Quantity, AttributeInfo = orderItem.AttributeDescription, + VatRate = orderItem.VatRate }; //rental info if (orderItem.Product.IsRental) @@ -433,13 +443,13 @@ public virtual ShipmentDetailsModel PrepareShipmentDetailsModel(Shipment shipmen if (order == null) throw new Exception("order cannot be loaded"); var model = new ShipmentDetailsModel(); - + model.Id = shipment.Id; 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 if (!String.IsNullOrEmpty(shipment.TrackingNumber)) { @@ -500,7 +510,7 @@ public virtual ShipmentDetailsModel PrepareShipmentDetailsModel(Shipment shipmen //order details model model.Order = PrepareOrderDetailsModel(order); - + return model; } diff --git a/src/Presentation/Nop.Web/Factories/ShoppingCartModelFactory.cs b/src/Presentation/Nop.Web/Factories/ShoppingCartModelFactory.cs index 432b60d6ae4..14c74bf7ee5 100644 --- a/src/Presentation/Nop.Web/Factories/ShoppingCartModelFactory.cs +++ b/src/Presentation/Nop.Web/Factories/ShoppingCartModelFactory.cs @@ -84,40 +84,40 @@ public partial class ShoppingCartModelFactory : IShoppingCartModelFactory #region Ctor - public ShoppingCartModelFactory(IAddressModelFactory addressModelFactory, + public ShoppingCartModelFactory(IAddressModelFactory addressModelFactory, IStoreContext storeContext, IWorkContext workContext, - IShoppingCartService shoppingCartService, + IShoppingCartService shoppingCartService, IPictureService pictureService, - ILocalizationService localizationService, + ILocalizationService localizationService, IProductAttributeFormatter productAttributeFormatter, IProductAttributeParser productAttributeParser, - ITaxService taxService, ICurrencyService currencyService, + ITaxService taxService, ICurrencyService currencyService, IPriceCalculationService priceCalculationService, IPriceFormatter priceFormatter, ICheckoutAttributeParser checkoutAttributeParser, - ICheckoutAttributeFormatter checkoutAttributeFormatter, + ICheckoutAttributeFormatter checkoutAttributeFormatter, IOrderProcessingService orderProcessingService, IDiscountService discountService, ICountryService countryService, IStateProvinceService stateProvinceService, IShippingService shippingService, IOrderTotalCalculationService orderTotalCalculationService, - ICheckoutAttributeService checkoutAttributeService, + ICheckoutAttributeService checkoutAttributeService, IPaymentService paymentService, - IPermissionService permissionService, + IPermissionService permissionService, IDownloadService downloadService, ICacheManager cacheManager, - IWebHelper webHelper, + IWebHelper webHelper, IGenericAttributeService genericAttributeService, HttpContextBase httpContext, MediaSettings mediaSettings, ShoppingCartSettings shoppingCartSettings, - CatalogSettings catalogSettings, + CatalogSettings catalogSettings, OrderSettings orderSettings, - ShippingSettings shippingSettings, + ShippingSettings shippingSettings, TaxSettings taxSettings, - CaptchaSettings captchaSettings, + CaptchaSettings captchaSettings, AddressSettings addressSettings, RewardPointsSettings rewardPointsSettings, CustomerSettings customerSettings) @@ -166,7 +166,7 @@ public ShoppingCartModelFactory(IAddressModelFactory addressModelFactory, #endregion #region Utilities - + protected virtual IList PrepareCheckoutAttributeModels(IList cart) { if (cart == null) @@ -281,7 +281,7 @@ public ShoppingCartModelFactory(IAddressModelFactory addressModelFactory, attributeModel.SelectedYear = selectedDate.Year; } } - + } break; case AttributeControlType.FileUpload: @@ -387,6 +387,7 @@ protected virtual ShoppingCartModel.ShoppingCartItemModel PrepareShoppingCartIte ProductSeName = sci.Product.GetSeName(), Quantity = sci.Quantity, AttributeInfo = _productAttributeFormatter.FormatAttributes(sci.Product, sci.AttributesXml), + VatRate = sci.VatRate ?? decimal.Zero }; //allow editing? @@ -437,14 +438,19 @@ protected virtual ShoppingCartModel.ShoppingCartItemModel PrepareShoppingCartIte } //unit prices + decimal taxRate; + decimal shoppingCartUnitPriceWithDiscountBase = _taxService.GetProductPrice(sci.Product, _priceCalculationService.GetUnitPrice(sci), out taxRate); + cartItemModel.VatRate = taxRate; + if (sci.Product.CallForPrice) { cartItemModel.UnitPrice = _localizationService.GetResource("Products.CallForPrice"); } else { - decimal taxRate; - decimal shoppingCartUnitPriceWithDiscountBase = _taxService.GetProductPrice(sci.Product, _priceCalculationService.GetUnitPrice(sci), out taxRate); + //moved up for taxRate + //decimal taxRate; + //decimal shoppingCartUnitPriceWithDiscountBase = _taxService.GetProductPrice(sci.Product, _priceCalculationService.GetUnitPrice(sci), out taxRate); decimal shoppingCartUnitPriceWithDiscount = _currencyService.ConvertFromPrimaryStoreCurrency(shoppingCartUnitPriceWithDiscountBase, _workContext.WorkingCurrency); cartItemModel.UnitPrice = _priceFormatter.FormatPrice(shoppingCartUnitPriceWithDiscount); } @@ -459,7 +465,7 @@ protected virtual ShoppingCartModel.ShoppingCartItemModel PrepareShoppingCartIte List scDiscounts; int? maximumDiscountQty; decimal shoppingCartItemDiscountBase; - decimal taxRate; + //decimal taxRate; decimal shoppingCartItemSubTotalWithDiscountBase = _taxService.GetProductPrice(sci.Product, _priceCalculationService.GetSubTotal(sci, true, out shoppingCartItemDiscountBase, out scDiscounts, out maximumDiscountQty), out taxRate); decimal shoppingCartItemSubTotalWithDiscount = _currencyService.ConvertFromPrimaryStoreCurrency(shoppingCartItemSubTotalWithDiscountBase, _workContext.WorkingCurrency); cartItemModel.SubTotal = _priceFormatter.FormatPrice(shoppingCartItemSubTotalWithDiscount); @@ -712,7 +718,7 @@ protected virtual ShoppingCartModel.OrderReviewDataModel PrepareOrderReviewDataM public virtual PictureModel PrepareCartItemPictureModel(ShoppingCartItem sci, int pictureSize, bool showDefaultPicture, string productName) { var pictureCacheKey = string.Format(ModelCacheEventConsumer.CART_PICTURE_MODEL_KEY, sci.Id, pictureSize, true, _workContext.WorkingLanguage.Id, _webHelper.IsCurrentConnectionSecured(), _storeContext.CurrentStore.Id); - var model = _cacheManager.Get(pictureCacheKey, + var model = _cacheManager.Get(pictureCacheKey, //as we cache per user (shopping cart item identifier) //let's cache just for 3 minutes 3, () => @@ -728,10 +734,10 @@ public virtual PictureModel PrepareCartItemPictureModel(ShoppingCartItem sci, in }); return model; } - - public virtual ShoppingCartModel PrepareShoppingCartModel(ShoppingCartModel model, - IList cart, bool isEditable = true, - bool validateCheckoutAttributes = false, + + public virtual ShoppingCartModel PrepareShoppingCartModel(ShoppingCartModel model, + IList cart, bool isEditable = true, + bool validateCheckoutAttributes = false, bool prepareEstimateShippingIfEnabled = true, bool setEstimateShippingDefaultAddress = true, bool prepareAndDisplayOrderReviewData = false) { @@ -768,11 +774,16 @@ public virtual ShoppingCartModel PrepareShoppingCartModel(ShoppingCartModel mode var discount = _discountService.GetAllDiscountsForCaching(couponCode: couponCode) .FirstOrDefault(d => d.RequiresCouponCode && _discountService.ValidateDiscount(d, _workContext.CurrentCustomer).IsValid); - model.DiscountBox.AppliedDiscountsWithCodes.Add(new ShoppingCartModel.DiscountBoxModel.DiscountInfoModel() - { - Id = discount.Id, - CouponCode = discount.CouponCode - }); + //MF 22.11.16 was this a bug? if coupon is already present but a non compatible item get's added it will throw an error + if (discount == null) + _workContext.CurrentCustomer.RemoveDiscountCouponCode(couponCode); + else + //MF 22.11.16 + model.DiscountBox.AppliedDiscountsWithCodes.Add(new ShoppingCartModel.DiscountBoxModel.DiscountInfoModel() + { + Id = discount.Id, + CouponCode = discount.CouponCode + }); } model.GiftCardBox.Display = _shoppingCartSettings.ShowGiftCardBox; @@ -783,7 +794,7 @@ public virtual ShoppingCartModel PrepareShoppingCartModel(ShoppingCartModel mode //checkout attributes model.CheckoutAttributes = PrepareCheckoutAttributeModels(cart); - + //estimate shipping if (prepareEstimateShippingIfEnabled) { @@ -796,7 +807,7 @@ public virtual ShoppingCartModel PrepareShoppingCartModel(ShoppingCartModel mode var cartItemModel = PrepareShoppingCartItemModel(cart, sci); model.Items.Add(cartItemModel); } - + #region Payment methods //all payment methods (do not filter by country here as it could be not specified yet) @@ -862,12 +873,12 @@ public virtual WishlistModel PrepareWishlistModel(WishlistModel model, IList if (cart.Any()) { + bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; + bool subTotalIncludingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax && !_taxSettings.ForceTaxExclusionFromOrderSubtotal; + + model.includingTax = includingTax; //MF 22.11.16 + //total + decimal orderTotalDiscountAmountBase; + List orderTotalAppliedDiscounts; + List subTotalAppliedDiscounts; + List shippingAppliedDiscounts; + List appliedGiftCards; + int redeemedRewardPoints; + decimal redeemedRewardPointsAmount; + TaxSummary taxSummary; + + decimal? shoppingCartTotalBase = _orderTotalCalculationService.GetShoppingCartTotal(cart, + out orderTotalDiscountAmountBase, out orderTotalAppliedDiscounts, out subTotalAppliedDiscounts, out shippingAppliedDiscounts, + out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount, out taxSummary, includingTax); + + decimal shoppingCartTaxBase = taxSummary.TotalAmountVAT; + + + + if (shoppingCartTotalBase.HasValue) + { + decimal shoppingCartTotal = _currencyService.ConvertFromPrimaryStoreCurrency(shoppingCartTotalBase.Value, _workContext.WorkingCurrency); + model.OrderTotal = _priceFormatter.FormatPrice(shoppingCartTotal, true, false); + decimal orderAmount = _currencyService.ConvertFromPrimaryStoreCurrency(taxSummary.TotalAmount, _workContext.WorkingCurrency); + model.OrderAmount = _priceFormatter.FormatPrice(orderAmount, true, false); + decimal orderAmountIncl = _currencyService.ConvertFromPrimaryStoreCurrency(taxSummary.TotalAmountIncludingVAT, _workContext.WorkingCurrency); + model.OrderAmountIncl = _priceFormatter.FormatPrice(orderAmountIncl, true, true); + } + //subtotal - decimal orderSubTotalDiscountAmountBase; - List orderSubTotalAppliedDiscounts; - decimal subTotalWithoutDiscountBase; - decimal subTotalWithDiscountBase; - var subTotalIncludingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax && !_taxSettings.ForceTaxExclusionFromOrderSubtotal; - _orderTotalCalculationService.GetShoppingCartSubTotal(cart, subTotalIncludingTax, - out orderSubTotalDiscountAmountBase, out orderSubTotalAppliedDiscounts, - out subTotalWithoutDiscountBase, out subTotalWithDiscountBase); - decimal subtotalBase = subTotalWithoutDiscountBase; + decimal subtotalBase = decimal.Zero; + decimal subtotalDiscount = decimal.Zero; + if (includingTax != subTotalIncludingTax) + { + decimal orderSubTotalDiscountAmount; + decimal subTotalWithoutDiscountBase; + decimal subTotalWithDiscountBase; + TaxSummary taxSummaryNeg; + _orderTotalCalculationService.GetShoppingCartSubTotal(cart, subTotalIncludingTax, + out orderSubTotalDiscountAmount, out subTotalAppliedDiscounts, + out subTotalWithoutDiscountBase, out subTotalWithDiscountBase, + out taxSummaryNeg); + subtotalBase = subTotalWithoutDiscountBase; + subtotalDiscount = orderSubTotalDiscountAmount; + } + else + { + subtotalBase = taxSummary.TotalSubTotalAmount; + subtotalDiscount = taxSummary.TotalSubTotalDiscAmount; + } decimal subtotal = _currencyService.ConvertFromPrimaryStoreCurrency(subtotalBase, _workContext.WorkingCurrency); model.SubTotal = _priceFormatter.FormatPrice(subtotal, true, _workContext.WorkingCurrency, _workContext.WorkingLanguage, subTotalIncludingTax); - if (orderSubTotalDiscountAmountBase > decimal.Zero) + if (subtotalDiscount > decimal.Zero) { - decimal orderSubTotalDiscountAmount = _currencyService.ConvertFromPrimaryStoreCurrency(orderSubTotalDiscountAmountBase, _workContext.WorkingCurrency); + decimal orderSubTotalDiscountAmount = _currencyService.ConvertFromPrimaryStoreCurrency(subtotalDiscount, _workContext.WorkingCurrency); model.SubTotalDiscount = _priceFormatter.FormatPrice(-orderSubTotalDiscountAmount, true, _workContext.WorkingCurrency, _workContext.WorkingLanguage, subTotalIncludingTax); } @@ -1012,7 +1070,7 @@ public virtual OrderTotalsModel PrepareOrderTotalsModel(IList model.RequiresShipping = cart.RequiresShipping(); if (model.RequiresShipping) { - decimal? shoppingCartShippingBase = _orderTotalCalculationService.GetShoppingCartShippingTotal(cart); + decimal? shoppingCartShippingBase = taxSummary.TotalShippingAmount; //_orderTotalCalculationService.GetShoppingCartShippingTotal(cart); if (shoppingCartShippingBase.HasValue) { decimal shoppingCartShipping = _currencyService.ConvertFromPrimaryStoreCurrency(shoppingCartShippingBase.Value, _workContext.WorkingCurrency); @@ -1030,27 +1088,22 @@ public virtual OrderTotalsModel PrepareOrderTotalsModel(IList } //payment method fee - var paymentMethodSystemName = _workContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.SelectedPaymentMethod, _storeContext.CurrentStore.Id); - decimal paymentMethodAdditionalFee = _paymentService.GetAdditionalHandlingFee(cart, paymentMethodSystemName); - decimal paymentMethodAdditionalFeeWithTaxBase = _taxService.GetPaymentMethodAdditionalFee(paymentMethodAdditionalFee, _workContext.CurrentCustomer); - if (paymentMethodAdditionalFeeWithTaxBase > decimal.Zero) + if (taxSummary.TotalPaymentFeeAmount != decimal.Zero) //tfc allow negative fee { - decimal paymentMethodAdditionalFeeWithTax = _currencyService.ConvertFromPrimaryStoreCurrency(paymentMethodAdditionalFeeWithTaxBase, _workContext.WorkingCurrency); + decimal paymentMethodAdditionalFeeWithTax = _currencyService.ConvertFromPrimaryStoreCurrency(taxSummary.TotalPaymentFeeAmount, _workContext.WorkingCurrency); model.PaymentMethodAdditionalFee = _priceFormatter.FormatPaymentMethodAdditionalFee(paymentMethodAdditionalFeeWithTax, true); } //tax bool displayTax = true; bool displayTaxRates = true; - if (_taxSettings.HideTaxInOrderSummary && _workContext.TaxDisplayType == TaxDisplayType.IncludingTax) + if (_taxSettings.HideTaxInOrderSummary && includingTax) { displayTax = false; displayTaxRates = false; } else { - SortedDictionary taxRates; - decimal shoppingCartTaxBase = _orderTotalCalculationService.GetTaxTotal(cart, out taxRates); decimal shoppingCartTax = _currencyService.ConvertFromPrimaryStoreCurrency(shoppingCartTaxBase, _workContext.WorkingCurrency); if (shoppingCartTaxBase == 0 && _taxSettings.HideZeroTax) @@ -1060,6 +1113,7 @@ public virtual OrderTotalsModel PrepareOrderTotalsModel(IList } else { + var taxRates = taxSummary.TaxRates; displayTaxRates = _taxSettings.DisplayTaxRates && taxRates.Any(); displayTax = !displayTaxRates; @@ -1069,7 +1123,10 @@ public virtual OrderTotalsModel PrepareOrderTotalsModel(IList model.TaxRates.Add(new OrderTotalsModel.TaxRate { Rate = _priceFormatter.FormatTaxRate(tr.Key), - Value = _priceFormatter.FormatPrice(_currencyService.ConvertFromPrimaryStoreCurrency(tr.Value, _workContext.WorkingCurrency), true, false), + Amount = _priceFormatter.FormatPrice(_currencyService.ConvertFromPrimaryStoreCurrency(tr.Value.SubtotalAmount + tr.Value.ShippingAmount + tr.Value.PaymentFeeAmount, _workContext.WorkingCurrency), true, false), + DiscountAmount = _priceFormatter.FormatPrice(_currencyService.ConvertFromPrimaryStoreCurrency(tr.Value.SubTotalDiscAmount + tr.Value.InvoiceDiscountAmount + taxSummary.PercentagePaymentFeeDiscount != decimal.Zero ? tr.Value.PaymentFeeAmount : decimal.Zero, _workContext.WorkingCurrency), true, false), + BaseAmount = _priceFormatter.FormatPrice(_currencyService.ConvertFromPrimaryStoreCurrency(tr.Value.BaseAmount, _workContext.WorkingCurrency), true, false), + VatAmount = _priceFormatter.FormatPrice(_currencyService.ConvertFromPrimaryStoreCurrency(tr.Value.VatAmount, _workContext.WorkingCurrency), true, false) }); } } @@ -1077,26 +1134,11 @@ public virtual OrderTotalsModel PrepareOrderTotalsModel(IList model.DisplayTaxRates = displayTaxRates; model.DisplayTax = displayTax; - //total - decimal orderTotalDiscountAmountBase; - List orderTotalAppliedDiscounts; - List appliedGiftCards; - int redeemedRewardPoints; - decimal redeemedRewardPointsAmount; - decimal? shoppingCartTotalBase = _orderTotalCalculationService.GetShoppingCartTotal(cart, - out orderTotalDiscountAmountBase, out orderTotalAppliedDiscounts, - out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount); - if (shoppingCartTotalBase.HasValue) - { - decimal shoppingCartTotal = _currencyService.ConvertFromPrimaryStoreCurrency(shoppingCartTotalBase.Value, _workContext.WorkingCurrency); - model.OrderTotal = _priceFormatter.FormatPrice(shoppingCartTotal, true, false); - } - //discount if (orderTotalDiscountAmountBase > decimal.Zero) { decimal orderTotalDiscountAmount = _currencyService.ConvertFromPrimaryStoreCurrency(orderTotalDiscountAmountBase, _workContext.WorkingCurrency); - model.OrderTotalDiscount = _priceFormatter.FormatPrice(-orderTotalDiscountAmount, true, false); + model.OrderTotalDiscount = _priceFormatter.FormatPrice(-orderTotalDiscountAmount, true, includingTax); } //gift cards @@ -1147,7 +1189,7 @@ public virtual OrderTotalsModel PrepareOrderTotalsModel(IList return model; } - + public virtual EstimateShippingResultModel PrepareEstimateShippingResultModel(IList cart, int? countryId, int? stateProvinceId, string zipPostalCode) { var model = new EstimateShippingResultModel(); @@ -1223,7 +1265,7 @@ public virtual EstimateShippingResultModel PrepareEstimateShippingResultModel(IL return model; } - + public virtual WishlistEmailAFriendModel PrepareWishlistEmailAFriendModel(WishlistEmailAFriendModel model, bool excludeProperties) { if (model == null) diff --git a/src/Presentation/Nop.Web/Models/Order/OrderDetailsModel.cs b/src/Presentation/Nop.Web/Models/Order/OrderDetailsModel.cs index f0146837e1d..64e23433997 100644 --- a/src/Presentation/Nop.Web/Models/Order/OrderDetailsModel.cs +++ b/src/Presentation/Nop.Web/Models/Order/OrderDetailsModel.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Nop.Web.Framework.Mvc; using Nop.Web.Models.Common; +using Nop.Web.Framework; namespace Nop.Web.Models.Order { @@ -34,7 +35,7 @@ public OrderDetailsModel() public bool IsReOrderAllowed { get; set; } public bool IsReturnRequestAllowed { get; set; } - + public bool IsShippable { get; set; } public bool PickUpInStore { get; set; } public AddressModel PickupAddress { get; set; } @@ -69,15 +70,20 @@ public OrderDetailsModel() public int RedeemedRewardPoints { get; set; } public string RedeemedRewardPointsAmount { get; set; } public string OrderTotal { get; set; } - + public IList GiftCards { get; set; } public bool ShowSku { get; set; } public IList Items { get; set; } - + public IList OrderNotes { get; set; } + public bool includingTax { get; set; } //MF 26.11.16 + public string InvoiceId { get; set; } + public DateTime? InvoiceDateUtc { get; set; } + public string OrderAmount { get; set; } //MF 08.12.16 + public string OrderAmountIncl { get; set; } //MF 08.12.16 - #region Nested Classes + #region Nested Classes public partial class OrderItemModel : BaseNopEntityModel { @@ -92,6 +98,7 @@ public partial class OrderItemModel : BaseNopEntityModel public string AttributeInfo { get; set; } public string RentalInfo { get; set; } + public decimal VatRate { get; set; } //downloadable product properties public int DownloadId { get; set; } public int LicenseId { get; set; } @@ -99,8 +106,17 @@ public partial class OrderItemModel : BaseNopEntityModel public partial class TaxRate : BaseNopModel { + [NopResourceDisplayName("Order.TaxRateLine.VatRate")] public string Rate { get; set; } - public string Value { get; set; } + [NopResourceDisplayName("Order.TaxRateLine.Amount")] + public string Amount { get; set; } // includes subtotal, shipping and payment fee + [NopResourceDisplayName("Order.TaxRateLine.DiscountAmount")] + public string DiscountAmount { get; set; } + [NopResourceDisplayName("Order.TaxRateLine.BaseAmount")] + public string BaseAmount { get; set; } + [NopResourceDisplayName("Order.TaxRateLine.VatAmount")] + public string VatAmount { get; set; } + } public partial class GiftCard : BaseNopModel diff --git a/src/Presentation/Nop.Web/Models/ShoppingCart/MiniShoppingCartModel.cs b/src/Presentation/Nop.Web/Models/ShoppingCart/MiniShoppingCartModel.cs index ab37c7902d3..541e075d97b 100644 --- a/src/Presentation/Nop.Web/Models/ShoppingCart/MiniShoppingCartModel.cs +++ b/src/Presentation/Nop.Web/Models/ShoppingCart/MiniShoppingCartModel.cs @@ -43,6 +43,7 @@ public ShoppingCartItemModel() public string AttributeInfo { get; set; } public PictureModel Picture { get; set; } + public decimal VatRate { get; set; } } #endregion diff --git a/src/Presentation/Nop.Web/Models/ShoppingCart/OrderTotalsModel.cs b/src/Presentation/Nop.Web/Models/ShoppingCart/OrderTotalsModel.cs index f521bb195f9..44e49882ee3 100644 --- a/src/Presentation/Nop.Web/Models/ShoppingCart/OrderTotalsModel.cs +++ b/src/Presentation/Nop.Web/Models/ShoppingCart/OrderTotalsModel.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Nop.Web.Framework.Mvc; +using Nop.Web.Framework; namespace Nop.Web.Models.ShoppingCart { @@ -29,6 +30,7 @@ public OrderTotalsModel() public bool DisplayTaxRates { get; set; } + public bool includingTax { get; set; } //MF 22.11.16 public IList GiftCards { get; set; } public string OrderTotalDiscount { get; set; } @@ -38,13 +40,23 @@ public OrderTotalsModel() public int WillEarnRewardPoints { get; set; } public string OrderTotal { get; set; } + public string OrderAmount { get; set; } //MF 09.12.16 + public string OrderAmountIncl { get; set; } //MF 09.12.16 #region Nested classes public partial class TaxRate: BaseNopModel { + [NopResourceDisplayName("Order.TaxRateLine.VatRate")] public string Rate { get; set; } - public string Value { get; set; } + [NopResourceDisplayName("Order.TaxRateLine.Amount")] + public string Amount { get; set; } // includes subtotal, shipping and payment fee + [NopResourceDisplayName("Order.TaxRateLine.DiscountAmount")] + public string DiscountAmount { get; set; } + [NopResourceDisplayName("Order.TaxRateLine.BaseAmount")] + public string BaseAmount { get; set; } + [NopResourceDisplayName("Order.TaxRateLine.VatAmount")] + public string VatAmount { get; set; } } public partial class GiftCard : BaseNopEntityModel diff --git a/src/Presentation/Nop.Web/Models/ShoppingCart/ShoppingCartModel.cs b/src/Presentation/Nop.Web/Models/ShoppingCart/ShoppingCartModel.cs index cf906cbb4cb..56119deb326 100644 --- a/src/Presentation/Nop.Web/Models/ShoppingCart/ShoppingCartModel.cs +++ b/src/Presentation/Nop.Web/Models/ShoppingCart/ShoppingCartModel.cs @@ -80,7 +80,7 @@ public ShoppingCartItemModel() public int Quantity { get; set; } public List AllowedQuantities { get; set; } - + public string AttributeInfo { get; set; } public string RecurringInfo { get; set; } @@ -92,6 +92,7 @@ public ShoppingCartItemModel() public bool DisableRemoval { get; set; } public IList Warnings { get; set; } + public decimal VatRate { get; set; } } diff --git a/src/Presentation/Nop.Web/Themes/DefaultClean/Content/css/styles.css b/src/Presentation/Nop.Web/Themes/DefaultClean/Content/css/styles.css index 2cb3dfd441c..422d148a6b6 100644 --- a/src/Presentation/Nop.Web/Themes/DefaultClean/Content/css/styles.css +++ b/src/Presentation/Nop.Web/Themes/DefaultClean/Content/css/styles.css @@ -736,11 +736,11 @@ label, label + * { font-size: 0; } .language-list li { - display: inline-block; + display: inline-block; margin: 0 1px; vertical-align: middle; } -.language-list a { +.language-list a { display: block; position: relative; width: 24px; @@ -981,9 +981,9 @@ label, label + * { background-position: -152px 0; } -.newsletter-email { +.newsletter-email { display: inline-block; - overflow: hidden; + overflow: hidden; } .newsletter-email input[type="text"] { float: left; @@ -1008,14 +1008,14 @@ label, label + * { clear: both; padding: 10px 0 0; } -.newsletter-validation, -.newsletter-result { +.newsletter-validation, +.newsletter-result { width: 100%; - overflow: hidden; - line-height: 28px; + overflow: hidden; + line-height: 28px; } /*.newsletter-validation .please-wait { - display: none !important; + display: none !important; }*/ .footer-lower { @@ -1195,10 +1195,10 @@ label, label + * { padding: 10px 20px; font-size: 13px; color: #fff; - text-transform: uppercase; + text-transform: uppercase; } .poll .buttons input:hover { - background-color: #bbb; + background-color: #bbb; } .poll-total-votes { display: block; @@ -1392,10 +1392,10 @@ label, label + * { padding: 10px 20px; font-size: 13px; color: #fff; - text-transform: uppercase; + text-transform: uppercase; } .remove-filter a:hover { - background-color: #999; + background-color: #999; } .item-grid:after { @@ -1774,7 +1774,7 @@ label, label + * { background: url('../images/rating1.png') repeat-x; } .product-review-box .rating div { - height: 14px; + height: 14px; background: url('../images/rating2.png') repeat-x; } .product-review-links a { @@ -2100,14 +2100,14 @@ label, label + * { margin: 20px 0; } .attributes dl { - overflow: hidden; + overflow: hidden; padding: 0 2px; /* fix for Chrome in Mac, the checkboxes get cut off */ } .attributes dt, .attribute-label { display: block; margin: 0 0 5px; - white-space: nowrap; + white-space: nowrap; font-weight: bold; color: #444; } @@ -2477,7 +2477,7 @@ label, label + * { .product-review-helpfulness .vote { display: inline-block; padding: 0 5px; - color: #4ab2f1; + color: #4ab2f1; cursor: pointer; } .product-review-helpfulness .question { @@ -3357,6 +3357,28 @@ label, label + * { font-style: italic; } +.cart-vat-table { + width: 100%; + display: table; + table-layout: auto; +} + +.vatrow { + display: table-row; +} +.vatcell { + display: table-cell; + text-align: right; + width: 20%; + font-size: 10px; + font-weight: 100; + padding-top: 2px; + padding-left: 10px; +} +.vatrow.header span { + font-weight: bold; +} + .min-amount-warning { margin: 0 0 20px; padding: 0 30px; @@ -3685,7 +3707,7 @@ label, label + * { .order-review-data li, .order-details-area li, .shipment-details-area li { - padding: 3px 0; + padding: 3px 0; } .order-review-data .title, .order-details-area .title, @@ -3999,7 +4021,7 @@ label, label + * { } .search-results .warning, .search-results .no-result { - margin: 10px 0; + margin: 10px 0; color: #e4434b; } @@ -4115,7 +4137,7 @@ label, label + * { padding: 0 10px; line-height: 22px; } -.blog-page .tags, +.blog-page .tags, .blogpost-page .tags { margin: 0 0 15px; overflow: hidden; @@ -4871,7 +4893,7 @@ label, label + * { .private-messages-page th.select { text-align: center; } -.private-messages-page td.from, +.private-messages-page td.from, .private-messages-page td.to, .private-messages-page td.subject { min-width: 150px; @@ -4986,15 +5008,15 @@ label, label + * { pre { white-space: pre-wrap; white-space: normal; - word-wrap: break-word; + word-wrap: break-word; } .csharpcode { margin: 10px 0; border: 1px dashed #ccc; background-color: #fff; - padding: 10px; + padding: 10px; font-family: "Courier New", Courier, monospace; - color: #000; + color: #000; } .csharpcode .rem { color: green; diff --git a/src/Presentation/Nop.Web/Views/Order/Details.cshtml b/src/Presentation/Nop.Web/Views/Order/Details.cshtml index f7563dc508f..0dc22f96776 100644 --- a/src/Presentation/Nop.Web/Views/Order/Details.cshtml +++ b/src/Presentation/Nop.Web/Views/Order/Details.cshtml @@ -433,7 +433,7 @@
- + @@ -485,6 +485,7 @@ + @@ -503,6 +504,9 @@ + @@ -562,6 +566,10 @@ @item.Quantity + } - @if (Model.DisplayTaxRates && Model.TaxRates.Count > 0) + @if (!string.IsNullOrEmpty(Model.OrderTotalDiscount)) { - foreach (var taxRate in Model.TaxRates) - { - - - - - } + + + + } - @if (Model.DisplayTax) + @if (!String.IsNullOrEmpty(Model.OrderAmount)) { - + } - @if (!string.IsNullOrEmpty(Model.OrderTotalDiscount)) + @if (Model.DisplayTax) { + + } + @if (Model.DisplayTaxRates && Model.TaxRates.Count > 0) + { + + } diff --git a/src/Presentation/Nop.Web/Views/ShoppingCart/OrderSummary.cshtml b/src/Presentation/Nop.Web/Views/ShoppingCart/OrderSummary.cshtml index 4d8649ac8da..35fe69a838d 100644 --- a/src/Presentation/Nop.Web/Views/ShoppingCart/OrderSummary.cshtml +++ b/src/Presentation/Nop.Web/Views/ShoppingCart/OrderSummary.cshtml @@ -21,7 +21,7 @@ } - } + } @*we add enctype = "multipart/form-data" because "File upload" attribute control type requires it*@ using (Html.BeginRouteForm("ShoppingCart", FormMethod.Post, new { enctype = "multipart/form-data", id = "shopping-cart-form" })) { @@ -45,6 +45,7 @@ + @@ -75,6 +76,9 @@ + @@ -179,6 +183,10 @@ @item.Quantity } + } - @if (Model.DisplayTaxRates && Model.TaxRates.Count > 0) + @if (!String.IsNullOrEmpty(Model.OrderTotalDiscount)) { - foreach (var taxRate in Model.TaxRates) - { - + + + } + @if (!String.IsNullOrEmpty(Model.OrderAmount)) + { + + + - } } @if (Model.DisplayTax) { } - @if (!String.IsNullOrEmpty(Model.OrderTotalDiscount)) + @if (Model.DisplayTaxRates && Model.TaxRates.Count > 0) { - + + + + } + @if (!String.IsNullOrEmpty(Model.OrderAmount) && !Model.includingTax) + { + } diff --git a/src/Tests/Nop.Data.Tests/Orders/OrderPersistenceTests.cs b/src/Tests/Nop.Data.Tests/Orders/OrderPersistenceTests.cs index 11a69ec8cf8..354f07fc78a 100644 --- a/src/Tests/Nop.Data.Tests/Orders/OrderPersistenceTests.cs +++ b/src/Tests/Nop.Data.Tests/Orders/OrderPersistenceTests.cs @@ -111,7 +111,7 @@ public void Can_save_and_load_order_with_orderItems() fromDb.OrderItems.Count.ShouldEqual(1); fromDb.OrderItems.First().PropertiesShouldEqual(this.GetTestOrderItem()); } - + [Test] public void Can_save_and_load_order_with_shipments() { diff --git a/src/Tests/Nop.Services.Tests/ExportImport/ExportManagerTests.cs b/src/Tests/Nop.Services.Tests/ExportImport/ExportManagerTests.cs index 7b5ae5bf140..6ddf86d4d2d 100644 --- a/src/Tests/Nop.Services.Tests/ExportImport/ExportManagerTests.cs +++ b/src/Tests/Nop.Services.Tests/ExportImport/ExportManagerTests.cs @@ -156,6 +156,8 @@ public void Can_export_orders_xlsx() OrderTax = 10.1M, OrderDiscount = 11.1M, OrderTotal = 12.1M, + OrderAmount = 12.1M, + OrderAmountIncl = 13.1M, RefundedAmount = 13.1M, CheckoutAttributeDescription = "CheckoutAttributeDescription1", CheckoutAttributesXml = "CheckoutAttributesXml1", diff --git a/src/Tests/Nop.Services.Tests/Orders/OrderProcessingServiceTests.cs b/src/Tests/Nop.Services.Tests/Orders/OrderProcessingServiceTests.cs index 1c6a646b54f..402f6ea9063 100644 --- a/src/Tests/Nop.Services.Tests/Orders/OrderProcessingServiceTests.cs +++ b/src/Tests/Nop.Services.Tests/Orders/OrderProcessingServiceTests.cs @@ -33,6 +33,7 @@ using Nop.Tests; using NUnit.Framework; using Rhino.Mocks; +using Nop.Services.Configuration; namespace Nop.Services.Tests.Orders { @@ -95,6 +96,8 @@ public class OrderProcessingServiceTests : ServiceTest private AddressSettings _addressSettings; private ICustomNumberFormatter _customNumberFormatter; + private ISettingService _settingService; + private Store _store; [SetUp] @@ -220,6 +223,7 @@ public class OrderProcessingServiceTests : ServiceTest _rewardPointService = MockRepository.GenerateMock(); _currencySettings = new CurrencySettings(); + _settingService = MockRepository.GenerateMock(); _orderProcessingService = new OrderProcessingService(_orderService, _webHelper, _localizationService, _languageService, @@ -237,9 +241,10 @@ public class OrderProcessingServiceTests : ServiceTest _countryService, _stateProvinceService, _shippingSettings, _paymentSettings, _rewardPointsSettings, _orderSettings, _taxSettings, _localizationSettings, - _currencySettings, _customNumberFormatter); + _currencySettings, + _settingService, _customNumberFormatter); } - + [Test] public void Ensure_order_can_only_be_cancelled_when_orderStatus_is_not_cancelled_yet() { diff --git a/src/Tests/Nop.Services.Tests/Orders/OrderTotalCalculationServiceTests.cs b/src/Tests/Nop.Services.Tests/Orders/OrderTotalCalculationServiceTests.cs index 958e4417822..3704aff3f43 100644 --- a/src/Tests/Nop.Services.Tests/Orders/OrderTotalCalculationServiceTests.cs +++ b/src/Tests/Nop.Services.Tests/Orders/OrderTotalCalculationServiceTests.cs @@ -205,11 +205,13 @@ public void Can_get_shopping_cart_subTotal_excluding_tax() List appliedDiscounts; decimal subTotalWithoutDiscount; decimal subTotalWithDiscount; - SortedDictionary taxRates; + + TaxSummary taxSummary; //MF 22.11.16 //10% - default tax rate _orderTotalCalcService.GetShoppingCartSubTotal(cart, false, out discountAmount, out appliedDiscounts, - out subTotalWithoutDiscount, out subTotalWithDiscount, out taxRates); + out subTotalWithoutDiscount, out subTotalWithDiscount, out taxSummary); + var taxRates = taxSummary.GenerateOldTaxrateDict(); //MF 22.11.16 discountAmount.ShouldEqual(0); appliedDiscounts.Count.ShouldEqual(0); subTotalWithoutDiscount.ShouldEqual(89.39); @@ -266,11 +268,13 @@ public void Can_get_shopping_cart_subTotal_including_tax() List appliedDiscounts; decimal subTotalWithoutDiscount; decimal subTotalWithDiscount; - SortedDictionary taxRates; + TaxSummary taxSummary; //MF 22.11.16 _orderTotalCalcService.GetShoppingCartSubTotal(cart, true, out discountAmount, out appliedDiscounts, - out subTotalWithoutDiscount, out subTotalWithDiscount, out taxRates); + out subTotalWithoutDiscount, out subTotalWithDiscount, out taxSummary); + var taxRates = taxSummary.GenerateOldTaxrateDict(); //MF 22.11.16 + discountAmount.ShouldEqual(0); appliedDiscounts.Count.ShouldEqual(0); subTotalWithoutDiscount.ShouldEqual(98.329); @@ -338,11 +342,12 @@ public void Can_get_shopping_cart_subTotal_discount_excluding_tax() List appliedDiscounts; decimal subTotalWithoutDiscount; decimal subTotalWithDiscount; - SortedDictionary taxRates; + TaxSummary taxSummary; //MF 22.11.16 //10% - default tax rate _orderTotalCalcService.GetShoppingCartSubTotal(cart, false, out discountAmount, out appliedDiscounts, - out subTotalWithoutDiscount, out subTotalWithDiscount, out taxRates); + out subTotalWithoutDiscount, out subTotalWithDiscount, out taxSummary); + var taxRates = taxSummary.GenerateOldTaxrateDict(); //MF 22.11.16 discountAmount.ShouldEqual(3); appliedDiscounts.Count.ShouldEqual(1); appliedDiscounts.First().Name.ShouldEqual("Discount 1"); @@ -411,10 +416,11 @@ public void Can_get_shopping_cart_subTotal_discount_including_tax() List appliedDiscounts; decimal subTotalWithoutDiscount; decimal subTotalWithDiscount; - SortedDictionary taxRates; + TaxSummary taxSummary;//MF 22.11.16 _orderTotalCalcService.GetShoppingCartSubTotal(cart, true, out discountAmount, out appliedDiscounts, - out subTotalWithoutDiscount, out subTotalWithDiscount, out taxRates); + out subTotalWithoutDiscount, out subTotalWithDiscount, out taxSummary); + var taxRates = taxSummary.GenerateOldTaxrateDict(); //MF 22.11.16 //The comparison test failed before, because of a very tiny number difference. //discountAmount.ShouldEqual(3.3); @@ -973,8 +979,9 @@ public void Can_get_tax_total() //1. shipping is taxable, payment fee is taxable _taxSettings.ShippingIsTaxable = true; _taxSettings.PaymentMethodAdditionalFeeIsTaxable = true; - SortedDictionary taxRates; - _orderTotalCalcService.GetTaxTotal(cart, out taxRates).ShouldEqual(8.6); + TaxSummary taxSummary; //22.11.16 + _orderTotalCalcService.GetTaxTotal(cart, out taxSummary).ShouldEqual(8.6); + var taxRates = taxSummary.GenerateOldTaxrateDict(); taxRates.ShouldNotBeNull(); taxRates.Count.ShouldEqual(1); taxRates.ContainsKey(10).ShouldBeTrue(); @@ -983,7 +990,8 @@ public void Can_get_tax_total() //2. shipping is taxable, payment fee is not taxable _taxSettings.ShippingIsTaxable = true; _taxSettings.PaymentMethodAdditionalFeeIsTaxable = false; - _orderTotalCalcService.GetTaxTotal(cart, out taxRates).ShouldEqual(6.6); + _orderTotalCalcService.GetTaxTotal(cart, out taxSummary).ShouldEqual(6.6); + taxRates = taxSummary.GenerateOldTaxrateDict(); taxRates.ShouldNotBeNull(); taxRates.Count.ShouldEqual(1); taxRates.ContainsKey(10).ShouldBeTrue(); @@ -992,7 +1000,8 @@ public void Can_get_tax_total() //3. shipping is not taxable, payment fee is taxable _taxSettings.ShippingIsTaxable = false; _taxSettings.PaymentMethodAdditionalFeeIsTaxable = true; - _orderTotalCalcService.GetTaxTotal(cart, out taxRates).ShouldEqual(7.6); + _orderTotalCalcService.GetTaxTotal(cart, out taxSummary).ShouldEqual(7.6); + taxRates = taxSummary.GenerateOldTaxrateDict(); taxRates.ShouldNotBeNull(); taxRates.Count.ShouldEqual(1); taxRates.ContainsKey(10).ShouldBeTrue(); @@ -1001,7 +1010,8 @@ public void Can_get_tax_total() //3. shipping is not taxable, payment fee is not taxable _taxSettings.ShippingIsTaxable = false; _taxSettings.PaymentMethodAdditionalFeeIsTaxable = false; - _orderTotalCalcService.GetTaxTotal(cart, out taxRates).ShouldEqual(5.6); + _orderTotalCalcService.GetTaxTotal(cart, out taxSummary).ShouldEqual(5.6); + taxRates = taxSummary.GenerateOldTaxrateDict(); taxRates.ShouldNotBeNull(); taxRates.Count.ShouldEqual(1); taxRates.ContainsKey(10).ShouldBeTrue(); @@ -1073,6 +1083,10 @@ public void Can_get_shopping_cart_total_without_shipping_required() decimal discountAmount; List appliedDiscounts; List appliedGiftCards; + List subTotalAppliedDiscounts; + List shippingAppliedDiscounts; + TaxSummary taxSummary; + bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; int redeemedRewardPoints; decimal redeemedRewardPointsAmount; @@ -1082,8 +1096,8 @@ public void Can_get_shopping_cart_total_without_shipping_required() _taxSettings.PaymentMethodAdditionalFeeIsTaxable = true; //56 - items, 20 - payment fee, 7.6 - tax - _orderTotalCalcService.GetShoppingCartTotal(cart, out discountAmount, out appliedDiscounts, - out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount) + _orderTotalCalcService.GetShoppingCartTotal(cart, out discountAmount, out appliedDiscounts, out subTotalAppliedDiscounts, + out shippingAppliedDiscounts, out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount, out taxSummary, includingTax) .ShouldEqual(83.6M); } @@ -1152,6 +1166,10 @@ public void Can_get_shopping_cart_total_with_shipping_required() List appliedGiftCards; int redeemedRewardPoints; decimal redeemedRewardPointsAmount; + List subTotalAppliedDiscounts; + List shippingAppliedDiscounts; + TaxSummary taxSummary; + bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; //shipping is taxable, payment fee is taxable @@ -1159,8 +1177,8 @@ public void Can_get_shopping_cart_total_with_shipping_required() _taxSettings.PaymentMethodAdditionalFeeIsTaxable = true; //56 - items, 10 - shipping (fixed), 20 - payment fee, 8.6 - tax - _orderTotalCalcService.GetShoppingCartTotal(cart, out discountAmount, out appliedDiscounts, - out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount) + _orderTotalCalcService.GetShoppingCartTotal(cart, out discountAmount, out appliedDiscounts, out subTotalAppliedDiscounts, + out shippingAppliedDiscounts, out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount, out taxSummary, includingTax) .ShouldEqual(94.6M); } @@ -1337,14 +1355,18 @@ public void Can_get_shopping_cart_total_discount() List appliedGiftCards; int redeemedRewardPoints; decimal redeemedRewardPointsAmount; + List subTotalAppliedDiscounts; + List shippingAppliedDiscounts; + TaxSummary taxSummary; + bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; //shipping is taxable, payment fee is taxable _taxSettings.ShippingIsTaxable = true; _taxSettings.PaymentMethodAdditionalFeeIsTaxable = true; //56 - items, 10 - shipping (fixed), 20 - payment fee, 8.6 - tax, [-3] - discount - _orderTotalCalcService.GetShoppingCartTotal(cart, out discountAmount, out appliedDiscounts, - out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount) + _orderTotalCalcService.GetShoppingCartTotal(cart, out discountAmount, out appliedDiscounts, out subTotalAppliedDiscounts, + out shippingAppliedDiscounts, out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount, out taxSummary, includingTax) .ShouldEqual(91.6M); discountAmount.ShouldEqual(3); appliedDiscounts.Count.ShouldEqual(1); diff --git a/src/packages/itext7.7.0.1/LICENSE.md b/src/packages/itext7.7.0.1/LICENSE.md new file mode 100644 index 00000000000..482d6c91c20 --- /dev/null +++ b/src/packages/itext7.7.0.1/LICENSE.md @@ -0,0 +1,36 @@ + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License version 3 + as published by the Free Software Foundation with the addition of the + following permission added to Section 15 as permitted in Section 7(a): + FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY + ITEXT GROUP. ITEXT GROUP DISCLAIMS THE WARRANTY OF NON INFRINGEMENT + OF THIRD PARTY RIGHTS + + 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 Affero General Public License for more details. + You should have received a copy of the GNU Affero General Public License + along with this program; if not, see http://www.gnu.org/licenses or write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA, 02110-1301 USA, or download the license from the following URL: + http://itextpdf.com/terms-of-use/ + + The interactive user interfaces in modified source and object code versions + of this program must display Appropriate Legal Notices, as required under + Section 5 of the GNU Affero General Public License. + + In accordance with Section 7(b) of the GNU Affero General Public License, + a covered work must retain the producer line in every PDF that is created + or manipulated using iText. + + You can be released from the requirements of the license by purchasing + a commercial license. Buying such a license is mandatory as soon as you + develop commercial activities involving the iText software without + disclosing the source code of your own applications. + These activities include: offering paid services to customers as an ASP, + serving PDFs on the fly in a web application, shipping iText with a closed + source product. + + For more information, please contact iText Software Corp. at this + address: sales@itextpdf.com diff --git a/src/packages/itext7.7.0.1/gnu-agpl-v3.0.md b/src/packages/itext7.7.0.1/gnu-agpl-v3.0.md new file mode 100644 index 00000000000..4ef32f08339 --- /dev/null +++ b/src/packages/itext7.7.0.1/gnu-agpl-v3.0.md @@ -0,0 +1,651 @@ +GNU Affero General Public License +================================= + +_Version 3, 19 November 2007_ +_Copyright © 2007 Free Software Foundation, Inc. <>_ + +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + +## Preamble + +The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + +Developers that use our General Public Licenses protect your rights +with two steps: **(1)** assert copyright on the software, and **(2)** offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + +A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + +The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + +An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + +The precise terms and conditions for copying, distribution and +modification follow. + +## TERMS AND CONDITIONS + +### 0. Definitions + +“This License” refers to version 3 of the GNU Affero General Public License. + +“Copyright” also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + +“The Program” refers to any copyrightable work licensed under this +License. Each licensee is addressed as “you”. “Licensees” and +“recipients” may be individuals or organizations. + +To “modify” a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a “modified version” of the +earlier work or a work “based on” the earlier work. + +A “covered work” means either the unmodified Program or a work based +on the Program. + +To “propagate” a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + +To “convey” a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + +An interactive user interface displays “Appropriate Legal Notices” +to the extent that it includes a convenient and prominently visible +feature that **(1)** displays an appropriate copyright notice, and **(2)** +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +### 1. Source Code + +The “source code” for a work means the preferred form of the work +for making modifications to it. “Object code” means any non-source +form of a work. + +A “Standard Interface” means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The “System Libraries” of an executable work include anything, other +than the work as a whole, that **(a)** is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and **(b)** serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +“Major Component”, in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The “Corresponding Source” for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + +The Corresponding Source for a work in source code form is that +same work. + +### 2. Basic Permissions + +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + +### 3. Protecting Users' Legal Rights From Anti-Circumvention Law + +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + +### 4. Conveying Verbatim Copies + +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +### 5. Conveying Modified Source Versions + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + +* **a)** The work must carry prominent notices stating that you modified +it, and giving a relevant date. +* **b)** The work must carry prominent notices stating that it is +released under this License and any conditions added under section 7. +This requirement modifies the requirement in section 4 to +“keep intact all notices”. +* **c)** You must license the entire work, as a whole, under this +License to anyone who comes into possession of a copy. This +License will therefore apply, along with any applicable section 7 +additional terms, to the whole of the work, and all its parts, +regardless of how they are packaged. This License gives no +permission to license the work in any other way, but it does not +invalidate such permission if you have separately received it. +* **d)** If the work has interactive user interfaces, each must display +Appropriate Legal Notices; however, if the Program has interactive +interfaces that do not display Appropriate Legal Notices, your +work need not make them do so. + +A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +“aggregate” if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +### 6. Conveying Non-Source Forms + +You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + +* **a)** Convey the object code in, or embodied in, a physical product +(including a physical distribution medium), accompanied by the +Corresponding Source fixed on a durable physical medium +customarily used for software interchange. +* **b)** Convey the object code in, or embodied in, a physical product +(including a physical distribution medium), accompanied by a +written offer, valid for at least three years and valid for as +long as you offer spare parts or customer support for that product +model, to give anyone who possesses the object code either **(1)** a +copy of the Corresponding Source for all the software in the +product that is covered by this License, on a durable physical +medium customarily used for software interchange, for a price no +more than your reasonable cost of physically performing this +conveying of source, or **(2)** access to copy the +Corresponding Source from a network server at no charge. +* **c)** Convey individual copies of the object code with a copy of the +written offer to provide the Corresponding Source. This +alternative is allowed only occasionally and noncommercially, and +only if you received the object code with such an offer, in accord +with subsection 6b. +* **d)** Convey the object code by offering access from a designated +place (gratis or for a charge), and offer equivalent access to the +Corresponding Source in the same way through the same place at no +further charge. You need not require recipients to copy the +Corresponding Source along with the object code. If the place to +copy the object code is a network server, the Corresponding Source +may be on a different server (operated by you or a third party) +that supports equivalent copying facilities, provided you maintain +clear directions next to the object code saying where to find the +Corresponding Source. Regardless of what server hosts the +Corresponding Source, you remain obligated to ensure that it is +available for as long as needed to satisfy these requirements. +* **e)** Convey the object code using peer-to-peer transmission, provided +you inform other peers where the object code and Corresponding +Source of the work are being offered to the general public at no +charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + +A “User Product” is either **(1)** a “consumer product”, which means any +tangible personal property which is normally used for personal, family, +or household purposes, or **(2)** anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, “normally used” refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + +“Installation Information” for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +### 7. Additional Terms + +“Additional permissions” are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + +* **a)** Disclaiming warranty or limiting liability differently from the +terms of sections 15 and 16 of this License; or +* **b)** Requiring preservation of specified reasonable legal notices or +author attributions in that material or in the Appropriate Legal +Notices displayed by works containing it; or +* **c)** Prohibiting misrepresentation of the origin of that material, or +requiring that modified versions of such material be marked in +reasonable ways as different from the original version; or +* **d)** Limiting the use for publicity purposes of names of licensors or +authors of the material; or +* **e)** Declining to grant rights under trademark law for use of some +trade names, trademarks, or service marks; or +* **f)** Requiring indemnification of licensors and authors of that +material by anyone who conveys the material (or modified versions of +it) with contractual assumptions of liability to the recipient, for +any liability that these contractual assumptions directly impose on +those licensors and authors. + +All other non-permissive additional terms are considered “further +restrictions” within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + +### 8. Termination + +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated **(a)** +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and **(b)** permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +### 9. Acceptance Not Required for Having Copies + +You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +### 10. Automatic Licensing of Downstream Recipients + +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An “entity transaction” is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +### 11. Patents + +A “contributor” is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's “contributor version”. + +A contributor's “essential patent claims” are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, “control” includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a “patent license” is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To “grant” such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either **(1)** cause the Corresponding Source to be so +available, or **(2)** arrange to deprive yourself of the benefit of the +patent license for this particular work, or **(3)** arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. “Knowingly relying” means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is “discriminatory” if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license **(a)** in connection with copies of the covered work +conveyed by you (or copies made from those copies), or **(b)** primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +### 12. No Surrender of Others' Freedom + +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + +### 13. Remote Network Interaction; Use with the GNU General Public License + +Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + +### 14. Revised Versions of this License + +The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License “or any later version” applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +### 15. Disclaimer of Warranty + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +### 16. Limitation of Liability + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + +### 17. Interpretation of Sections 15 and 16 + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + +_END OF TERMS AND CONDITIONS_ + +## How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the “copyright” line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a “Source” link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + +You should also get your employer (if you work as a programmer) or school, +if any, to sign a “copyright disclaimer” for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +<>. diff --git a/src/packages/itext7.7.0.1/itext7.7.0.1.nupkg b/src/packages/itext7.7.0.1/itext7.7.0.1.nupkg new file mode 100644 index 0000000000000000000000000000000000000000..abfa19a6483f0b39d8a67b0756b5b883cf3fbc96 GIT binary patch literal 2093436 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%0DuW1IV&*rU`t9`_6JJ>u`) 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
  • RQ*}3&!nTk7BdaP7Gl`ew2!|2TxYN!;$v& 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

    I2wpOY-(u1{moKB={xH=|w-wklN@Nr0EAH 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 Bp!jqkk1C8>W9w#ub;l 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%0A3SIMw41b@RD50?`5hFWiJj-fCIR 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%0D^H%IPQd2i2;V3H!ONrUzZ{1P!OU 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

    vD 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 z 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 z

  • dE1bVSr`YLz%Y~j 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�_N8T=3Nz@Mc$Sx=Zj-2-c;5H*LJ57qsxlwY} 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 z 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 mrsrehJ$;A`y`jzy={y;_T;a}U30TLEnp+U+Q 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 z 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

    } + + + +
    @T("Order.Product(s).Quantity") + @T("Order.TaxRateLine.VatRate") + @T("Order.Product(s).Total") + + @item.VatRate.ToString("G29") + @item.SubTotal @@ -635,39 +643,76 @@
    - - - @taxRate.Value -
    + + + @Model.OrderTotalDiscount +
    - + - @Model.Tax + @(Model.includingTax ? Model.OrderAmountIncl : Model.OrderAmount)
    - + - @Model.OrderTotalDiscount + @Model.Tax +
    +
      +
    • + @T("Order.TaxRateLine.VatRate") + @if (Model.includingTax) + { + @T("Order.TaxRateLine.AmountIncl") + } + else + { + @T("Order.TaxRateLine.Amount") + } + @if (Model.includingTax) + { + @T("Order.TaxRateLine.DiscountAmountIncl") + } + else + { + @T("Order.TaxRateLine.DiscountAmount") + } + @T("Order.TaxRateLine.BaseAmount") + @T("Order.TaxRateLine.VatAmount") +
    • + @foreach (var taxRate in Model.TaxRates) + { +
    • + @taxRate.Rate + @taxRate.Amount + @taxRate.DiscountAmount + @taxRate.BaseAmount + @taxRate.VatAmount +
    • + } +
    @T("ShoppingCart.Quantity") + @T("ShoppingCart.VatRate") + @T("ShoppingCart.ItemTotal") + + @item.VatRate.ToString("G29") + @item.SubTotal diff --git a/src/Presentation/Nop.Web/Views/ShoppingCart/OrderTotals.cshtml b/src/Presentation/Nop.Web/Views/ShoppingCart/OrderTotals.cshtml index bdcf5b024be..5d34ee107a9 100644 --- a/src/Presentation/Nop.Web/Views/ShoppingCart/OrderTotals.cshtml +++ b/src/Presentation/Nop.Web/Views/ShoppingCart/OrderTotals.cshtml @@ -62,39 +62,87 @@
    - + - @taxRate.Value + @Model.OrderTotalDiscount +
    + + + @(Model.includingTax ? Model.OrderAmountIncl : Model.OrderAmount)
    - + @Model.Tax
    +
      +
    • + @T("Order.TaxRateLine.VatRate") + @if (Model.includingTax) + { + @T("Order.TaxRateLine.AmountIncl") + } + else + { + @T("Order.TaxRateLine.Amount") + } + @if (Model.includingTax) + { + @T("Order.TaxRateLine.DiscountAmountIncl") + } + else + { + @T("Order.TaxRateLine.DiscountAmount") + } + @T("Order.TaxRateLine.BaseAmount") + @T("Order.TaxRateLine.VatAmount") +
    • + @foreach (var taxRate in Model.TaxRates) + { +
    • + @taxRate.Rate + @taxRate.Amount + @taxRate.DiscountAmount + @taxRate.BaseAmount + @taxRate.VatAmount +
    • + } +
    +
    - + - @Model.OrderTotalDiscount + @Model.OrderAmountIncl
    ?$+skvb)`42`6$Wj zdJ+@4Pk2M@js3m4FaBO#ir99fi20f->hbsLQ}y}jtablR&YSvYOyoY{P5qC6oPu$^ zsfVA%QhX19@|M1oNuj?7n5M7d;_m^b>7S5@?R1)+MO;72AH5d9v#%w*8MC>=R zb>%r8BUZxO`UEE9mFtj0+`eY&M@eu7QD*5GeR(O7jd%1BBx3cxqv!O)7`b|L^fgRm zHgfcR0r?%qo}-^(!n1+$uKsj?tVf=`&(^zhQN%HBww}&pOBt~;XX|@O#PNNOeuYV< za^G|Cn+T|Ru3mott632lo3D>15&Of1`Y|S%%A-&!ZX8^{>*qO zmY*un`dYo}Ff5g=V18S>s zA~Lo`@5)53x_tdxCOl(!7d>D9lb7 zvqQg|MC50e-jYOYr@QngnaHhsm)@C+f4;g)??xh4_%3|_iP&;?=_5%*e|GCrNko74 z>FY>De-7wdnaCA&KtEhcE(W9y%+3M5Y9?lbuQ8Nw^ifRY8at@J%Eez}2ldHJ&iNyMC5MubGnsbe%C5pxP&yk13D$$s(|CwPY$<(=?j&MM8EA|l z5nIY&V-AVfQbrgXnaDYP#h8}Qvmv&WSBxAIF{iH>tGW1dI@Z`gF=9@~8ecMzb2`@e zAt23Q6l0BICVXo`8E35A%4blVlVln-3%H0m9dEQ?BIk61(U*yw)7Om1Ha-e5r>`5k znaDYP-8e=f=5&hj3yGN1w~TWnVotM-ze&WL&M-pTv8_x+Y$@*;m6*t^&Nj{#@&1Uc z&NhlkL{?`T)lXxL%-DN|7m%kQKkpf>nedFE%rUkwk;jy|#!fE&{%5YSpNZU3<{FiL z!?xs>GS5h1B6B$3m`@^dxX_6E9ZSixl!ZnW5^+wl#HdN~C7qKjH^NM0HdYuL&zH=` z3L~FHWMhSKoQcfFO5@*vbc3<4G?Ff0Jv-)BzTRM4)U4t zF9}|wp=>Z}{mtw75Vm)e&y5(#CnOtMn*BK$G^7v(s-ST%<7lM z;(+vnw!Sp-neeQld}Z|b2kVhp-DLFP;%9Y}F_4MO>Lz13iOA|^;}De+S>0mv|CiOQ zh^%fkW-!TA@?fNRb-dNM!bIkIt5M||&(Bv7gIV2bG$JV=*>3b85jost3?~se+-+nt zkvZIB%pnmu++*ZjFPX!=#sMZWhkK150+Ip!*=zjFL}qNCahi*tv3?CJAtw1uW<%@W8!gK4K8qe6FBF$`g6j# zpNSm%|BTLD{Q3HykxnA|^FO0ST*>~NH1e2aDi!BM6AOz22=w73gWPJd}HdYFO3etcY8q28!h5_TVf?#G{!QKb8*R- z8W7CJC1Z9#mO+0m8;h98Z2VUuJtOKLrKI8 zRyNm?i2Y9$b2Ev!_N-#=W5WA`U#zWS{vMF6(8F8IayPLavb#pO>Z+L*iCCG{&15eA z6?AoTGR25BU)`KZB3iF*E@U!^-AUdJZB;jODMs`;(foo$j6KoZMIy#t!#qMF#-3#6 zR%WZkKRVPlf4!NB@-eK;gV0uOvs@J}8$phM)HN$Hk=dwc4i3l#h`Ga@!Gs?{P*hW| z%Im@3Di(uiW*a7j{=Hn?>_{Tkyl&2BGAX`MKmI(wT!Ff)-NlGW+7#2woJyMhP>tI{* zeEw1M5)*lb*4j*|i!t&lppChoNv1La^4t%4_=H(ZGK!?FdHd~r6t93h4>9dbodmB& zQPRwYB(GA;)8^eIlSn$7Ettsjx^CvmJFsS%!*1p`0T~H3cQgNCB98^#O-030h5oUi zyIGDz>_xkq@k}!Pr!d{ke2Nj07GxR7ShMQW7?Y{svvn->s@bOt>yLkr`c-p2lT2kd z)jZz3Krvz^j5jNF<@Jas@{`P!B;P?P+#63ae`X?|noTkbNyL^r$vjUY=3PhRYc^(WlFuOKV;K7svpGqX zf$%gCO1)`jkyJhlPj`2~?}^MqT>Nu@x6Q7-v1a(f9@O(2lzQ8oO0o~+0?2f8Ey-b! ze?exLyGe!}gI_Jck!YrQj7$7+h=~W8W&Xy5e}CAf~8t936{z#RccPDQj1HK8j^lvTVt67+nUTI*w+41rH+;=b*fY;=h++EN?{Ug zE0syGttx$PEOi@`V5u;ZV5xznN{uL0YFw#OPds;HTV0q0+e&8=Y-?qyQhB9HZ7Nl2 zTHhPndXGu4t;I}&ZJjAqs<>1sr61NDEY*sM+}F)A)0hO?>RPJQ;!>q@OO?tiRZ8i9 z#fE#;QpGmNX5hlSNK3A&LkW!^a zmnxNCs?`2cr4E-WRe#`(J#?4^dzj25*u&AKN=+_RDyvkf!=*}{Dpl%CsZ#c!8++J< zNw9~hOoBa}T&mQJQl;jUDs`k(sb5Q#I$x?(?D-pe*o;ZAhb@={dpNaJshm=!=9emU zyi}>*OO?7*s#NnAZtP(zCcz%IWfJV+v{I$sD^+T7sZwW3m4aOa{FCp3&N4~--%UtS z;pnNt(JIG}NiHRLUWae$=9tB-Eq?xkZ_B@DR>;7S%JZK&=Iu--DMO&1^>Cz~V@7xk z`wck0_dds*N+Mc+-&{i?T7Ta>O7*-3_29M4`{pH*x2cEo%qvt%^l+Y8=SA#KrZN{| zu+Iz4`GdK9K(fRfF|_3DEH`&Ek>gr!9tp@7Fp3Y%!hmc8Sz%rZ$U%^g%+|xOW;ymx z%`_%5KcAWdx%k=m)XXCh*;r$qAraYFW7d5MYc33lk*+bDGm#@*Z)OZHInoVglaX8s zVC;AW{e{_)WDm&C(C4qrCNE>mtoX`<;LCyV4)7K;l|%>m5x%If#ca()9%b^)iA-e1 z^3A0IF~e}z+T0tE7|1sBIv0O!>@X{gV&n4f8tpLabMf=D!<MB38?HV!QC)4f{cezcon_ z@7Ny^F_rV6X8awQFLBtz`=E6x;ws>{`3q}3TPgEA{7og;ERUOilf;AIvy%UrRmbvC zR3Z7@N)e%xdjO z;t4JytG7iO6rsqh)``G>;h#J%`{M6OqRNO4cD(<81h-9&vd4BMhT;WI_ z6PX`9()4u6{FsqeTtcE|Gm`ZiiwTLEqme3SP-N!aNS#tLj!B`ve|97LxcDnP7Ws}u z%tb76luC)r$08|bu`T%~>RpkhBw{PLE0W42lf5^H_v`M8WRYBeYz&5dLUJVO53DCs zxdwu7qBe=lImZRp{L2uN5(%H@g0q9tJW{OO1f}Hcq(&Bmm?)iJ;QQXx$ZC>4Bo9Qk zle`Er0qSWH$tc514JUadl2n$pUdnFjTAj4UUa4>Aj+b7Vis z3Xu0fxqM9a+U{0 zo@XN0&!EU0F8-Xp5LrYb=JbWgQYt0p>xIZl5;4m!MB=MsTk?(ZA(2!KMP`0TWH%Q- z^Ft!XNJQp`Mb49m%ny&`gjqd`$o%L?s?J4ZJ~MKOL}Y$aWVyj(MCPYN_LGRrzZv

    |k60vCNoGWvli&<~3Z-U7;w@ee&LB!o zB#T7M;Cqqc20TX0;M~ZmhFru9E{N1`gd%5fNn{`sxj$SI8A2lFYf0oKF8=;-N#tb` z@oT{)k>w;}2A4(3HO98&46cZzG(nLwxFRxuiR{mc$cR$1oQYgxDHu5pwtm4b<&T)Uy1$>d#zKxh&eqS$r_GrjaT+TsS{A@bmTA-nT<1% zqJZEP;+aV52rMPn*x5+Kktq2Ix4g)&bI(Omn8+>VTqJ`_Nc8YrWIrzzst2WTjh&Aq zz07MCeLf%AN+SAvIdYUqe#oOzmm|t3)_MptkH7l499d7&3}WzT^>^e1lj+LiAVo0J z;z+fzSZcaby;X=^1^gEYGlAa|QO#E)%}M%!;Pu7T$kc#bfO@V*3Yo|ibuFTe!JisJBUS#ZAq!kxMWaC<7EQuKDwa8QwG16<1A`&rD#kx7OWa}ZTWt(EQB%<{))?yOTdKv4sS6Sv2(Rw+n1(WGYCgnNKIyMnw3PMvs zl1?Z}yj4gd_J{FS-AOFQ-$zxn!X#oJRnaoJ`1{q0mO~=;q7|(XBx1GPY>j3zgI$sR z1@m>Yl|C8Uny&l+{kaBG)q)o$O3YUc>-=;SIj1$O1}vt4y&-^CZ8faxT>Q6>YFXuH zu~GQnE2w2vA`v~TWvwLi0H|2(s^Y4>r)~_TDL6mli61Iw&WGlZ{Dx(w+gD(PEZQsLEvOlJk!bE1n zv@)2;t=qIlaPj+WTJ`4e{)qbtmX%5(?k8AQItk_n&$=zE=KCdEcdYsgQDp0mHFgmb zB?m@vs~%Du>un~o&z`lMiQGqdR-47FKYk7ySRF{j8f##6;GGI<;}UE=Qz-z6Kun7D*-}1=Lm+schD48SrR;YqO`EaFv(Q@f>QV-{UIxdCP$>|r-6=cAH6>}GAr zE!o3v)^09-54%}upOoxj4{Ht+*~1=I*-uOMFx|R|MD#G-s>uZ23Z~h4)-ssLK0jx5 zE+xIG9-rNp^SKWjOO*b4etmr2A{Fv$9wNv6^m<^sQ9 zFvzO9nveYf5d7WW3s!xSHXyiN47P@nvTFAlfLG1kvaSu>dMD^@)JnP)}5;{DkL{lV>PzSVUT zmjfX0Ll5U$3pS(36}7;6lZnjO0_$@og?{S`tb8VN6br2FTX+wTLe2QT)*@>s7x*(< z{QPjS^)HDy7A&@$d{)ZO&tmJ;RxU@9;jOdZ;4iaSsRdjLq2@o}e&%xP3d!Ff__c%& zt-Ng*ldY5;S_XcR2~QBLPTNuBxK>%cO34f+a&NQBdXGeG7ptuIx%fwiRn}q>v3;$w zib=$l^0C!p2k*1kQdV2Nn8?{#ZOtdifIj0@(i&^`PAmm~`-~*d3h(Cf1_;jAMr$`o z7RcuyUsx0O@R(T?^Of}x$!wBM){i9bgKU9PUt7t0v6P(UE!MM4WX85wgP6!!-eSGP z#a{_qtuZ7bV_U6pR7&hgwptTNM1Hnf-;;>^?68h9;rA+cKo56VXZB$aW$U}FKKr?d zQhThvT>Sp*v0fk%{n=v;rc$Cmd#n*8qCb1ALrgN26_6j?M;)-vlZZQA2d#|e5B(3;R!40C*Ha^ zuRCe=AQ9(vr>ra{vOk5^2qv;Wg;p*T*`Gq|EEm5&Mb;&X5qqQ}E9V&RkJuxfwuT(% zBIe=`YXTE_x9|_^z7rTD`*Y5^i;3*dIqTt4F;6p*Tje>cJBgUnb5<`d{#JR;N+%Ip zJjAf#mNW_t`T=WzZnT-n3f0@XPRftwR#ro{ePKD?#BqAFXqKRDmY*dKW zClT4G5FJG#vXKy-Ln3Cda&$funT^WP^S_kL#?8^mgP5>GVa+ld^`aG-$hA=~dJBojM!jev7e5>I zqV-8cHtI$Dl89{F5#2!|vY|(JGm+WQqv795X2Xb%{=H;2qS2#gWHxXmc+t>VE@Evo zh(<`n+GrT6news#Gaq;&pO{2-@ zSd4$ocuzF%Jnv!hF!**cJf}{LeoNAf0OY$_y zL(vu_-9e7QN@y9KL4r?aPk}rf-A>XUV(?7nk!a$dysaT1zd_8S(T*f9Q%vjV8j{yQ z@NDPtXz@i}D%+P6m$~5i;RUGY@o4{lP~;kG6CDu{ymQhfdh}n6k$HY18vBolzg4!2 zHYO44uwC?SF8(@f7i~czR$aU3b`r7bIz)FfklPhCBF@>mMaPlgDAvO4 zbc;?Zk2T92_Kd#EM7Gs4I-iT*R?lde3amf=`C;#96%x@_?`Rzo(N^zhPbQhlVVKhs zXW-@P=t`1bNcu#7VIuqUT(p>poWbX!PsNvPyBB%(hV(WWFK z8yV3dOk`U_qN6BA)I22m9v8p$VbO&oVyzB~E}>H59BEi|B?-2L$H8II*KguI6l-;4 zbSjff#e{6ItF~yzN*I%=q=3|$09S3%t|SkTjEX)((i-Ge$m;0mAd-$GW1=HSo&~|< z#VgU}Brk&C7p}%d50QMi3%>aTR~+M`^(*t%@mnajL(LPSok+$&DIMgs=n#_0BvYd4 zw__=p!)ejOby4KDGc7u>RLn>w@++Xzq7#_NH3s!?@iU(tJx(GrpB?>~N{P&8N6#=R z^qbMoiZS)e!7Lw1R z=^BsOM)F1U1j#OvucPT<9aD8@*1Y#2Pyr zE%!Lq%(P-UVx(p5QB35ud0Bf6iMTc|XQ!m`k&0{cczY8QIf_bls}6Fcc;-;aPJD{DF8Wj1 z4nK`Do;_1|0`^E%>=sP;c_#iwteSn4j_fZPVbB{(-k~BeGa6K{S%XHm@L%}1r2?)W6w?=6Ehv_4vr>G`qu6!|6aGXM#kF&~ zVa;30h*B{-t|uD>`z;K{+-X;3B1_$6r>2)I)yRIcFN$odiJiklmb%-XoKdn=Gkfg_ z6!{$O9(yAbxpm)TZ+{hI4n5lqFom zDB9V}mvZSo8t%r!-z4i`kN$v5ZxGxkJZXM~~**lrY zQp4;6Oyra6VfJAz?Av@vhmkB@SE-Hgv5q|~db=&z2>yf`om};kS@!zwnH;hJxVl2ea+g8@ap&QWyH1ZKr?1We$i5GSklc5@nXssSEx_9Nv$z zbM~RgwfdgjbU%t*KkwNOFp=N%e$Q^rBwrEV>z-rJClO!go@?*Fp`N++;T!6iYoEHI zp7-q;2e9>gMbtBojt>0q0pYj7rF;P6Y6hd2Z-2{5@!7$c`SvLi5wpN<_)W=iEwGz1 zk+ZYFZoy={@)^|p7}UJLK0&e*_bell{;R6x1m@}{2^?8y3!0}6v&76gm1aD0eKB% zmHihJ*?O+Mm5Cfhu6>OOKgwVZbL~3cVLgQ*F^W&@p-i%s?ojjC-{IXbdj-jhAov%- zR@+}Qk!`KB*YX(u9rAVdj#4q-1q8nsxy~+P!n2C`S#PI&FZ28!$OgOXVJ?`lg&?2X zLz(1FYWlBxZFAxeq{rtzP2|p$yV+M ziH?K$wvRBGp`?NAfS7Ie1&Zkeg1>0qX~!SKdS)okf*gXFU3M}PnV;SE{Q<%6&+oR= znaH`=V-G!!^~kx{Yd1f^d)Ny_-%b_A7YZJY=Kc+gSHOaIlo{j_{;Jj2@!Zx&hA;n0oO!O z&u?~vbG)sFAd}#_@{GNKNr8X7IAd?;5)#LYGxq!EW$W11IeYmHa^C**2DxDGXTtN0 z{rS_bd;#kz^xL{<*I)u~q0ldtF51aF#&7+y-TF^fik*|-9-!FnN+Ra#A3NzUjLBBo zz_{ALNdK|JBwax|fn2d?kn{z47UY^erWi}ftX{X%naKUmb$fd$Y4OjErCKw|R%SrW z1EJ>Y_F*Rch&BwQj5GCLEHz6hw*|h04HD<%|Hu2h3`%WN6?6IbQYty)Ddxey zg-oHC>JS4rX`mD|4gU(_&OtpBq0c2{DoEuLF*87_l#n?fxA>Bf$?HjUrn9zYDF(i(kSLu=X^j`Ld~_EyRKJ)QOH@Y5d!mzSlm z^;D-XiD>;n=TjzfzFIh61qAcc!a2i)|3V}m>Uqe?uYfhn`FhlOE?%~UJ$%f`2nhDD zwKF3i*uyr?_JCjy+d5ZBL=T^IZco6P<#}eBlT0Gc4?8$*naonQ!3^RNwu6&Y5lhWd z_K-Z~yv`(B`3?ozdfJ)8M2@|avzQAz?u>=I`kkDrH(@=KloL=2|3YGCXABqrYa5-N ziA?xj0{5AnosA@7pV`H!TA7U^)cRF8QbX%qoZ2_Dn*DmZIcr#q+#hyxR2IYcY^R}A zHz%11oP9w(D9yaY9_9I1n<7JXxN_>Upbzj8Q`V{B;8*(_sIUW!^_nYDr1qA>0&{XHofII-Bc+07C z3tK<&?V*QlK(d`MNiSaiu@>(?MxkLm=q}DucFUzBGs{!{LAU@IQ_ZAH-(z9o*d^jlJ>qVCTZ!*GA4!bJwWEOQXi4@ z_hl`~a1i{CS&p-j*P|qW;F;CCPC_E@PZeKkGAUFHkk_EpyG~sao9cPb2{YmAxijQ& zj&nb+C-fPNeZ?7gcg}f)B;S`bE{e$EJZCD2$n!$yD-w~hh0bY`WsBhc5IkF7;@nh& z_1VwZQYVQ-WNfLUlZcEhbsCb0j4gHUBM})}>a->i8C&Xf=Hh2;nKOt)WNex9GKt98 zGUs&?k+Bb)cbM>u^?{6i=&a!}p`Rc>gF#j~8%cigC7M2#@R?BGPcIq$;HpuTIVMc zk+HSTZzLjPYn^{cM8?)T6>8zQc*aIRe)60!lY&ra0{p@e6B4eAKxFo&~MaDKe)ks9fHaqo5M8-Bd4vEOv zW~V6^KVw^*$4NxSwm6+hM8>u_{YXT{wu*gb9w!l5Ep|GR zh^!Vnxg;X1|2RYI@ll8~nJdoi^-GTHn$waA&nk}Mn$s~LnCI(G)jLY|Fyz)}lC9hg z*|-QXAvcZWK@j{EQdxI7FXfM-oSUrj*2O5|+~y==6mjkh5;2PMZdPB|g^St)qT*qy|gpVDsMjdww6Zz!Gbr*68eFv?tIHM@8 zyOKoAm+KZfSaU&0jNNnpAQ7|UxmQR;4`Xg6mzNSfZ0MdP5j{+H6FrQ9?()m;3!c+_1_BKrKOyPrh#`7t-85w98dw73ye1D_ja3;h_OH8wk8qt)yM71q`*IR_jNb4VA=3jOMmxkE`IC%-R&eI z^Znh7hp<$pG83}Fo}0V*Em=MOuc8LIsSk4zJsjk=eT0kX;q&fmOyt^l(VbRG-U$d^ zt-t8b4+vhZ4|Z1u1h; zsFZkHW4ODGM4W*PcZ*2G?2K~5k74Wbw+y4)FCUPv5%We)o+ z1v8I*mLmEy(J!?E<^r#CrECEC3HAw-{22Vp*Z6m?U-t!nSAu_+e5x;EwPgFU{&Rj8 zeY!8=Z)!^se|LL^A0x6c(@kw%8Ai%y8E0pvyOK#kNQ`uryM{!Jbe22maV#bG2|4au zF8)m@+%Zp~$nPWNyUUr#wUO_Zdm3X36p_`f zZetRW)dDw_M9k?nH;+Wj=}z}VM_!MZ(|zvZ&a6NFRlor^sSAto$Nr6*%*9`s-?+_5 z#Mr-aTa$>j`i(ofE7p^#jE334NfTmFhVNki5UA2ZZe4&`%!lwi5UC;+`JyV9%~^I3O9ddX3ob0?FCKA&^*NyNC$yH`j=Rxh|6pXK$4tp4fdGm$fR z*-h)iV?>@WyE!Bx&&6*1b37ZDARG9_zpL&DCh~jl*WIuB@p{DmFyw7#BIiBiW%cLv zD3js0rLc!A2!g+?kMoiT@R*tuQ_*{dqz(ukg)4g_NKBBykohWJ7D+M)-Y2Q< z!SumD{%+4NC8QO^jD}K)Udw^l7T-%?OieG1M8wqc<}u;(jw}4uQZ-9?3TnoBYJ1CW zsQET8k3_`O@s8h6b6u~9Nw)GF)RPT8tm`!&gguw0rZ=7G#&^(+a!M1JG{dr%b=c>5TkjQ1F{+<>?zOl9)1il zpMw})T_&>>l+{p==|w2!&x4Ax8DcCiEg(BVY_HA>ydI4C2E_FiljK1?KZ3-(Ljl23 z4ZQdaUTQPNH1wJW1Y_>% zxPynIxX0@`n3qDi26CS_IUw;id`ZJQ91!eJOYfc`vQ#yQY2`f^5FGns-pqj1ftbg= z^-Sbj1Z}+)OyrTVt+$DZT+eO2szb4@!jM?c?Ywj*naX(>DW2g!>E$tzD?H8HM5S<# zgzGuYOMZ#hBUWz*Z!;5F>M3tG#fVZ*dHEwsmU`OTI}$~f>gXK~2%a@{^jg2nV%V2v z@%*rpm&QaM%{zHFjbb(XN3<@UN+OQtT|AwOzo+fuH6Rg3^Df?dqj?X-UaN<MIw#`{k+{I;#kn%EB7j|M;r?Vc&Q}f zSTN9=N+S0BFL?V&#GZe!=Zxp|h$HN9@2QDgM8-yXJ(%Q&@Ch%jmXTft$srgQzIXAm zx0~cB2<}BkdF3WyJ^7)tAb3m}Sd9LdxPV>O(bGlp6Fd-!dE7qRlepu`Z~5H&njQ{(wN9~ z_`3HO7k`$gc(vcaQiTa(9ZvD;a`D&U6fZ&|*5MTIUlNhkXq35^+v4!&^8N`;)17kkw^y-aE_7 zB@w^Y$?-0|#bd;o@EosocF9r9_m(h`Tgn12mqfI+z#H{8mXhaDi@di<#JCoDGr9QV zTI4Mz5#w6q+0$7){<-KfuO}0^&s^qp%)yv!rRcFTN(jEQz07moMVYSjf|0g{^|Rc| zXOgW926+Ab=tn>zwybpqZXXGPq3=_Hc`^cLZkmuq4 z>POxTCI$Wzx{tm0NW>GmPrZdC;tAcS-Z~QTgl?^u&&7X2x6a!}BA(E#^LCPmCv@w) zJtX1@-8%08iFiV{&O1ypoSt{D^Nw>-#PhiI-v3C%^SJe15s7#n_nCK&3D3rJkg*Nk zm4Lheve8R<53^dBAfDNM<)v~7iD!0Sd2cb975@q4`73WG6aH2?X7wvCm+BGE$ToR- zRF8N@w#nOiL#a*PA&L=ip?vL~3J4w@zV_nhU=QUews?&L@-mEKtJj)|9K|*-jf+2u zZQevCvd`PR`BX~udAqlqN{K#i_txG}YP*+DG2%(z4)0Jv@JZiJPnj$GytW9QA$nDq z6o$ley}h2wV^~(#!#UYL&kB^nefU1_&Vb-Ot9@S6fZ#o={Uy$p-~At)+X<=s1I*V2 zh&d=E?&a(dJLXC`4Ka9R{MKv1YUcZ1tmixLX)Xz(p6|V$6eH^S-pe5o_59#%Wg@r1 zAH6*kgZm$>`A2Ub7r*8s-eHOnH6QURz0X!=NYwn3*NTa(`Iy%>Ab5ZNnDT~s5^?PQ#alxnj@?CG&U{`<9J|kYxeIuo#YoS2 zpK|d>dfwYeF=C|Wy;CG&qfLdFNZ|b z{Fk?riLCi=?;9p^-v9QFaPiwJ_D)fZsJYm47V(jan*a4WF_AU@=k?+-em(zreW{el z&wt)P5>fLNZw85|`I`4R6It_hZ%aTXz#ibbw=W=Xfs~0|Vp5Rs@SSCp*&yX(S4ghG zn#a3gsl7G=%Y znt*%?F|}enmtxKGxLi9nnu{_P>e&D>wPP2V@R4E<>&D`iVJR-y!`ov?On48sK&d-o z_XGs{9F9E}5bU!NdzxetjmwN>kW44BV)IGfC$VGKnDEwjK|O9PaXITx{H2BPb|j26 z7OTT#y7B>(`W9khu@ol!I__tX2C;_&@*7CQSoa(1X%y>2vKi_rvkv~|W-NnAfl>#a z_%wml(kM2I*Tdv8)YCY&hGH~`!4=gccAQBzy!yv~Mbad8nF$}ord{wiH)Ej>WL7^u z1b-PbRyiQ$9k}w1X#q(Dxj&X15DlbdtaU&dgFF^{^FyAWjQx-;ch$DsB0vF%J`#@fgJV3Hrg z9JYg)Cu7bE>|uc-GL{zW%Y^5rJH({LMh2uW$WyT?T;i`dyoa4)GpHUhJDp-1m`q@^ zGYm>~jvb~L(N>q(8D0-N=f}3X#BN$C=K|a67PGm;KS$Z<8B1m&$JH~|nF()eG?eNU z8$mImt=_SzydKsTUVrtDZQ!D;+N7|1Xz8)tT;dl&TbSqc*deM%j4M6%FB9I@cxda{ zSp8KcbJ!>LFcY4`X%N#Vmd=H3DfnBezOjK!_;xW5V*18L-w@M3Hi3y8Y5&-VycEmo zGAK15ww+?cNC(C)@_N`vKZ2NnvCu~{t9c+9F^@?>f>=MpVvR}O()rkj#qK3}kyhQX zSWA*|zO*3`?>-HSJ;j8N9k-@ovGjm!ht@~LzT^@wwu{lR0ur$mjE?Ok5nIaW*moqj z!Vg0|qhlwS$o!0smH!w=%4g>o#EgmE&qS{9S7Lpb@E-mSF|WiXa`D&mxL6jI67xPT zwvGv}`7)Hsj2)sFk@;6+MZ6xCd93->ScP1cd4Kgzh*f1G`#d4mm3Y3}{OQRT3 z^Q2f`UXQ6F-RF8+RXO6*T2d@oVf zg=dJdq)$q&@Hb=kG2uCEdj$TXW$cN73ru+F(Rw)#f~%KqqqfP z-i!SkklR4!#;UI7GnfmjP6wG6OCq@rdu%*5&5PA%!nfr+Sxm=(GzD20TSle!_U2as zOJX0B;L#InUJ_eRB5Ga|+r&h!mL;)5CVaI#2&IAe47ia z^Y~lNXt?NF95cTnib6~@{3sK?T7Ci98djO4d#$#G>oW1jvn||>w_?ZXK4`T)oJuib zJU@h&@jPricy04Tcuhc_*da+jh0k;GYVWS_RT5EYcZKheh_l14aN;_?#>DZpD_n;O zpPzgf&(GnW6eGry8%}4!kFOIDlNnDBJ;1|JxqA!zoFIca3RHr%z5F& z_5Mo0%z5D&T)axVCoD7Z=WtKBEfb#k8npU7JeXoc=Dp#GJP(`0B6Z;V(eTFsDFw1W z{2dpzzjXK=(82H)5^>}n4DVnvO~T`=JhVC(F8?i#neX=+Acw*=0}=-LBiw)qUkRA` zNVo}!$b2N+nu(wJNO%E{VPnQ~?$Pk_fYgP|e};>G$7ami@2A40NW?k!RQN%Xk#sG3 zDqM*K&$&$?=tCjP9R3O8lKXSF57oDNT-7%{77!Yg?mHo`6tb0(a)!QX>eN&X6V z=Hji5^WmO>7;JSuyd)sl>hJJTY9+1@FNIGBV(@vbOJQlF-xvP%@XO%`xg>>?Na}fFGx2BRYIqWf*!FAT4NUmE5&FPZTnpFwpFf_#AUDGn z6ZjklnMZ@%2|r8n2FOH^ko+=}SSS?)zw?cgU*VE80R*2OiIclgjQE9Yoct!q_2&GO zZ=5`m2|p^?x6<-#F4B6+oFFeG+3v|_ToQi+!5K@CR|WFmx6%o6lwxjCOp?4K5c4OD zIY~ai#XENwlaB>r@cZs!@^y--IF)Z{2|4sVn=$F;Mt-ecLQdksRupC~AwL)pydo+o zJJd>C5tWu-3dGEWSuHKM56FCw`{heaVo5Eb_vIjE z;jd5U#`h&Dxiqa*Jry}I3BlizTbr4zpl#93Db$LDI z!K)?(Vsv>^pjAC4mjZ%Mahq~zGhaW8pm+R@h9xI)@qV>o$@h_n%$EER7q;yzyD4#FUb|Tux)P+d0vtm zQOqM-@Y6heM=v)eIR&k7zrQTE4YcaXV&0^f_&6TZLLN#Y)^iIvm5E|B1DWe3r>H z=}Blc3bwtA{J=IgLN;dnZlar<%!JM|@MeZ?a+t?RJLBOu2q4{Mi$vT7?JhSXnGcz9 z9d?&{k>o%tE~zBn7Z);{MBJ6_E`PuVLX8YCM`;5#Y$h4m2m1t$DEvh@)2w%n7+k)+Dgc%2_EzriKc6$JO! zaCtn%Ob5Z^eYiY@2|qXCY8ftn7?5p{dAR&N6TkNn@@^))_n#qVq}!JjB8lj0qTD_p*w-X^9TVOc{$6IXTx17s*KsfN6uEssF!NM- zIf=;pfxICgm^nk1cVeD2X&r1S?u!}n0Fup~2;?D< z6>^c?Tq=U#9DXI&WO6)7Z0R?$&Lt$a^c#6Y9=19j5?i`X-pV9Rk|9qu$h=-YLt=r{ z2KiPl{~PZM|85TcC6ox5d7{YCeLKzuleor0w#X%+vSXX*ow!z40*Q8WA~%@^RrXV z@j5(Lp3B6a!(ZhFhj=S|PZ!S5ZuxbRHPk9ko=mb4 z1o!D4**@%#Fa>09G726Mrl6tfk~Fs1Y|Bqp}d!*=ze%p0>~A)ARx;@uF4Gyu&*rkiEJ2* z`MTVaiNEUpkzWf4?yrC3cLTB-^4yR=U=j=Atm0E}H|4n`9bi1z>X!U@KyZF;%j>vE zKOf@X+1-)12L#`4dPlDPC!1#$laLC7RR)lVS&dUha|xl~ti~%-DW*U4UatdGC1nZ4 z3d@IBVODZ3ch#4!bEMwx&{C&#$fZ%n)eae}D9ER~cpp-j_nbW0PIL{zulqw_- zX2nT=fs|Ehkvu{2pkk0b15yYv<&<6|FM&u3d;M+W8%-Vt>mBa$Bdb6rSw@8|LC@rtpUNk z>?pfQ#9nrl{D9y(j3|YFVIIDIIzjJGDZS4zVeby@1M-a0<^mTyTVe0dDH&W6&%u}n zK+JPWHWU7<4LmPDr{t2z&G>W6jg-W{G0!wfoI##f$}-`-r$L?<6rG8`dYdSz0m1Eh zS=m7%wyUY4U&PEYNvz&xN(U0LdRr>}nE12WN_i(BxO!VD9|icA0k%-?ac2FIjAyWteVva`jAw{);h($5 zGfb&^gNt~_<~vGll2I_iv^j3J&9P)A1JvbI3D~wI9*AO z<9Wn-p02DT5$kz|vXzN{e9ce}@EC6;%v7ZKyT>zA8A2k)GfNpuBF2-gWRi&Se5lkf za`$*XRH7teJad&_xOlzKRrXR0KC6baF;{7y!1IWipQqF=>SxAxoy}KT--F`s(?tsW z7yiXcPKH&7cS=2BZ@jq->+o*51F}T4N>~7`@J`pKN=hIzenR|Inad=WBr-2m=5t}I z1v4*ImNVf`_hIJGmFy%wW|8>|WkWHPG-(a=Ra}KPK`A$x91n@N;;c}L6!*6)0x>I; zegSzN`Td$O15(|kp@O-OW zD}^z!kl63vDGl!BBKG?RC6h$#)9;lMrFo243EwNJ_j3^|^Lu4L87|_DKsm}VCjQ%2 zbCfKSwJ<{bUE5|QkK}(KN8xCSDmi5_k3VDEmF-OUN@xl5vt6n1AjWXPWAG=X1`~hA zb}IF`cr&(B$!C%#i5dG@d8QoZNt48k{h~By;?LNxO3{b-c8M9=t<)zGGnS`hl871m zT`BS~ZzX2zcjdw(T*Qq1uH;qXz5fQ=j&C~JtDI&M3yC@0rxY@oCe4}+zfXZ}->3Xh z8Czw+Co#N(j&gJ z%Yi&N&qtL`0l~Fep!5&OHi$W)j0wmrh&iof24pwHoKuzsGhsR)LE2|C(NFL1LgX*M!90n<`e#yi?b{N zVYS{9e1zgCsGz2qmHY?Gm9;)qt*@kTY=Bdht)|;_!$JxgR+{<#aqv+`Z2|bzht4R zc_jF>{28d~np$4Q%>HloZ593t|6(P}ErYv-Fk}CpRAa6D`y^gW!i%sKg~3*yIO>!C zYUPT)5>lYmO=#t+28)S>gw#`?V&bpW`d+I(&?-pmFT^(+MY!8 z-b8(cMD*TNO=05Cd{Zwo9`BFBn0+}9V}2Z@nTRPF_aVIf^)9LUA2C62R=rjU4MEE1 z2c-=NURC-ruY=&XOTN4dVg&n2m;r*<63xYU5*CBtuW?&=vJnK|bJEh2Lm>FgQ7ca_ zgM9is{65!{2j<2}b)l~|o;(Wj>|LUR;5W{GOjD4?ET#vWRez1ORR;#73&gZlQ<+3V zk&j@6AnnuzB(H$<2kD^x$b?_3mq^Kt; zkC^8ab)3SgMAAs;9rtO9`aYMCnE4cMHpCw6q%NTtu?IV;>q*4k>7;%~BKA&aHA*7( zP8T(oMC_feYGsvIEwOiA^?Db3r<*5Y?{rgZQ!BA|x~Vc3NgO-f)ZQfG*y*M&pjKiJ zdYMbkgV`7iv(a7jRdOu|e%J5Ic93`OiunTs*SsGi_JuEJAqMB!m)jtCeEIUgJpSfM zUmgX)`%#`GC?KiOs)xFWZH0e#x0kw-3BOD8ImGl*AJh0=7TeWFtwkcXtB;N$V7CJ2sam>2U52xcDcNk@`*JQ)mvBOIaj4$RL%$TLDs zWfBd24KW2Eqtw+*c-6ug8|~#0Gxn}J&)`S5n4fpmPf5farmEj?NfI-brmmwHF=J`! zPb6Z-($qsFV#dp>faP2W^9}nBW7&8dWmAhjHP=qV#X$T zg6roj%!V(UVaD)Cndrsr0m18yNuK-(G8g1MPws%=S?_&MDlg<`w8@@WAXrh9c+T}>;ui6h zIn^8U!ypMdJbR(uWb^EwPd`wTOk6))@O=7#TAoQX=?JvK<10gbgygy>$y`FE7xOL6 zpuVEsHA#lrfW?fH#2GC^?Lc{|Lo1xs47DqXI9Fz0QzYWT_{cDdzLL1Y?$Y@(aWyKwn>|57SnNF|SY`BN1c%(rYEgyh^P@F=EWC zy%-c6^IFxV7%}GWyciT5&jz&t#fUNg&x=99nD0FiW8S1TWv%=(<7O}BD73=!sV61x zfV6}>TmBQXMQzJ_PZBXvwcEckN7a5zvZNu0;cgP-iK_MM;(pJP`W@r4RUOI1A5Tmj zM=_#ROkMY{R@>CVf3@1~jj;bG{G98{Xb`NlzDxtb^WYC&s|B9iWSMzIy24^A)Wb1z znFx10ep2f)iG@U8JJqL1L|;4A`Xr*SU20SF5cTV4y!9j#Je>PtE))F`!^1& ztGRgZ%{Z*Cp&0T0jl*gVm(X)C9()tRVfAl{5pU%9L%l{a6rMD|?<@aM?@%lJ%Lv#i zUoBA|x7~lQ$PqO{BHk-fpuS2X-r7;1_9hW;?KrLu;1VjnkN;Kl6Y5+Nd>aX_x|8Y( zY9-!Ja!So15$`8Cs~%^PCA|vUwE&Kkztqc2{IzjTy~TvDgrQMMI;TGT4BLv(cxd%5 z$a(cql5CLAA965$tGkmql8 z_OsYmH1rwubx~c$#J@7XsBUD!N4OqZT~zloi6xzcR(Ql+R*#b00>NvN%j#*8N=t*us-NuCXl?!rBy}~4xlnHt8is&EpHp$mfc#CRBl%fs!j4g(k zJ<#eOwKj?9>mPL(6W-TBh`FJzW0EO-4Xp}5ZmI=L{N8V?4>V$#z1}755t2`-cS(DU zOXxSqa~kqUS`!k{yQEEK!h62}F(EC7iQjvic9x01Hi~G4O!#>4s4Sx049GReQ$%a= zJRf2Cui$U2!Tc1}I*?TUjL&>gtp^ux=8I~3nPf>S#7IU6?wYk|jCuH{QS2*8>&iuX z9%69)Bxw);|6&QvLGUS^VxDvZDGqsxd-4`ZX>Bgc!?yxIag^28y}?`+6E@k(07pe5$LOmc9J9pQWK<_R{JGv6%G9cB7@Y>x{{m*aX@Nn%Smp5 zJP-1uwu9t>&*P+KAYtt$Nez%!Ks4>CCYaejE=;Wn6Mui1T5Bd*Qr(~V-?%ijxBnGm zX`}xm>Hm?b{}G&boOy9gVtMnNlna0L+krc`o>Tt9oN^fBpI& z`2%DE%)B3S0^|@J&P}{l;%_`P(^j*-_%WCPt(s|ro3X7(6nFkxXd}5u;;w!RZ99p$ zKi^V&v<0?`hHCx-Z*+k?ZM23YHc5M}H_6K&A45z>Z2(CRkQE>)S~|&ak}ld@lBp!! zw694Plf0(=K(Y~JJ>==F{Y8=svIV5C7T=PO@Ce9GkbYVXlE10do7zhxali7na}U&7 zlHBh}2a+0|^dzYZvKKNB)CQ5f;z=sWFi&QXWO=fHWIYJJ`*EQ51<7tt){~q8In4UX zAqnjkt#*)<11VrJHCo}e`{#?dwD5n#3J88Se@h$5BqoXb#)GsB67iXFkXE%d?RV(w z4D>Zf(@5%)4A!0{X+bhX>qgRxC<2xekJF zycwzGa`9$tq*lNrCW%ktBej&aY%3%&haAh)RBdoT%EESy)!u4<_pDCPMsktFC&UR_0mX<V&UCGiM4We~Yo$A~*$9dA&J3+Q zi8$}f&<2x;^Ue%yJBc{&%+PA5usq&*CsVVzgv5C#Q)@sX&O4b}a}sgh$<(4G;=D6c ztJ{f>P@H$NwZ(_`EYuTNlXlGVF_m+Cd(ZRB{XaRu8UD7i$G1 zjdt>1A}`j?a`CQ37i*=v;F$Tfaa+iN9OSlT7InNFR`8n$#6DXG#zL25*)I`BF-hv$V#m~$#WoMK~`z6GvV`t zy??C@W)cf^ftX1Uvs#<`DrVji-wR|W$QrG8FFx}_K^B6n(!Iw>YLkSaRRPE@Z2*%@={b;dAi3HRCjN^0 zRXfXsAGviR@Hf=7x^H6UvC^-<^QZ3fw2oX7Pl4cf^m$r$l0p!?{>jr`C%Fw`LY_Qr z5XrrJMIJ~C|6&O*fh2r|tw!(|ww@b9tG(K4YBd64@Ke%$Z37o+0tkMF-LK`5%m!%+ zt@djb2l6qCBQ9TSz=dVT_YCA~uP{kZ%z;)o=A&9Sk{?0vp3G70b&@=aIi?LFIY=?b zw2@5sj7@;v3$#KeTjDS5<9SYMceoUf+t2THpVE@v!VzvMem@9)TX9M&!zKPvlG9pc z5*1`7WIm%cCV38IF34H!JCb%FSV{iU4l{{`#I@*OTJb^HS1cs1MgP)l5^*j1mnOf> zW5l)S-`Wo(;##y&D`W!44)ldJs!*#tlx;3tk z%!U{|3d-w$l8E~f<@LWv8o?9(c*K?0F9-7Aky2il()esFfjmX(!rxHSYjI&mTxpPp zb(hKUkT~8S(VI}KLTFW~H~dv}eJ)8Hd?3QRg~_@(hG!PXauvNX6TfOz(c1?ED}NQe zH5r<9aH|G>~>6we$rfpM!J*c~UQ&#Pi_yGJQen=>6W~g5QJ=29fn_lK(-g zQ6Q?G@jj3F9Ry38};)eC2csy&w4NFZMk??UoYxCnDDEw zmG$7A4|+x*&pMDMdXZ^-@8JE+9FS)E04BWE4UomaB? zkMlct?eyjUYSms}9cYDj_1o*;{wt<~zL^Q%c6?sFgT9wZEJ;7XuUk6m`6MraU_I=p zpXB20osN2u8GOIv9s6Hko>TPdOnByfAf5EOOyCYV$?JS~eK$$6Cx=KBPtK4$ z3&Nhf(*GfO*^?qO*=k8_=gGZXq*p=Mvsk)JG92U#jJdl$m}CY?4}B#Q|J@p|>ANTf z-!_J=dg{k0M!XNCmwuKB{2ms5`Fjy!dg+t1a6B>TTadq-N>U#^lL=f)KwtkrOdma; zOZhCwJOlP%KmD~?Y%AE=DiMyBe)^z*+zZlQ&t~G^Z+TN+P9p9Vys2Lx5zo%Osb6Oj z4c){og=>;G^(xtZUzH&906iR#Y9IskrOH)oP2m4g_pBop;vOr}YXgXFhZw<4C**OQVYBRnZfGTD=gBy&BfMzY+KCz<%?ohf?zfOLm# zpP~<;R`_0{z93WeR4!hHoT^8e@N?V%i1|Rj7083%PG;z}7x1lU@E4zr>AFsG;Cue< ze4$??SqgFhWQBf{WF5#c zkT3P3AG0w_KT*s|y)?;wl2v*Il2agOq19JHhw^PN76iGR;?gZ?pzxaYaiI|^jTd=vW0 z(butxhYq#&&0pF$kn@&h+h-r>ggondH!5|If*#G|Dyjz zBCbGw(XTLxhBiW9cm?u{{@`c+2w#Bl{Hj+CNOO?gy3WM^7Unm-9*Ovd zH+>M3m?WN}{7s)oBA%Jtqvw%`?_Boi$)EGpBDP|W{wfoH%zN|^O#HR_yFQi)pLx9d z{k#4J&*RXNtu-}N`+XeUPM@htA58tPsClPn`_vyDt#9jUUdg*0+C5XHFhxF+r z;=aTmdM=Y`()J7R>;cT-ANpA?-gV{?z1VVp=C8mVm?L^JlUUL|XoctQV|q=J;~-cw zkLfZKf7_4gb|BBI(AP1&F%y0jHvr_MzTyi$X7RMfIX#C&JgsqFum2^F5l?Fr>Kj&~ zL_^{UitGAqCb6VX|BjQ!L*|=$(N(Nf(rQmiGvWK#Na)vWJ6ld zXCs3$S22c@Ec9dn$=9Cbe~YcgOItzkDz}P}`W;G|w3}k88ACR3Dez<%$qi3#k|aW* z!>inC#+w_l)z(ll2(I3m#z+zeBoiuIP2(h!Xy|1S+^$+ikN;sSxT^+&iz5Jx0~YCwY%zG~?kMjENh{5OmOk)GdG7uc0W&BRE z83adY8PX<}C$xhyyM{)xms-^`T9I6!R`rZwOroJ1Ab4lHzL86U-wNTDHZT%5^Q}m_ z3TON+D9uU4{qTmyB$8x^!Mo@$7}+FGg5Y)COGb$;JWm4<%-qDNPtw(sktBmb@H(@J zv4A8468KX%Kg5U^S8jG0BlrDhmgXjHP8XHJ%f;lx{@>naX>m62T3}CJOh$q=rP`EILM12os8ZjlR#R6bTQ77 zEClHY(#;sWjkj70(gUQ2k@gdcf5z`=Oku*S4Ia5YjqHHn_g1}(Ra_+T+-V;phvW!k z9u1lM8pU^DX1^l6Zj=oOR;2z$azL;m4KQjm;ooVFhdcv~dv{`g~r7^1fb&Gj*!bI3SI`hPPZA z^Ozj>o~#~kEF=+6R*yH9kccO%#~WWViAmxZ9B*uAk|tHE#ph?dk$eDqkA-@{wJL7u zc%v(c_y%gc@xnnC<2|80-e}7usSnH;*1GXVXOf{HI6vc!UQCWBil?~KjW#(N~f~+=rlgt5mB2|*UF$#|J zm}MZPAZCqG_XL+MAmu^U89hi2fjkECtudM8JV+R1gApaU|2CKJjhiI6zv@BEW~22< zp4otyMj%^^B_uC^Gy{nmUy*bGX$P{^XnhJ>`Pcm0j2sr@pNX~^DJ&)?iC;5qGnSGp zg5L_^Z^&M91=1HJ&$|-dcAkIg z-zy|xBE$@Xn7zgb*2*9AK4UDCm?VDjw9lBzGfSe?eq#ZbM3MP`v6zWp=MNamm}E%{ zp|5wLuLDL7$yN|thlh;4Bqu;7K+GS;O_Jhw_&UrtDxTr{;z^Ku^5OHBVUn~4!F71l zXiYL41207*3GlIS4SK$6T~NuC3# z4N~09Cus|U$4*J}0!dF017b>o0mCbsg=ajw~v ziC>ZGnH_kHSCO7FU+3agq^Hd{nD`axY4dF+SyJV=cqtwB*VE=0k_ZSsZ&TmQB56zV zjQKgqP>?Cm>RI!9k|`i~_H1D8CRqlO1u>1xlO*4Rd<4?iyh*YL$$XGQARWxBBpX0*Jg=CynM{-Rf#6?XeZ{PB zhh~+eqgj=tL;{aVF>9050r?X$cQQMXGz9qzq_dey(gS2#I()NlZXg*BatUI(nioi> zf!qLj)vO}j%l7GV5FAf8^KmAACFyP|O!&&gPov$GBgU_&L3nsCoy^sg**!MJ3 z0x`uPrl(magnh-59xNKq?mE71nk4Tmh8hMX{B^TFm(aZv_^-xZH+PU!g;wPu&j9nq zILs3b)dRu1b3@IJBrQPj7tO=Wx0&!UV`Up|W(K4>e~2&Ey0&X0JZaHVa5ZRiAB6EXrcMsy@eD z&P5XSaE@u*!}ExG_@Vg}iKvHj&Bi36hJR!>PUNjb#hz!@PU0de_Iz_96L=d5Y&$+9 zG~X;z3}d39Z6M8HOBa|GnebV~yH5+v8ch6cUu4Q8V%ry)DI{V`7n^5E#8xaZ!^Qb{ z#8!N4K0_k5;uAB4L~O;UW;Th~ilt_hi9hD0W*vqR3MF`7;`({H*_cFJKd&@< zkcjK&ugn1?;wpEwIgyEf{rru2Ur9b=2Vut6J_g@*nH8D%>u|kUlS!6z8e-bRtgbgV zkQBcsUV05=gSoO4=J6}Q_vSh#{@w5I&CN{wyWgA4txR}dxWYG?djf(he2aN9Ab77S zW?tiY*b|a(LGRnlmG`o(@a{EjH}kl7_nNkw2buWynzow-6eI36Z8y(S9&xW}yV<-9 z&n)hT|7cF;5)$vS_{l6%md{~-m<_!9w8N}PGJ#~LnfxG+`IKar87;?U6UonJ>+)QV zfZ*GHa?N2RcR+^2mi}VSC8>}IS8)*YtGR>32EltWyUpg0@I1{tNhRqEg7=?yo1c*2 z)0lYgG|#IW6Ceh^7xtv&e31V^D^C)>pv<0>TnEDQGP{z9N^;s31EULX;7K@+UzB;qdUJytss@iaoB)tf}z;Vf=tFyZUx2FzFq z>p*ojLht(L0qZP@xc(_)rPSat;`-+yE0+np{}5)rbOZPs5LW4$7!wUO0I390-Ex^^ zNpFC(=q^c*TTKE|17e=AI+F~im|E6=fM85*YYfSFih0t?3<$>5u|6T01~DjMYaPi) zB(n7*$!8>rb&%v65L}t6buu8eA&+KVCfQ6ex)pjHw=_#a!5G6TMUqP~ruA?@FvhZK zkQ}B~wk4CCC2_3!B>#}O)^jAs;Hgdp@&8(_V@Hs^J^fCCXXEi2yfnr))69a-VZLJ)VHWbsrs#=TZLBW`g)(DbULE1oH zovrC4Z;*7gmXeGl>1O?pH~Dfn8bZto__lPkH8CJ|)sXbAwUnee#iUvJ0l}CtR_Qvt_l^)V4l<9k zS_fnS?2GYM21!qfnP6=Q2*ymb&XNp-nD-#hdsfvj&)jVvyg3hKvehaexKF2A{Yb_@ ztLNdFoD3_2WGcxtYc2lBLww4C^MzT9E0Gd8Soe=Dn}q4{tC4nPu4lSqw7U z>Ory%TH$QWu?7bOXLYW%fMgHEd^`IBUxRYBo>T>^=~=kEDd2a+NsxpI2WuQjC6GoC^OL1pJf;RnE0CY9R1zDcBgik- z4w4r^6nGoJZY$O1t-6BrfS5hjNs_lgu&=#Vro&@0K;D3u{nid9S<(`a;UI@B*~J(x zsUU}~#w1@t%p{OMtXz`yATvOYScN3pK<0oPwUQ$|Paen;kOHeWNg>E*Ab(mrn9Puh zmxg;OAjhp?b@>RZf_w{d(#mI&CD|bOJD1Z|Vm+SuWsoSuoUxiSnI??}NvQ(gAXt-0 z-Us;!V$NF2snskHeEZp7R^6v~o{ve+S>+pY*+6pM%6o}R>UVI*5n5fa`nBYe2Qj}w z=D)4C+i*D!g6H>(RvO7Ql1tWPlH&LAm_ln7NoA0Ikms^ht1ag7uPU!v8(55gU;V13 zvlxD5h3|{IYGpHtCA|cB{%#6?L(N)1(h3AWe_gkha`B#4y>1mU;ZLg`f!_bIdbGQ{ z_nTHfCcLk+5OdSo!z7lJ1)1^f+_$a6BwtWpx2@w$_|wk#x$?Gkj>!yZ2gF>5Jh!cz z0g)O(kX@oZ_C7<}2Qi5t@pjFClmaPYHz0Yecbrrfq^SLBKpp|P$9^{;SykaX7kf!i z;QOx<_V$203av`o7fFso@3lZm+r>NZ@t}MFdG51o2SkCG2kiDF55jm{kO%D{0jUpC z&VD~2y{f}gdG?Zkd<^5MU~dn|^U$iIT}W~XdT$PrY?pt9j|ZhI^DeiKn%{aZBGaY&a-1L4G7M2#Lf#y7m%lHDTViidHRApXX_+7 z2=0qUc8h@EzIfj56%gDPFW9Lh&p<1j=O*@&fZ#m8Y?tnaefh^=GyA1JOeB1_2G)dT zc55axq}~t2OCupqbGruS7#?SA4>|7?(q;Vi0Lrfpr9>`;=!&Os0yr=7RJDW+GG#z4AKukY7`7ItZ&xX4u z5Yyj&VGx&v5QFlD-JVP0QV_fw|E9fxNtW~t2+r!8cH&@cHBH(8vIa5_u)8x!PyCTu z4YISD%#ij{t3mcDCTY?iAak3+U(mGg@K#c05%|r`$0#L-@XRRxgUmzhx+LeI70PgX zB9nC1YCFg%`yi7vDWOcf^fSn4d&f||T`0I+sdkTHTuOlKg_tzE$#4|^mw;pJaZJ*r z$`Er3T8*{yn50WGO;whPj@>;+i_vcR4=hRbn~Qjlkfy>c9viy)OimfG)(=OUGjmkz-4{oKwZsq!NH zz6Zv$+#Zllt#(UNb!fH1zQ6?j)+)5ZHNVQ1C-9gGAPU5+vU5nD1aU!D+o_XyOa!Dc z$hUUp`&^y}X$|tdoy#O$x(I#YJa4fVOvadW3CDxSa@4+e3YU)1ii=Fr&68;)`x}T> z3rGe-ObYZBwZl^}PnI+cq&vt~yDpP7DIEmABim*VV=|2i&c=4T-~-+}#^4Blv~x00 z(xeQK{*dP>QOs>t#APbJ8c3FA3x?BS@j$g^TnH}w==>>L8QZkJxjw*vP&wz_Ut3JA9P$9|Rsk4lWWVZRX&jJat~B*8NX#@w>E z1_Wbn+lNVT&EuHw*jGvL{B;aOa_(Ei$Af}rtB~^q$w}B!Jl^A+`Xt3}NF)QecxR9} zXB3I3^Ks65B?ssK{l$tURnQU~Ndr}9!B^CpM|@_t5zW8oYJ51%ws@0fjsOCAejjA8b}34|D4C5ya`g#$zhTy zO$8YNlI&cgn8hGtK&m)7%Xq8RATvN}IOUgf*$grt5m~K%R6` zR`6D5L4E|$oS`dG_;HaA`^#|Fu^9gf)^Oep#N_?4gCdsRC%&`zk z(HG!uv-1*(O)*b9Z3BWa&rt8tga#0!9e`h>kZet82J!&J+%3r<&pQKHo@l5u#QaeM zevRVHrp#}HU}byBSwQjuhytyeIM+z#fkZ%>I<3FrWBvl95l9Q?W0LhC%|Kc@s{_&w zq?MSlk}-%`2xD&Ny!kcDle8P82gI~>^9zYMzWO_dD33V4`a6Fz*(!+@{)TfQAh^N@I=7hk$NLbc)Os9qmW1cH`{54o z5a&sfB9Fu~8Rj%6DGu@@e10F{3?RYYe}v4VoCPHJQ_N^*2MM0zFmtMNj-(uA9_!ru zE$EM?-){72$=PZ$QRaKC#}ay$s$uk%IhcTW;(rL(KJgXb?pay;Ev}O`Z6!Wvvd6h)W;Y#NXE|S=;mCm~)VnwZVCQ~c1 zqECR%JN!vj1yu8VIhg#jN&Clsk{7*JwU|Uh9YFBPbF)*QNi5U{WNtH*!6ZxeLWO`C+w5#$vLzm+J?p(< z4114;-iB7Mf^2abGf7XJNO`t8ok(VR(whtWD<|03R%afQSZFE4VCJpPZjvuaVou^V z?92b;vdt;ag{`sv&}zGrxShv{HTHwkl|)pDADk0hLZV9i;MG+W{6sHgYsob<<{!Nn zJSy=T(ic1iQGW7b@c6ZJzIH&K!7#!dUJRb&FlMJGcrF+LF*}_)Kd`L`?SWR9 zd6!d{Bp(D@{p>Vk;vYLdJ1v>;s)a|&&rX+s;E}T18Nl;MVoUcrC4S^HCVJoJBs1}k zxc$zP0l_?now`i6CW`qv<}_gvmBbNu%<20R&n)KWm@|e6uf6X>o&slTKyb__oRv)c zqx+2WJ(H*;j>-$pZYD8F92XaylO$sA{Ougv#d{a?e9?ikGrsgqIvS7xP3zQ$QAjTy}bsECTtAN!MR7#=qXU;v8q-0 zJg+!Kce4?CSDsg$l3cti&#O*Z5^=t`>Qvz(i7U^m&f`q{E6;0=9uU0pyx}~<#9u#m zoc2uoE6+R5WD;@Z8RzaG5m%lGu9Syk&SJgemFGRKO(L#56WswM;>xp_n@J+BJWINH zBslYU4O!YPBoSAh_q*A@@xG42JmZyT8CTlFMO=A4=vF1M;rPNU&+=|p5?qIP<@t!4 zN+Pa2E4eF4#Fb|iw}8ns=?wI>AI9^TTlaVDYnpVKq^fJ}Me+A?HTNWs@!q>q&3!r$ zgV!6?+))9++FRYtVB%kIRCnhy@vk>(xXVez^+pYMJs0nKqlWtpo#PvoE z_b`cAKQ-L5)C!LsymF}FR@jHz6_dpEMoqT?iMZaV=?*3lSFn$}OGw1^#uIKniMZaV z|F3I$dm5-6eG?B zb=;4c_-nq7yNZclk?Ob`1A-MvasOcAS0uwd$HcElu3O|FAEBr}uA6t5i>N;lw;-R3 zs6P=`J;FuQpSo^45>bEZx|5msYpkw&n~7gZ>bi+Xc^>f@wys-=i&shNxz)IMm872g zB#BrF^<0PYh)Pn=eTIo&NuF|>1OzL|Gj0ba{;W21Ut{9WYD0HmKyX$YyEB;hv)aU6 z#KfP~7VbJGG4@%Q-M4Wk{(1MTwsc#cxO-Mxx{FA}thREuk%(Ds<=$lC&uS}o%t_4S z&uS}oG84Z(w{jP8@n*HP`xzH+R$IHPNW`qRb~jQUF{`cJtxWt`ZR6$!1ZTCqdx(iY zt0`_F6Td#Exbi6+bCx9Pa~F3aiKx%r+$f2-Gtk4mMk4C-Yp#5n=MnX}m)n{|)aSnL zrzE02_jk9Gi2D4dn{kHc5%qbX8zm9-d60XOMAYXYZq>8Am8j3d+$JQVK96uGlZg5} z+8z8Cw(|F2s#}$bKVzxx$UqF%-clibn)!74G?RhjrzBEx-#iC-mVy6u_xRbr-F zvyhKj)ZQ%DzQRS+-YhqrL{y1c?lKZlC1$z#O#C%A%k6m;^Y~R_mOFrnUnOR_>0G=j zk?l_5;#G-kcP5Eg3EA#K$|I^ow)+_qze>z@R|f>E#9Vh16Mt40xO81iKr4Q-1kXDm00C2ClOWRYxf3; zs1o0}@;^MYs1j@4)+C}ztamd=M3va!t|Jju;(PZRiKr5r-GUpK+24avHM)P3US-L1B|^_cioVyoMPiC-mRZU+)kC1P$*E?$+0xkE`rm590HsFkP^G538E zu{L6EHnkE}BIa%<5mjQFdx1n$iEVE3E!>KjcSmTu+nhvHi67j_B%(_E=&mLaRpLkY zAc?3FKe~yxd1g^1esn*(!$nkyAKkS9!7A~SyOj&85=TIGxW_0)REeGLEhhfn+36-p zrM*3fRbr=GHXv9fa$SpwUnTO~Moj!FvCmCm;#Y}%ZtZx?9Fs(q*zeX&;3BHTe)l~R zQ6&zzUy_I_alkEL;;*p-Zrh@m$FC9x+^$UgDsjMln~PT^4!Wbbcva${JDxM83O5>X|tx@Sm4mALLEC-KapO5AW8kccXA z%S|N_RYHm^B@tC3K5~*oREeUI{9>5d--C&f3?}}JB}PsLVz5diMyeOTyH!%eVd7Vb zq)1~Xew8Q|X+t8aM6pOWE?$)=78yh$szk9!Dzy?-qF7`CiC7!OBAL`mREc7dD2b>N z#Up1)M3pEWsZav9!mkn~B27p{l_(jRNFu64smMwaQ6)-6_L7JyQ7TfTB+o3WM5)O5 zQd~rpC>5Cz5UdjSMiz2mRpKH@>B#pKBdWxGk$p`3y>nmWPbPd1VwJcr@^?V6N|cE_ za4+`mSBdhGN16ClqGH5m;#Y}^k;MD?m_?PS6v=yli>MNnB9E5gBC15?$nzwkN>q-F zWa6)}%8|IT*vhXGl_MpX_*J5E>_$dTS8qDm-{ z1tg+MXptz1s1jzRTsfXuR0$_ylZYx2iDZ}OF``P;iMM$M{E*NC7y{4CJ|Ml zK_rtzREg&!c_gAryckJ*7&H5KgqlRUl88G(FGq$vf-(MHZW^h|#Gloskx_vdtRzh% z-vtCel{Aa&V&YelW|2co{7TY1a+*X`lID@CT)axsJd#)e$K!u0X&x!d#rss!JW_!~ zte@tQn$${ElID^2B%+eEh@_H;O41^-l0q(VhL9#KhJM;efbO42&g zk3>|G){$%yQAt`yI#uF%L?vk*c|9OlN!mn)b77U_Hb~pZEQ%48q+Mhc6Myfti)>`# zSCV#-9|D4v}D1#5u%!7u_TIRk(;s(j#)=F)pH#^oTrF zm5Zn(uSL3(h)VKWB%6u9#$JmQtA?%oO7dEy3=_YSycT(ai&sf{MieeyCFvP)NW@C$ z8EHs)L?!7Nd6|h{NqR-v2Lvlg-^goB{8@b?GLng3N#2O8BN3HkV5D?)+zP*v430D* z5tZcaNGge_Bts+XNJJ$W9yv%ND#^%5xCYNGD#^Q%<|Lw$q($bFh)Oaxl1Cyc$@s`M z5>ZJeMD&_Gv#2ERMN&vaC7BYLOCl;sM&#qiv6a6Er$<^d@n>v$qu=$V3uRC1yrel87oXGm=jtszg?#bS*Y!_N+eMf1VX-LL#a}c4RP#s1n(c zxg?@WWd9#o_a5J4`9BW)9#a#FS|yZ2Dk6BOXT=gc!Xt%|M@lw;aTFAWcV(+?-0gNmYC&>Z^!zPvop)riHpn%uMY}ki89|bF7mvw&vy}p&KvuE9eu1HIm_j~V_lfYQ7!jro*0%%%6)Hm zgk_QgzNuVfCOP1n!$oG2gT5swlt~Wy)(VMcl7qft6v`wAeS5JLWs-xw!zi?V4*E`D zE6OAXeGOIi6!H_OLp~FQGRYy|NEFH>hkf%<=)Cc>?=T8wk|VxYP4rBeIbJRBh zl@nmtycaRVpCH0XgHFf$|$jFOc)TdrTe^{p9h2??EmyM_urB z@(9aO7k#N*WRAM(dx4A0Q8DTSE;2{Os9gi%xhO}~Q2T_0P>!mhEqC) zH8#vz$<rg01#jBMll%sA}TRNg2 z%29W!4hrR{M(QjS%29W#`6!g5nyPgoq94jp%~cbHa@4)*AQZ|`_o?$xC`YwYi%}>? zwNc|-(KF?!c53rvCUOQ7)KVTJM=U{Y?TKOeCqaF|BP{>4SI2Xa`KP`50T-EnI;hzw zlz%#?UkZulpAKps3gw>;>K1H8`KN&Z_PamL*iRKNp!LO!Z|hGE0Qi3@$QD zgw$S-iK|nV2&>I{385?zR!5*vmax_7D3m2^wU~=sW47A;3D%Fy61JMkMP>p2A<-=1sBfasN^sN8lG$ok6v`xX)l3x1B%i6pD3nP)S1VB{Z_HQg4-q|6-dLcv9V&$K z#v-*8h4RJ{wHk%;#xgbGCDDrV#tO9;3gwMe>I@Xh8*9`OE;4VdRl5#j{m8trPW^j0 z6FH~r)%jfH7_L{ZdtzALSg)!h{%@;q)t+2r-uPDS&qd~q4eHA%ls7i0ZwZO!jScEY zD3mugsB^Iuoj^9IU!l-y*`VfOD>{K}P*0#x-pE($zs&Bri2waE%Psk85QXx_Ms+L- z<&911Iuyzqo7D;w${U;2xRIh~${U;2L=?&!o7HKrh#1Nno7K4{w`WhNP2LdwP?k8U znkbYdPN@@6C`+7C^HC@#oKq{g%uq%^&+Oa0f2l1eu%2fqZ-B5*p#E0ZaFO$MQJu|2 zj?YDPqbG*tgp2AGkFc}ECAI#WtY?`ME~!nq$eeIlZHGcR;j*d;iROgMYIhXM376Hr z*otz(Wpxk=t%S?!NNhzp;j%g(g>u3bwFHIE5?56CVJ*04`FZYDH4%k!!Zmdi3gv|B z>TDFs3D?y^6v_$L)hZOq3D?z>x5c$6CtO$id4%PJf7M|^cuvTIoS;)2iPQLOJ1PE%rSjloM{& z9z&s=aEmqug>u3z+EOlJe>H)1c#F0Lh1F#yw`!+Q*nI7Vm3g~%6@~4%B`_LyY7Hl{ zt7j{0oLNjmtr&&vIMz=itrCTu@R{7L)qh{KVmpqtYNn;3Ol7+O{wlnMR*1spi^a6m z8cq_e*eo--UsF*&fmTdfY5h^yu3QCo_@MSF$^wYl0P>JFpNrUo?0(v5lRjWQ&rp^@ z%od1gul1WOgzZY!b4P6y7r84R)h2j^?ZHR2Z#=^GV3PJd7qJHm;klBuaxO)2wBtH! zzoXD!=XKW33yJQ*&e{zhvip&rgm%_$6B0eoch(xA&>rlpHN#f42RmyiD6|K=Xs@Ht z9_*r}p-^4+X}Ku$*OaQ3k3xSMVYyabttz3YfcR@_DmdhneQJTZA ze}Ht?9-7LYOZ+v{WO$0lG=q!$%kd|*{wVAh3~qpW+FL8-BL9~BX>HO+teovUu`UxqS`GWhW%YzUoDS|T=RW3Ulwa6PY!*xAQyRZ=&L;~ zBzjKhr#**4E3=6@;E-l zU~L)-&GKMv1`5scVC@qWn&rXTr(ERUkqy=sdW8KQ*-&jA7x`CXFKemO+4vMG^p}7y zYrz>p=q~|BYHLy0j$@y#zoL~2iDADhQVH|^sy2HjZx!=0JjHn~L$jI4n(~@9+9U1Z zenx9KTx3lduPx*vYs%YNK9{06`a8|HwfIk1&+@O~-qyNuk!R7jwZi|2r=Yr%rd56_ zgz8S3HgC2Nsxs-CGKYy=Kk3^0T;%#m*QRli>nB}XASAke-qDt$(E52tTZOG?{k)@n z%SEoAceU*vVe4n2wj2AQ^)p%9Dyj zi=3~?+If$#`I@4|&t>By=POIQmy4XQY%Ph4oUd$c8W%ZV*;=uPiTeu{L9Y=w~kM0(L4`s#T%Tso-lZZJ~&vQ^5*tJ_?-*R%*p4 zbShY_)m$W6(W&4Yts$2g%0jqS{jTu0+u9@)c2;NSgmqflQr1eI6EC#H|?P+X9r}u5z02JC^+q5CricSvOv>X&VITUIeQRw7Qs1>8o$zi*85rs|; zMOy4~_FP2@ogBW`hNIBQp+tKFg-#ATv@{etIqcBBLZOqx4lNgjP7Xh5aVx~N=;W|V zOGKfQ!)~n)3Y{EwYw1FwCx_iy1`1V~-P(sJloNJqQ&A`~*k7(OaD6byTicn|_k7(bc z&=?-kN>FGFk7z$}k$LrqcE}?vuO8K|agllTcdhP9F+TKH*neuxxX8Tvr#6<0%&UKD zxgsW-SO3&n<%tzVdG(Z*vPKBy)l*vRS|OBIE44vfIKnkpnZUl+6h3eDFA zEsR3*bwP`u(0pCcx}wm0UC?@Pk@Iyyd&VPdzAkESa*^|ORmtKvNkBgkI82us_ zIbSh)!g?|C^m;i)U$j98%~uV*G+zkKR}DR5lMtG(n)*>LvJTeNV>YuG@hLg01vlsh z3e~~ddMXOl!8-aV6nb@BS5GPs{ZJjeS?_~Fb+Cb+hC+2PULUqaw5q~7c!!>cLUpjA z9^5Kos17#LhoDd$Y@%nPP#wHSFG8U@*jz8?BI{raJ-ChaEMG6*tG6l=La&$a*H55O z9ehwv{f@=RHQ!b@xyU-$R`2hLVRf*rKF1@h4nCyka*=iLA$>C!SqIzcB`8z}+vx{{ zMC)KX{WJ>I!FKv(Y(;gjogP!n?zt$s_%h3a5} zo`XVlu)RJXh3a6UUiW)`_2@SVAJrS7P#sLto1su0OwvOrR0osv6cnn1UGzmLR0mbP z429~Tre8&&I;iQ*f8h5Mt%I805{2rZrnf<%oS^CLP$(y8dV3Vg37VcLBw7bG-R}`r z2X#F~2(N>kp$;1Q2#ldRXzCeUtW@w-OS~y_TN9Mdn_=?(+!Cy&>JfekjvM^kgB?OdHW3L!nF?(Vsw}QH|)S zC^V`O{TUP*)rj7Yi_EkUeW*uRrtPX{a*>%fMW4k*X4)t8TrM)xKB32zuo07)_6gnO zB3~gsp)cAgRxf4RC-uCagixk^Qt!G;2xZ#d`Z6wZ27Burce5BdgS~Z?i#*Bn)*llR zov&2=X%w2TRDA%pqLWOj{vsDSUr*_yJi_LykDiYG(0ujRGlWFvtH1ss3e8u4eJTpg zSARVVh32cjJ_Cj3tH1sU7dc=3^)Efb=4+t7or|2W7xXeNa=wP?C%DM@8m60j*ci(B z8m5osBIj$EUbR=u1KI9^YyZR09(;~y{sSQBIj$QUg;4wU$5zxupgSQG5S>@(fJyq*F4CdqBhOf z82ttmny)c>Z4{cXF?wATny)eXEnMV$jnNx>gw5A@UE?C>>n*)27dc<=>ixOM`FdAh z$3@Q9yLy#~iJm;6)z4xsXudM^VMm0}d}Ziy6+&pf-qWAwB2O~!>C;f?75yarHxxR_ ze5fl&SwG@eaoGF(Q}iY%bds5>m!i-~W}04&LMNH&dc$8uKXj7G)(cUb7UC6sj^6M$ z5kn`LPxVD8bds5^m!Qx|X0Bd^LMNGd`h&+rKXj7$QcvU}PcrlMVw9e6_1zD`@2Bf+ ze`l@a*=C_$cw9&yh+&@vEYZLJLkOL1mh0!a$X$@DU-k$)$>i#fo?xwH%u3zmB2O|a z^{2SVlgui8FbbVyR_U(^iJoLu>F=Y^NoJKk16$EaW|ck*h4$AfeLl9LSM;m&G88(= ztky4~&`D;s9($5Kh5S4&PftXllgt|3M4^++I(-oey`tZse}h6NnS4DTg-$a0`cV`* z$>i&mD0Gt9qzC^L_d_R{0(~S3on*G?IVg0J*`jY05Ogt2lRX{GOr%ct3^ySuO85cofUIIdG(+^qe=+n)q}coUI^vYL;4~va=s4f z9sXi5a=s4fDi>J?59yByiO$zy{b>}MufzHPY(?{RSbvd=oUfntQ66FQRiUS2KQv#* z^b8@<`8uY5h(dMnm_8MS=IfaLF$&GsF?}Wq&DSwKhl`xAWBPoLu=)B!FXAHS>y*Bi zi=3}2y^@QZuPQxQ&Bjp9SCu}2i#)ef>D7OWxuE$vr#HDMgy!pYpszupI(ShpLt%5l>fmL)3We(66}{k!=$Y!^HT@V0)j`FGyDDO+4%Rd( zP}nz2*gM;`jNmm9Lv`>*qx8BEs)MzSY80x2b&Q7piWsVc^^C_*s1DXQ262&fuz^vH zLUr&~BUNbt<0I=}yipY+gzDg(MrjQpR0kUych+Pgcfs98Q;)DZc(>8VBkUFZJ;uvi zWF5T6c#DgygH4SOQK$|!HFAVR>tIu384A_Grp9_~MRl;LQGi1GtEo|pt*8z*H7=r1 z9c*UY9m}3eepjuT(F%oL(Kk0zQK$~KFb1Je9lXydL7_U>$~cHZb+ENjfkJh#wNa~< zxH{Fr)<%64s)KEeAt+P_A2Md3P#tV%Y-b!560L*njAJNN2iqB^P$(y~GtQz= zPH1PGN1>e1&bS~XS_j)1HE$44!Mfpmh10lQ)z5#VG!FU*Bs1CL_f?VW^YHxhY zMdsD^Mj^JMI@sPgh%xkvzJqa8#6;i8>0q3|R+Lve7*$+kUVX&4;t`ftI~j2|@~4Pq z>@G%q6w0(+jN61nGi?{65ejA6F2>y`G^$;U<|s6(U5u6}G^$;U2e`;g+r@aqBP`Qu z#uHp*rnQVgTx6yV8)Lc1OdB>zxyVc#Hsa#Me9?QSVPnWmLMYSPMrIu$lxc0FNj)Ky zX&qw%7deBDafOSVLC1)_S+t@Vbc{wqqT?JfTAM4`$! z+{i_t$~e-y-j#{EJRs*H0Dm5W?abB#4z zWL}+X;KQpSh$V@xWxZ)9(X}>h$ zTJWccqfEQds3RnrX%`x|qEMz?XxxrMqq@*&fF-vei+e85fy*j~Wkik-7I*Lq(z7`>PQV63xB88hueH_x@@O#a5Jie>Gk~ zp&j?DF&0}vs1qe(|`b;`X}hJ!-6_na{jg>vsXW2TU3?mcJZ zpiu5TXMBc2S>l}W1qx+}bH-OFlqJp?i-kmU?>S?wM_BGXZ)_LBbMI=%y?+_MVGQNo zYU3goxmv1?5uMnR%G_IRjO8NV?Ws1VVhrWp3&uCXV%C0?jgQQ|v1UUqGWXUt+j5b)x3-zVMdsexX1<7tzF$|{Y|=%nG0MF+ znWj$&<=&gjQdJ1$-a2N2#zf9w9diX2IfHe~bzI~O)-iVqiOyGDa~}%LS6%aGY(?`` z*ZiG}oUeLjl}Ffo)i3e8u%Sr3KgE8eV+Lh}`G z#&ePL6>m242%E1vO+OboUyaS4T;zN;Gly`I^VQ5O;3DU%nR!vfMCS`ySz<0|zM7k( z{6c8Hnw#~5LTJ8Pm?OE!TG_%ZM4?)FpLqg>-mhzAHVmE8CdyVG%>M z@*y(`g=%Gj*$0Jc%_J16l|Hi%3e`%@%t4`A zX`1u6$XaQcxi0Hj)=IxQBv}a6%AlEx!d{i^fIMcKxm`sJ)yib^S1xiFbTj|-2&;_U z%m=!$Rx+l$*@cU&jNQ!$7g-s5m`|cmW$a-N6cVkBJLIR9m9eim2!$$RKQj}BDq}x$rI2W4>}TeoP-X0A zZa|@&(9hhALOG$IxfO+SLO*l6kZ5J>XYTO`tBlW@M}_dpxF1$jfAe3Ap~^VGj8FN$ zt7U*WnTyP;1I!s*WL_O$F2NY8jL(@XMNBl)K4-2+p_TBQS%CderhU#V<{~rgKy!~r zSf(9pR$xDrX@{D>35jOfq2`|`lxc^Wr%`BBhnnY5XjF%q)hINoL(PA<$V@xbtl3kJ z56iT}&HK2>O#6zN$VFz_(Wb*iX4=u_JT5ZRjyB6hOf=JuHj{dZHAb0sjM?W2A(Uyy zn3Ycop-em0?Alx2A$u2Mtob7sIfG-(GA?ok$C`f%iO$zJ^E?X8*EsVMwxanOXU3$m zYsvW}xn%K`a1QqA$L3*_2~e$=Cur2vyHq zGY5sL=PENFg{o(sS%E^;^Bc3#K+z9X&-G>k7x{kOx8|rpEM|s6m2smv;dvoc8Mm0V z2Q!gXZ<|@gW8|yIZDs>c46Bvf%J+Hu>>d~8Lva=TfHLbbBUZ14hmQuz*Ek=X==YUOukR}^{` zS!|}FP_6vYEI^@JxzpTk!x(fxr&R-wEN9`Y(?+j?KgL04AshV^MHtn=H7Di zCL^*vu1NHa=xm}wjN>g^_STh`=PVVKc*@qI$!^o0Ti0Af6Oon z&DTF>1cm17AG0e8&DTF>3Ky9V|1s0J$Qk^{%=8GG!OP}yE^-F1n;W^v8LVlQagj4v z(`x%F8!RH)c4!7;ts|K#q6nA@x$Q67bu?jW~YdSB9h9YgJ5;G9FqzU=_c~B#Xyng0!&;-V!nIKr8lK?X6yK3&{pq0P={{ zEKSC&1?glpNoSI!uvWz&ovqgIFp)b&wc2}RMVX?gmg$kh(8{nDa1noVeHJ8Wt@Fsg zAdYqOyR2uq)t=B!mPv+J; z*bn_3S-Lfu%fvXUu<6#vLX?5}6-CBV{*(Tjui$d8Z-P(*Y75;Z) z_M6`6)^{i~qEgCbx^fU!-3S<;bnD^w<;WNOs3`APCKr)^*s9C0rgD+Lr18F$EhPF& z8t+?kP-tzuZ!O_6F>Z7z{DKJF&->OY?1#=;?_1?4bk>?=HJK!4hu)W%WLYS5*80F2 zfowcS|BR&xQ&{=Dm^*5MU7 zSjAlA|F%5eIwORi(B6l8{>rNRF>6&6J)teInsE_XYdXX%uy%W7F33{r7cS}ie^*}! zvfQdWo%a(*|3m#s>n@ZW_!Szq3sze9qtKaurS*uA=$U_|WunlTf2Gw8ThW<+rPT|C zR_02p50`9ZAUyj@xSy3)Irc*tF;(~GSXJ70EIHrR;!$gTv1ys6(Z5E5Nch1SblI;8hplB8!~TD21xDkr z)o`Kcnf?FN?0=s>Vg*sY04alhj#z<3EJlv%FIEqauu=Whn#@Ix>T&CTo|wDf>Zh#v zT;w=cTI;yTvuLH2w3uB>o<%FI8C>L9w9@LjoW;ohi|w?Pwo(X<{AsJ#Y9VwMJ!3%= z)~eP=Fh1<$5S4n$A`3m&&mo=Y@=sB(j{5G9+6kBz?U;JMi*Q}E$dQ{G# zL_pXt3th7=p`=75<{NQ8y`!yaqx6eP1C-}M*e^I;v+m*|?&lpCjce9J9yxMfjB?#l zJ+ct)`Cn_KN7(x|HTqI|w z9uRjJ>mS7>UAYy6#l-rTamiA?xdYDpZQvD$KYl%Hm9E?kF)QE>Z}2-@CPx4F)f@fI zzGX34N^6K&12H%H`*>s%NFD!dE)&K7T?Nv>f00YN(jHosav8Ki^b-bQt>XQ8D37Bw z^2g?jr+5zJ0K_!&cjY49Q9KIL!k^}mN{|Qq%TPu^D<`Fz zHIUx^v0Sp0BIqXw@|1rz${vuOApQKqidm~Hq z7p>x2)l^;sdCi}JavKPX8RsuSX$kT+$lLyAKj2eKMkcb$cG^B`>Rn{6=pG$ z{i#2SYXu=@CP=0~2St5f;XP0DDU*1K3MS>M0$Ckf&8eLlpj^=DuVeg3u1{~ec! zN(d6v4=_IK{D(2-TljUabKMn?)YJTaqW`zWIzL>9U8@#bV@sjub^h2h{OU0z-(pN* ztx~vW7L)Jq$z#Op!HJL$H~IT;DO4zClYa{rks(=3fxqNGF$MmKXW6w1V~#@4EJtni zPv;_AZS&9KQdIlK)-{!laP@8eFHvp-=>gShn|~QfQxKM)3;lU0twC6RF7$8YB75HM zFX57{I1uw7)c5WF^C-PQeuQfk`Ri7(JIq#wgOq`M=fC?L6WP!A{svs+`%&Nf@AJg` z2CcsLr*e_w^P~TuCx(sB4*xNaRKY#(@L&B;tDpSK&WjP7`bCU#8hYO4SN`G>9r-=} zkzC{*?)8uTPt0EbiT}jx_gDQVX1~8*_5Xd+1OCBWCj-n^1lS zVduIE|E(xzLH2QJ%tc=PsJ|tbY$f<$P30fx`KbRYN^g*VL4NheU1ZnFRz`xv^^8%D z`Gc2)OaQqJF*AlNvzTt9HO?J;kwdq>71@1(lYuy$|K%r~h7Dz&&Yuy$Iq0qH% z3#15%er|PJU?vLHulVSbQa@3twFB!*&hJj-!%|S{)8V9D{AXbY8ascF>zz*A=Evag;{uaV_CZV|kiBs#BPh2%Bw~DlKe&kZzPdn+FK~^^#JH{y!*+@msC5gw zpNVn3LD(x+El>~T<*2maBIex>{b&ILV|HObX23={1hT0He5Ewd1LY(L+khZull-EGs1nC`U70>#~Qqn;tgY*gX;v(;-Z@>{T(K+oK=)*;PGvp&^)i*E=Wjgfp zDae38xktVPc|MSEySV3B5c4(2(7-5+qM@m4_0w++GL(D#q_W}ubidIZmt4Vd>E`ux% z{DHCqdR88X*C2r^lzkvKfGiEPx=Y*-lX@Uu2L_=W1i1qwH!#~H_kgSpl%Z5W%mW~6 z16NVlCp8Hm-v)X$51As%r+z7NbpVegal1lbWNL1FKcF!?D^jne(K8cHh2u0TQ)(KC~QAbSI8DAjPS zS3nL2N>E}RhSzZ*zXg=LMJpyBfcz0iLaB{%Ixqp{4v?7;^H-n>r4`6$AQuD8?h*Yk zSqO3^(90vaApZt3Jn}6_&0s!CM{HF)SnZJ_h^ZG$Y$|$YF{L2421k125XhauT$BLb z&pp9%l%6QfgK^D7KTOyiwhXEsVRzUnILssL4%-Ijqx6ARCqOy`52L(@{UinBn~R>A zuzOa6y-;2UsRprvGdwZuDZ;^0kFcjm4%TlW`eCiEgFF^YL1FJ6u$}T$u*@SFAbo;E z?iH=xg;p0}Wex}~@<^RtG0LFeMU*UvxdY_IVB40W6_e&5!-K<6W`aBnGAfwoktC3@ z!MgW}R&yZ65AtR(#Urd$S}+G?AzmvZSmqIStx3Tq_ltg3LyQaZQE-Sy9tW8oEJWD^ zF?~UDf^{Ddt(dS@vx2E8KZ3CR{#h{7BhN$37r|nbJrKk0aAEMGN7!>M4JNb_J^u_b z?74D-{XN23tqx8@VV@I?1X&v_MLFNMhB6Lheeg6FSuZvOFJcUq1h-bOR)dTrctQvqoNg)#t_paRFNcv?f3gY znuWS{mhv#jy`fSRwsTob>rhG;5ySSE3eq-|?~x!#yHJu(#ISwQ9i&5O6iN=<^OGQt zgjS-kopKQFFfp`^i`*$4LnRnPJEdc&t15b?ostwffkHdQ7aF387}_aXC|(yrJH;Q` zfkHba96F6c`@#ubK%rgWgcL*cLu=j%sVKDOyNCLt&v|Jp-L25^TnZNPl|qM&HoTe@d#V@=$0O)Cdn7C|c1jhz<7{EQEGJ zT)5gJY!}oC=e;0eXcsgH4;dnac0qhtc~J`61$Tt=P-qu43Rj^}#%>&bXsBpK`=UwM z<|6mS-Qg6Bp?z_8_^L)&$;1N9|@uLye8~U6GH2GeRu*2)q)M- zY!q7I8^d!^s1|GtFF>I+zcIW5g=)dZ@H!OQ1slWFD6|WtSb-Sk)Z*jypBn=Xb&ekO!=)75Y}3hgGv4$c!Xw3}k>u_&~g;_STnB8KY4O}4sN z2<@huZDolRwwoH*S5as;-D#g#Dq?6iHL{m2lfrgW6FcW?A+(#C*<~nHFPhs8mWvqL zUoGrMxXAr=ukFJaTF>{|&!Euiz0V$qLTmLtdl(9>!~5)MD75Mxu#ch8YH4NHUm@;? z)<$bP5ryvgLHiXh@($bB6EKF>PaC^!uIPtGzMZ{`q*=6?!hs9)Uv7W!Pg-=(!9#4TYY|u;-)Db6NI96nZYd-6~JqAw5^X z?utUs6|^UFk#`uf;fCQ~QLRUt*HC`^T~VYo{{N%^s@@1#&oYL|>mXt5r?A#;kX7(` zifzx}*D8#;3Ho^pVr;wF8g_?8G1RkTw??6>J9Y;y$7ASPj%{KLT`OV_VCuSd1Bndf5j-D@R(jG8VGyRWP1RYLWQ0^*?tjYENj?#xv;djQ zd!CPSBzn(zT+)>e5cA|B*3YoD{C<>_L-4LYZ`K_UD_97JeEV8@3MzrGJ7~crj>Tdt!ku@h2rH6e+i1HHLVIjn% z*tOS-Ycbgg@|fMyBZokquq}_A0(r`Q24xJi+FY(E{p>+p(v>M7*CFOvyVnNM^Ha~n zC^tU|e|us-=aHr$gY68DuvRbGbA-gLoLoa$3*TEBVb2%BpNrkk2z#wZ*!{d}mvRxG z7$!l_W9-5C;_7qZev&~Z*wcjYeui<$^~h@=Z`ywdiCYD&*h)yVuVX)3K-kJmv+HjZ zJ@1K13n6jGHjBU7NV5|$<`~4>4|AGk_eE(Bb2{?}_%4M#go~Whbo&bwn$vgeJTBSF zdFbaYxWjks6I`;C8#=&eb|4w{_)YA7Tli@AuoSOTr4*h!nkh&=%J%*Jr4Jyu9` zcTcsmQ7%=(w@25qm|Set5n8Q;ex};jFsAVZv6rXX@dd1BxtFKfO}I>qqn$g|_6t#H zpH8*=bCLUWsvWyU+#&7Lsdfqq?bE6Dd?9f`xF7b3^GEhNjG>+Tk*#hO{m{;xW=Bw- zhF0s~DW=)|JhB-i%N~XDJj5)5wK3h!^+*DIraQx4k1`r!*p8cN7jj8gJ_Pw5`pLGd z3&qvhjynwUsjY06@++6R9;pJEWA_&l-Ep7WF9_i~g?)SNb9=l;*gb!4Ps4s@!PR4W z$0%Rgi##!PxvcdFTP^eL?H*yPw1=o7_8AVxW z=b_LzFSHA}$lbKiu12BVw8)PAPL9THaEFWRNkXDey2PHwMee30cFzAN^SH?UwZt|` zSkKdycOYwB&Q+8p_HdM9Ha;ui`$c!JnN!X(!(5Rw2*?o7=;s^z=AXnJ(p;>yo1s|H4_m8i?dOEV z4S~$YuC>-4g)#=@GCb*8`%{#OAncT~)?Ua(j^SE+g@}o+)wR*nU~Tx`JKLvg?X^5c zeB(vPW^b#XSxmW*xO&^*Ge(eg_U}TJ)9}q=Go48_N)LDnA%jch`gsnXeVskhBQJui zw+{GuZWePqhMr=RJ%Wqa1uRQ! zvd3^KQYdD#{kA8@S*<9W?aBX%*=px_V%W8|+VecZs@_(6u}4_d+h(s8688q&;dr>` z?e+#w4C`mRz0D)6pY3*uM_4~a_C79RE~Y|1#rE%9iehM-zqikeR?2p`7F%`S+p&AZ zh_RX?B!i3ShmFSfb_JJorA?yvhSCqVS;ktyuTI@0*6I&-4oXK@8#AEiAM7T3S8S2jQlt1`dY zCs4iv`3GW-*-iGd`$^|3vqma>HfpasDDIH0%(@^a?QI@mYxR^}%0;fs)6pIGJM

    `5=!{V|4G{cUG(Nms7G{X7iUx@b>2BA)c-jy09eAeZf7zX)j#lAEF^ zSL}6MkUEa@D|;^ZJ8_T|Aa$L?T*RoBfYf)C-}tlhC;bhiq0`bMS3sIO zDISS`3VtoYDf38MkVNOIM{E$?X?0BYJP;(}d?Q2&cY@arAdfpgdgMKjUd}O(d<62O zlmEMz9oA|#$WzXY<3dtEmVxwj=A#S-S;eK}AM)yK#QHj#M_7jJ>x4bRGGsp|#Um48 z-upW*3W+<{wgz8O1Dw$)b+^Jd5unuo=UpL6cbHR_#|AjlFs3sItFQx{FEEBO?Eq&5 z3RScL&RQYSdOE<_?2%948^{BkQZDJr6VJvd`{7BSa}rO8(O`MwSCGL@u1C&-yyP@I zDPyjIjC2yY$V~E@^NfgzuKCxTQ66D=ev~uGBP_GM?&JtjroqV9eL6-N?c|>l*P08$ z&KskhLX^cI>|Kb_P9+yy&oIuToqs)YC-gJMX;LZr*$k~%wi)ZpN7*-5Q3k``7mRbZ zaYa2lQ#{jgT-p5Jg%J@PZi1SbcD)wp}$>Tf$cghcmgnsX3k z7R)KjEosglC|wT1FVVtNq&d|nUqejz2$R?|;_CFeAkArs!tyE`)imc(A$;W9z}3^8 z6puUt@{aSm5T5kL8?pDC zJTCAWAI9+F>rARqVjdMT(FvYq&n0HC8(jT;rx%x^=$Y*UX8@Op3R~g5A?5?;6_i_{ zA10HXbd;7Lli}3#p_9qwU8N&PKZwb6rk!KgN>@T4gF&V`Wn4radl}>-r}lY1&e8e$ z*lB=bJObY;|3y(gcJ4y4+t*MEo5AnJIW4(lD&2PWovb@vr33k0Z+@H<}xuls!N?xA<8R{id?tUIfgNJB#9H_Qs?0- z;tn5-N*5G%!e{$psbiybff#m&OPv%U{7k}TXQ{L3s<N6F{0^;_ISJQA%)20`K)!a;P(BCw3uL)dg|Y_wS>X)%SG3v|l`J7~ zw4zoxvoVHt_X;N;g?9G}Cnx4M7}beN8T7->8@W!y8ced4BOsUH>bXt}kK6$7m8^E! za>-K8Ld@+T-#A+^<{yw2AnTnUJkkzigHwjB>U6HDB!O&n%$nj_tQEV~X2JWKI;@q5VcEFcDd18RQ&6)8KaU-7$~^J` z^nAccs>@mx#bm>^*g8DuI4HCd4mv|nXeAtUO1We!^P$x==;x3#s2=MlTggND*(u;M zT`2^a4KcqsXSs;{{1r%rQ|o4V&+NH=b>cn3p5iyBl}FxMttiKw3L$Y+9e#ICqfmAD z-MK78IfBpiyHocTel0F+H~sG1>5=7dKgXSx9$5o&!f8@pv|?k(M(mW6f$}Hxyb+|* zY1lx-}=d7a@Zs6Y}_#tcZa+dI~$vk1TLbVdVSy%sK_8LSxPf_uGSz=MC#wkS_$a_ z@^mDhi&(wgLHb8dcw_{~OOb{R`L*I6fqq#2850?ZLiu@2WF$&38Z!fBQUScKg0sz- z$mb~Z%4JMsIrRe-mgVpq%p`B z5%XSghXX-6fGmzoKp6$n6=Y@PBA09>4P*ewx=8JoqURix4UtADc_1SoW>cgUm+8t+ zAa8(diQIReXvO;Z0AzclBMR$>$#;B*ZKN*%|5o z027&wcSlBW5xK7ZK1JCR$>1V$_}<72kFZ>~FH)m5>t~`u8GC=E5lSburm`BYwLkI@ zN&tkdy8RK=6SJPj%tPskG3Ak56q@()NGS@Vw1`86_!i_G)KBCEN`XFnd<>Jj$re?-bX!t(ry$h3!8&l970 z{$ykh3g!8ektIT+dH!T1-yC!?TgZhlP9r zatfp>Qi4Kx{#@iR3XSUd$W;^?)$@_D39O%NWi|A39{Tw!ass6gMS9WXS72=ghDVK@S6ZMtI&nWad?n>lO6nd?9CGw9D zXV*NML zs z9=RW+wtK=O2_SV`ts`$0N3VtJx-JS;hq`WWAxaDAnY{;6*L@aa=ygh6_Z5u!5$Xkd zRZ`b|3u8J!D>kZi-FX;u4q_gKtJif`qtt@eJ3@-Mq$@#arSX_DjG@n1>$yWZi6^C1 zSI?bQ3{>0FZcBNfKA5m2juqh>Kha4c+c2v=Z)e`=cy}p4s~wce&#|{jjys$erX7wl*5O zWhfh<)iAjFJ#Hn+Pbf{@*v{g9eg}CCVw$-LDD1P^@gU9Ju|5%V6=Kr3E!;4k_=J?XQU)IUKeBdBuGXDyqn`bi1IAR zDTvWr17#G7;r2rL2;_BWWw|e+ECTrho+99;p=?75xu2l?hGM(RQEHj+4HLLl#NCF{ z0Aximd?Uy`?vY|xW8K`khPdbE5OW5u)x#Z%(g}n;dr$Xm6dU9p==pK?6O`T{UE!X4 zyI-Tc1oAiZ^R!!xG694=dtbK#g{@4s-=B5cnf&T}53={Ho^>rQq82Fd-srP#iilCB zLqBmK{oTnZ^FSJa3~*iupBke zorH20gyp({?mCn>i&|Bn+#8iRzqnQsNJAKnfo=lI<55XPc`YhwC^JFWse7Qi2<3AS z_Kx&Gw-m(>@6w1nyojUlSji~J=0d3Od1eUd)d zEfNw_1^xUEzo9hPtwLdIBLqDUarF?tR&;F)aeH!+?{E%r`*4BRjDGko7H?IK(h%iE z_bN(DkX{fo)SVP&J!dNe;QJEKfV|{3v4wPinCC%;yQL`42Wu$9Kt{N|91#B_Sp%ivdJUvt-E%t(;Q5c9hG1Ik2{vF;(1c_29u^M-o@`&kdN z0OT#VZL+xfE|4`K@3=!evI*opcQ%(S2v@ zid(lElj-o6)ispe&}ynX45cx|90K{sorH2f$T5&C_bVdw(2k+-f3pv zrbA z#d;RG_hE?n#(jiKQQU30@cI=pXOC z--QdozoJ^drPWYu=x0+@*z9!W@{*_3q{Z-U4R^dp9)p<8?mRA8$~kz7z98G&q$kBV zGZ_R@=w|d55)*`9ZvZKFm!&c}9z)mq!L9!k6Y*T^o_}!f@(8=3^OVJQ3DJ}Bcs_Cm~l zcRtEwkRu@FZY9dcAb)@ya1;89o)?2ufgE&Al&v6_Kn}T+P)a~*41n(rx@iOWbHU%M z*Hr$5@3Z~vt`-t~(j#s)#+-qe+R*BVyX-mEYP!-*g}=H3IqF^=DEhe$G53NTcaIEW zBF2ZEB~H4>Jz_x2Nw?YaJSOh75d2~-$e(U2Axc$qSan=t2lE&u_AB^g8|0K*!X;ff z278cYn@YF+3oIsGc?xsr_h*mvhkpKcuM3H56t1cK@2H|&bZ;CYo~sQA`&P^)w?0Y|2>T_&%We}ca?M|H zi@C^C_Z2tcMb?k_-+x|&JG|mnaha$jLqEge>Q~*kp<;ZPj0U;pHb!{{WYai!o$Ed% zgrCe=tLtvaBdpcGZYs*l&}!`-xNP!JA-v+VXIGNn^2iiu6_Y&GBcFiOOrDGV(5Hd5 zk{5B2Cy-jnyHV%_azkGY@*cA^BgAtOTi(9RCt~b`etmQa?F^ ziyXt-lgGZwVzQK1;dJphd?)Aj(lWW*C?3P#Rb!vWwN8E(h2C>$ojlwl z>@NjcC%@s5zo4H7lc#y)GDw@`xYt?F=}Hk?E9N=)O@rh_F4OtRsw+HuyW|;M;7pQS zLy3X<^>Fg@qgg9C8VSk6xyaE-NFMDGHX7}d-}VR_jSk6^JinhY)Peg+N}fGN zJp0dZhwOa`HF+`0DG)aDYVul-Z2AUXqa_z`$%4N|fZPK8{6F^21>CBs`~P$Av(M=q zT@=YJ-GmULBDx46t-WqXH@ZkdHz`G>q|@cdB|1(Wp;Ssj2qB5me5 z|98$Y#$Ic$(^h%k_y7C-pXYyidd4$9Ym70kYp%KGnse?J)*3_T$ygucT_k2HRAmxAc~%eC$%g0*+#$F=4& zIrBBVpCK;Ks#QR-Z-Pz_5Z|glipWlF1(I)V7)>P68Y5wlOD%5yYq-1?gekVF=Zi?n!!CGebnzdvEJmBDqJdr@b(Jtc8XIzo*d0TE#?8 z>g!|eHl$GH{eWi;(%jdoT%=?FDt0u+`?~k*YaL@qVeS^Ja2t=h=xfy{q9%QgHLyrw zUuz`m=v8o6% zZ?d)+Qlb{X&u6f@$?A8Xwj%Nk$Y5(oi2MXH)Oybl>Jy&)3~PI=Z6Ol73VUj-Uqhrk z$gNi1WGb=4pFqiDMPh+f)sWy>P++xSBF}t!EXX)(gz*#f zkmIazhLpy1ot|iPraz!_T3~AVOpx0xZ%TSyjJG-(QX2bnM_jdr&UmZ-)O4MDtVSVH z4mrKYnr}#HUQrvG6`5cyV)7WseU~aV!CJxOHP$Jz-eU3z>l9h|q z_gY^wIR!+|ZxmbqV$z0nimlyDu4SExmU>9%qBO6NbtYPQOio!s>)t0>RhT>i9bJQy ztfPp;o&wQzQDW6&@+yeVdx^Dx$=e{8p~UX9)-(AU&7>!GD2Vnm z*&4^>co4ll;eKm3lXF1y+PMd;Zqv!i0l5aAAFzf9K}*o1_Xn*hhAfX=37zYp^PshQ z2IajEh%T`y)_NwRKy>X)u|8&UKS&+)QB$oinLGia+v-$nCz04IAUcH)S;HTu^sEHA zXbaw;t-G15VKUvC&g2Uaor@XPlT3a|k%dI^Vm764hP9M+>VW7J&amEO(h_74^7XLw zHIsHAx||=jvS(6ydV}ctnrT&KG6F=G^Gs_NlY3cbmNlQr6Rb1Ksy~bTEN7iZtY%C; zVVy^;`Al}R&TMNrlL`*SHQQ?Yi1t&Waz4cRM5ORhYosC6M~w!VV?At0Y2Hp)>5_lk zdYnnzrILT#T1q5V6-4Lj3F{pu$ARd4Jz;G%WVw=hpKH~dZE~S>FQMDUla|Ls&S`(r zT4YEt7JbrEk7}zT)gGS5BZW^|%jal9X+9l&-BZ@bA)-fCPg~nVWJWPoI$8A}H?ik+ zgP#}IVZV&kgh=Xa-e;{RnMl9-oVAEZOup1SXVrbe_%YaWh~!Q|nwKCwOROK5 zJONT_$Zt#%ZD}3j5-a{pkml7!rzw$Cy)U!c8d9pH-j`YLhpfJV)iUcBBDwD%uHQgb zTIyNtIj~x36&VsdWuRmJO0)F}NqwINV)@>o8%lR#9dWcj;?C)5)FPWU?_IZVVzhIqp zrXhu?(pYD;V3Om}eY(zSZAh_d2T}`u)>%CkYCpy5YLNOM?^(;4j0HIphTJt=Xj9YLB6!=Ehf?gWHQJ% zR_+oadS}_AAUmw&Yed>W=XsDHtm;dNbOu=rvddb}WCX}+kYB7R%Sh)QkPkq9wK^^* z@+`<#Ab(mtR}fhW@-s**xvEr?BJ~bP_SN{ho!o9nvC^I^fMh4nK9ics9)=VcokCdEN-kicbu_7yRBuyynt@CJshgb4q&3JC zkmHk8-yt23b?PO@F}VU{4s=dRZeY@zb?PTOuOX|OK%Rxpsmb|FMu5Bm(jd8&$(>Bj zNKSZ{tfqkMz{tE&atV_sK~}=*%w&_bq*Ds=E=ZH)W+rPuJ_Tu(Y_yJawlHat9M0qy zkguWBGP#CHZd)SfCy!W9R!4v+yg|20j$={}D*4Kp2-HAwN;_o4Wjpn_eySOQm!45-pM(ikI~Np}!+4Zf@;`)|=!Me4??XdCC?%UW_4lc7uoB;RK;F@ZHiC*sRm zayOHEFGP<8s~eN`zNGZr2+w--H8|P8kl>AeaB_(un7M^cImA9VS@|n%6}%x1Nvf|Y z7vrH*5jsPY1DVVKISgc2axRmnL5>EwCE02#S-lKW8)QUsfg!;&_txZ--)%K=fF&AX$^i%wl+pXtqbUvGK{KAySBzaCfp55w!+sz8JCJo$N{^ z_ahKp8uugzvCei72Rip8A7Sz{NC%J!$x=fKbKk5KR|7B>FG{X6BzRsFCASz-tg3Xt zvl~`L$^PH#6c(!*AiY2)CVTuyq&~IRs>BvVS=uFMu2eQkra;OJq67Ss-sDJ2H6}HEk7)K&sgjj84#2tJw{VPO&-~RUO-+JaG_>o7$Z61NX!i_}CLoRNB_YxZq=|h}E#oJ55#rKw zgH7!-n5+i5?qyBRVX_WH_nA%Y^O<}CqQ9Orwc8j{pmu=hndGMSNY;s6&R-eq35Enc ze>3}%+B)`NUbLCr){vksn%OTHQli?xkApNfvzLcR2ax7=uR7XKiRuiUt{^S!AtBNe zq@}HnB^@~-MfL?DqOC5r z?GVvcm)Pw>L|a{AcMTD3)yD1_BHAix_YVH)t(N?ZKF+{YL zXFn7o+A80k9U|H)-+nqowAH2dB13}uYG=1TDZNhH*`6UK>PFO#KCPvl{Q!~N+d=xH zcG}ysS?AFQ;_602=7-1#kPh~mhUqof(cWrEk(vxY52MvxW*^Z=>lCTkAY);5xqVCH z^z?MH#~Bi&xszS*9P%tF>}20SB$dL>_6XKF2+z2O;kmPYSBN|T(#77~EIox??cGco zcEx)Ebgr;#H7A{qv17Mw7wmerF9?y&Al>cuh6MLT54$Vtbh!dgEBNVQ_X&|zAXnQ% zm|PE?cR{YP?_hEp$cG@;+LJ@%3y_|6ofedynb6q|(#viTB0qujwjW~h9CY@8^s(oL zNV#4Z)7nkXB|q;#r!vTO_9Y>5BuGEI50f9EQwQXFdlr*DASZ+Lx1R}-Mj!+1S3{&Z z$PM;tCW&sCl?54S*KbL&YvO_ovUi6_N01xs<>zUgB2^VSr(=EHO?Kt;iJS^@C3J?^ zNkf8?A8NNHozzNpQPq@P_2@%~V+-Xk_5#5uF zx95b2wi<6g6C&E`Zu{jB(N+`eWg()iCfKV(L|YZv?;8@-SFyd>kl^kvwtvm1@_F%P z>_SG~i|yP?12P9>qP?me5qUaHvNtn1__nxu8agF*h4!SQ@7#qT_uHq1$Wo98?DGu? zN^FYVCPZ{Drr4K;h|a}SyNe+~E~eQ%4GD5F&F}i|n`X~t(j7#%!x{D` zhLorwAi71(w0~l9FUV@>%(BaMrd(+90my8-YKUwGnPb-tk?%m}+AlECD~oo6%(GX8 zNV(ovO=qv~LY}wW5?570Ua+@>NG*^B_5)oh7mpzJlRy^QbC|pYGNl2|EV7p~Sqh@t z;j8wSh6Lra*xpGxsk?HqU9S&$-VLjM@VwZbYe?|iS#EzwB-N|0u(z^KUU$4RBCZv7 zy}p!-LztA>J(<*IveKTzqydvv_Esk6FnQgseI5C^1mqT^=MB3-h};hHmVFtMtD#d2 zvc~QeA~QhN*+WBQ9>@oFafrMOveAArL{@@qvKNHNI*?E8c>^guBM{eSkT2{dA@ViI z7Q4crKxYTYm-f*&61fvrv1jl_-)>|`k$Mp1N9cTQKV(QyJKOBpY^7Iz{t2CJ_JR<} z?t`}pdv%CZ1o_r(IF#a=1JBh!w%g~0NNtcEwi_ZRgY2}s7*eFPRb!AJ>^`jX97s!$ zUG^;@;(`2X-(yIS<~{a(tRrdOV?P!mI?aFCi$bI;h;oLH&}lAGuOhB%L1NA(CaXdE zfyA9!w`!fBPsn!ag^2DGvYpdIME40fPSX(4{b8ryR@h(&XI-$X+FfMYe=^P@nK@N4g z-A6johgWq58$u=jEOZWY3Jv)x@9pk!^Da}(DJBxr-*?`?yG%9bF(xma99PeyPdLI^ z&7`Csk)xe|8A6_4g6A5}9wNE8KtYl_rm(kn&IGNeF_MZT86PfaIj zNbr_a%b7z~W`1lnbZR-vLPWo9)pp(u5&gZgj(@b z6Mb^B{x(|AInj{d?7n)=>4p^I%Nwn;s^^@`bbk?wyUN!watj=^cg~)FpO`J|sC>L5Mr!U@@o!%i*1*Exi zvmrq*(bAb5A}2zprL!bNbiJSFtP2rc@8>&P4JlGJkkbaRy1;2YmFitq>9lgX5m61G zqwBPlvy@3okh5Xc%K471TCc>)K0|i1j;_-S4XH9My-qK5Y7jAPwF7i6bWRG9D?u)H z&J2-$AZ?t!h7_pjh+X$=jx&kLJPJlQHNkLR=uk?|mx zI&Ei=&RSSa0cr2dGbET7z07%ubWECaURCOHXH|%-_yTM2oQ^Z8470D!QZK_#XQvyJ zi%Riiz?^Vr=Q<|Ww8nk$nI<V^t^`jbaTcqxo9S>-0S8P8A3U|cBO*OqaiXJ04C6b%_A%2N*h1MBF#FXJ+tdr^Pj0us)K(2B6KT4k8 zfM>myrmr)U$%jm?b4D@w97NZ9Kj%&+KQX!9naHHvHKfzunaboy5Pf$KaAq?(4n%*c zxxtyoM7Nd?P+|j}S&y0Ym^6P2a+9;0NN#;t>HcT1bI{{PC->sKxcc#3O%7+$7DVrc z8|>6%at^G1fS|D!4`qjbCI3{{uwyul8 z&I3%O#~$pwKqS>;4|Y~Dk(Mynd5^6eq;R)M;budEC+uLS;#{56VCBPL=P*Np9%-<1 zOo-_Fbg*-Li0Bc>5T}75f#;i@T~BGxC2BHaKj=E_zja#ABl0xJAt1w?-G<eN9F>lbPt}MIDd= z=Q%@ye2sEm4iTNNQO=to^6(A#j^cboBzG5L*Wb~`I$s!4qAFjDxeIt6>--iX4MA>m zsys_M)jIj`bGuWI$&pO%bQ&?K&1AfDO~|S#{M_T*5+ZZk;_Hbs(U2l_CdXdvEC~@E z`y^+3i0IhwbB=gUr-!Vx=lh*jA)-A$==3+F#C(Ce06J5hRZR3MrlLZui*x=6kxQU6 z&1o>7a;kNFkcXWfA<_Y4rZYK2KEDL-p3dqJxdJ+mI_mkr&$S?rJFP?HI*=!vyP4>< zH2Ms;r<~~_qQ84S?UaVdjj(#g*@{fO86~_d(V=9Stc`C!h!Y2#x+d=PuSc53AGfZh@ZESrsB*z-ptDc!~UI9i8Tn zoR&;}U#HAh>5rYem|TvyzJ=ANPKAYJrF9;;2;Z%oj!b$%N5}QKGciPTTw9zqA)@2@ z%Bk=&`8l*=+_a6YPRkI{En%B8$dI6~`^Fj0I=3ROLw&rTI%7g)Hfs4>XAP5L=-hS! zPNQ}DFVb-Zes(%T3@K5MLgz=swbNO}WFE*LAU`;@UZE5|ffek9=i~j1M5{a*p838JljOXcndIe1>|aTX*gh6Lr4>17dh_HODJEG_guFj6Ulq7+lqB$fB_?n{Pz7Q3V#?JFtozQ$I%GzKHC^6u+QBp2n~FW8TiMg{jPCQ=#|+-*#x zG%C2;nMh76xVxCh9+nDj;x&`6JlTy>!5zXx?_s$G>8aorGSPcjbZJy{=NUpH^UAoF z4|YpgNA9M|?s_KM(GCxXPG$EmCVEewzT+ypxl5^3{)8Cn)?M4 zslmfJuI1U^SEE`!(*4Hh&|RQ+i5}(tN>)nB=V-UwGE-kl%BQ+pjYw*5R}J@A){)Yv z;Wl6$X}vYv=1e4~HQY8#B&Rjp4ooDcHQXLdB&Rjp0fsDBl8YMdt%d|Gp{9EqS(%<~ z{3A-$bnj*&Euof_e5xe`L~>eNbW$zhSRtm~^&YW+oQHc^$91ebiQ}r)6T1#^7t|H2 zR7*JCokBXPmT-JZN6P1TcN+VV7InNko5?Zotlu!)cb5 zaF%-=6M4opaR(VfHK^D7o$Zb$om35;;}$WId^L5a6G_!zbN5Nsks567E@T~fQa5*3 zGLd{Wch@tKTr_t-VK2fom?bmtiojPzQ%%M1xTw{+iRtE*tO z0{vl2_k$2=v=+0M?q^K&8D;uCu9dqpMD&}>g>HpXor@ClOAXDCp4M)I5NQR{#%;^w z63l67KaSfkM9Ou>{GmG}L>yRosaB_BZwHc}BHE88?Nj6isV&b{8TkdtOQ=smH)e)CsF3zoG$X79m>q@uF8>I6Pbo6hCT)pfO(uC489OMSKH4zoNDy}x*8^<7b4wH%? z+du}pL*7oe8t%>^lCm1%=DuTelvowG8w?5Rz0lokNQqjB6zX~}bhn3yuJ_yA+%?({ z)v2zpJKU-vqU-%m_uLTC^?tYOhlsBC32qN2QtuPp$swZaeUiH>M0CBExbGVhwBE_? z7DIwMo$SteHz*BVr<2{yh6Hsw**$Kp)+ti!kkgKMT1|0V8WPm$RChZQslgek7WE;l z^gPnc6w$3k*F`{fu+E&6j?Sspnd3gYPRIULO#D3QE?^>lo^*~Uvw835|q!2?j}Qm`g+lA@SgS@+?5O5R!pQW7P^x|MAzWU?%WX3weyPmM~LX! zdDX4_-iKO zSA^Uvm>h;L4EijlH{2@?33B?D+p7#25F&bJ@-6q~5YaQPZ@XhcWWr#auH!ynNNL_j zi|JQ9*18Wf(N70GBfZvroQRU0iEG_eOk`jGTKA9*x_k^>)e`#P@2bmt<-w= z)DU?bW%$195y?H`S&D0;+mXoyC};i6Zll|cNp0xp?~EJWo`wV`v20AW`3|ts=j3d3 z`;e8HvEPGx=R?-)tBfU@DpF@81-TkS%Cq#5Pf9jUo z7_>uO&YRsTh6H`x=WcaFO4PrSadUpu=WZ(|`IYgd3+3~L+k;7O#IDbw+Tz~Eq&wb@ zzr%anSMJ0R`3YpJyM)P5Sj|VNZg+PvDF9gnvco<9!ytt#K)!c78WQ|&*N^THCQ=$d zx^tMwZ&&^7u8a8j+1(cLv)e8Ak&dfWN$cJ19>+vl`0wtyhEUtkW%!5N+7O&6_h{U# zuKClwoXJGwLa(>kZ%C2)5oG-` zrLsKrFS-obyu+C6Mq2dgXNP#l zgvbtfKGZvoiGJhw38bpmgvk>4(WIJZF+(7e#+GL z1~b_OKiaB}H;zfUKB#3_9qUaB5p7l1dzi@)(9u@MdCxI9iLH+JmV}75I>CF(kRV_6 zybY{l!%B~z>v{3d(pz0UZ@1A2@?OuYV01>SPOz$Qy|zkxk#1Grt6)fARp0BeMeESa zl5WrSy`4nV#_MUkTHjmsrO{EJrAXVah-^!dO-y!yoQ>G)dp*C_I>qWQkd`2)dXo(q zt;+Q$oin^uTgguq5IweOtkR?n`WvjEix4t2rCLsFBc(%7IMD)|MnOFT= z(m4-0x({#ebz@>NY2nRc(gCCmQh2VnhRKy6J7IO6H(@*Z=>ww6=K^mxlN&(}{!Ei@ zJ4okN5FdUn^m_h_$lYv(AB$r$B}FFhB%Mc6g2hq96_ZIv_q}%}Vlkd&nO{6N5OT8w45UI_i ztv78Ck<&mfLr&XyPliZ$kdEFWCQYH!8{~4Yo;pmoG1VI6CXjC4JSOcxbiH5cRfv&J zH}-s`w~0wVkXvBY!%Jk5&S(%F*R|d&*@l?s`)we-yzL<}4WysfGl#6&+<;x8AOpNz zOgs?nXP`H`D(SQXc?RT0ZwZsjL3A6t$=lAP7l=;tQ11^WgV@hdufpNlPjG(?^O_kF z++V}IRz%F%Vha%0FmDVKot~8-qr6{hr>AGE_Xm^m1M$3oPN6rYUV2=2dkYL1t!{yi zF6X2Zvm4TOz!oXG$5y5q2XA!vwViD$H87+=^#{>+_lsVgcImM%@W!>(B#3>1caqU5QC|#>nJ4uE@ANWOXB$HG zbs^$f;B~t^-OnrDw}up}DL3Liy$x?AUWHCtXS7-j;=$@wuT>W!Z!lTx4ZlK@V)Y(K z2k0#Eb~5=CM1L(^<~6)V>jY)E!s~gpCP6M%cuhk(dQ7{*yC_8ZV9jl**EU32KZg@1 zyqSiS=AMJM3H@#BO>Z`ni&EqgR_@=Y8+K zo~8`dwRk($R_}XD3<*ZL?|Vn|3d&iR;rm|Q5P5hLei6fa(2$_SHhK>+krLbJ&0r!W zw$Yo#L`rOS43$$#SwW+Em3jW?3~C@I5lz17!I8A@sF@U|K9RjT#w@OCnhy4dOcLRNWWksf`g zeD5Xt89#YNDN@CduTr)Ay?2Bmg-Z9v`kAuJs}mxF&`a#{P6-kH#Q(`VD@2Au=Vz}Y z6Wxbvo!#CbCc5X>o`3abFwuQDk%dfjKfeoeM8A6PG11R=?dNxIbBO4;{_wVkNcKp) zk$Jy_$gR*({*l+4_M`NZTKmcJ8!?e5e!}m@L_hJhpM*b*iGJei^yK)H4H>P*+(hG+ zTz_7F?Rm7i7evo_=K3uMP+a=it?&2pepe!9R6t~aA=InuGZxDGV?#QZy^OD}{@f7J zYbGoCxi=Wk#*h9D^n?96AySB(R`xw2x%oITK$mJ&e5{i;lsfJ_EC%pb%gc@$PlfgJ8{X7UDfo&Y(*&mTmk zu>s_HkR$y;OukN$2Z`j$+QK9K*9`e8bw0q6{sy+Hm>*Zi<1Eu7{dzZP&%p@~NBLct z$c)g@es4p*ifOB}P#Q=3qYR-Z+5)@}SNBUoM3?F@{(M6Ul+Kr4Pf^3~IGA#w^QCi9 z!(VPlfs)dw;ny9ab%He4@|ze^s3gs`{8feoX|C=6$wbmz$3JK&`H?i&@#`2uX=g?~V#Z3N_~)%2YxAoa?SqsqtCPQ(@HoVW`8Qk>p7zGunNdz=;(Z%E>_u^=&xM?`Q%?Te|Sbp zN1xxT%Q+yj4z{5`$CMcL8+wMSpj_CA+HP4*v}oIb?}Rp z(5Vk{rtdNNiOE_1_&Y;Hr|=yA*AUV3 zkWKxpThr^kxnDj+v{iGzYKUm77JiKo(WTnbZ)OP1XSPFnTKdZk3GVmv`~?L%uHddb z-(O-#VXlQ-bcWUW{wgM2K)M_9E|cp)^x2^o_#2tr1)@)$yukmQ$z!b3%KwJRO4e!R z|Hwqz;f4P1OumFpFT{1BpBQCIBUf7HMScY)Jui)`{?NI|Ka7do1sD6rFj)tk(%0~A z?jKJ??S$tB@8L(${YD|8cUxTIU(93=bo9EfHhz016^GKfG?srAljt5$z}G zk705$bhIDaFJ{t|{W$(~L-1RoAcK($$FDwG=d?&o2N?_E`llKav@y>=(-5lD$nQzG&|+Fog1@bvpPT5>W#Xmp(nXqrcUV zV4qY+zwuZdS5O)q{gxrJ2s!QOe_%*y>dAPS|0(I@wjM?&U|i;Z&7>QM?)fkCcQP3Q zqTBQ3{%$7YKy-V)+>aHS*mEaqKS*IGzY>#KAUYSF{OU~Rf#{u6o&CB@UIWqXu(N*( zleMhV#c#~y3)bo4w_vghMBjs5{fn674X1mstM4+Y0it`_EBp>j8iVNBrz`w!Op>hA z&F{&i7wdHM2QVoF(W9L!{Si!Ng6Q^qrGFQbCG4lWKat56_S4;;$|Uy|%KKIRBTS9~ z(RshhpJzyEYP`|IpFB>NYVghFYJa*R*yDU0-msDPtNrD-Yn>wXIsCi@a;;zgj&!SD zej`Il)Q`}4A3DAK>Ep?&(g>U+2GZN#WJrO^2iXB~o!|Cu<42vTYX+pBznaO_(D}uX zpA0EfLqPrlx!zCQqpb?nSdelz;7kL*Dif`vtp@m2Cj>f`pmT%o5J`=42l~wo3FZn0 z`t1#&d*_O0lp5&w4q08Y7{6lg-yR}o<5wnc^5+oAos8J^=yZs`h=^)YC9bZ<_<4x` zry-^0e9=?jd5C{dkxpSSk3Gb%Y6#t5*FCRw>fNh#O7ot9=V2g2{RT|r`99QdP9!GJ z_o03dCh~m0*&h@l`uTpd{~7y{k<2iE2NUVThxw|QVt)g%>yv7R`4yRbkRnGhkDlfP1Ao*}{8#8`hf6UkSh z-)xHZOnribxC;IGOmv^1PfWVauR4`uX`pN0q zDrjTHet$zsQzsr5`*WE{+bH%IhKOz(6aDq%C)GA4`F&<+&!u_NCzSX@m`D#$;*Vw0 za~Hkcl=$~C84RMI-6j4KLkd(|)6iP9&V7FFOwt(& z9esD-=eK4ey~KTfJ0f|~OWfyoVIsZ6ef}V}l3wCIe+-e-{dJ!|pNX`?$^LRC3*V#j zdnfyyAEorHZ<1x+=qLMg3|XGn;t<*aalikEA$0FdLcZ?z51*s0h)e-_z;9_tftrte z>0WfIzl3zu8!58Nkf2Vd`WskBdWori)yItI)GWkQ|9B?)7Xpu~sP`{^4^&5wX9=A;OTN#3NDFry^4Y_#8@6Y5^kf%YW`CFNs4YB}ax_|au z?Rj~uCCICWoM%X((!c1m5@d$&F>#=y?~8~1ZXq2V*GzxhlO}~q>*)Jpmj8?)!9Dnh zzm#;$oucP6AMxKcBzVHk_CGPC*!)t=JMc5x-^oP(2A}?=m^uEqr*vGwFU36OU;T7? z`8@7lW=IhG<9`1#r1O08Bfr1%xIfI0Vl@hJ=~KDp`g57w3(^I#Kj|l)*H*>qA&^{* z3ZC|lVA2J7--t9n?e{Z;T9mG@`Tk91$n8Xw>`IvLmoO>B-hVyzo9{1WQUK3-9_e|1 z4U_R8dQ|X&{{@o=n7ru!%w#rOE$|aBnDS8z;aSfEyyPdDJO>^9cD&G^&qVJ9-ichi z?7tc!zk)3C*M~^f?c&*K}A-)e!5E9mP={q9U;{8Q==Wg>6KrTzm< zYwxy#V(_`QvWO>sZm_1-<64s;#T^7Lqylt zO8*`9vl6p39`d!y-^4o7XRh+MF_CA%DnGu^#2(W`KMP*>s~ZxG6W;JwvySvXZ~7Za zN3Csulh*KV^`^g_b!3L>O~1*@uJtRw zqElF?X5qwuQ_;rO`SqCmfR@<@J1N%tjhM*k7Vr6&gor-fVuRn6iM|UO!_P;4&k$(= zvdJGAB5goE@dv$XVo$aCPyJy;Qlo-T{rice%I8!6876Xff9fx0@&rmux0cQRS|%?s z`ON>4$x4upi2ZZ_uMoKk2M4$fgi$9TxF2k{i>o@iKNOomj42gR5{1;-)16hESCQp6Dj$4e(qAr zyR^Ej{G*vjt4rjc%0&9r?EGd-q+iX+_n1hjmdjtjB!)AYbvw+>U(KX7YDbg2{LO|G zsOrf33&`m~`4=xsuZ!~eokK*|MWy_{A)@P|N`7I8=(?zy{~(jo;Q3W}K0JSJh^z!T zD!;;Vic5O_WAdwoi0=7oHoWa`We63DSuN; zy5j16Be5fEnoegzt>yIY>$>Dyq~z;H&bP=+>3;5EJU~&tw{m{(G&0EV{p_boYtmc7 z=^V-F%fzJ~L}rSA4*Mq=)Psbd<#b8Ds6CXe|4jb(Rlnpv$nOG9&q^cXYS&I(A28R6Zr+G2m&-x&PL&)=2*FUBw4$c-L)F%H|s=;0Ku8CoCzJxVu)<7#YI;u4Ntr!c+(uwIu};0Av7u8+0i-2N}*d^kp8 zc8Sw1tnIp7N}W>j@~3^do{s)-3>*EH*oJ4ox}oO-@KgZa}epJm>2 z8Tomg!xRsGBf-RzP;@<8)x;VD?Qg|WE#vA1a~&h#G(R}Oz9H#-!o2s=bpK?J=c};~ z{EMCV+j}}N()ohoKY`OXqF zO8G?VQ~3VXUr^st{?T|O{(tJ%;redN^?kXK(fgrnIdWCw`v0!I#3z1wEvM(5gnMxK%PN%bK_)#|TfymaU)TC^Rh)s( zVor8gS;6`eew_V~jPrGzO=fspiC)+36wgbnzjUv5_+1v}QGMZW^7XYw#?>cA;(coc z<(oKWHaJY>5m#H-PRd8Fi)_#1M9I$%){n}GIMYe@*ZsKP{+acJi&SH&J&;~pz1L3L zWvLv)6Y51j><4qC8N%uD$JJG)9E149e>xBJ$UX>1^`qB+Dy4Fy_bG`-?6Xu}BwrM7 zz@=UfF?w-z6zfwMJL#Ao%ltGBpUYvNrHtRDKIU+`NX8YlXRKRc`_4vU)rD#23ANqm zXRE8(-~Q@}f3nAS2IEKK!|y=sH66kesrdHKPW+3Mc!KcX#~0{PzRRXV+W%?%o%hW>!BOZ$=gGJ4}7}pKC*>mX$$cpPKjM6R&iUHLVY-fQukY#j5^9Ix+3H8uyKJ@QadoYc zSnkMISC^$y{1H6vq+{KZssd_?(9s7Fozk*yxY7i*kar(WRx zqz2p9Whw1Z-d}2)`K&n4hhiR=pI=cf?X#7+j`;*5@jC(hobAhYBUq9iRugmBW4W*N zQ@lB;^khnh#7i>z{KnTM&TrH{8veMGE-&QA=wl7L2`5xj6V6um6jFJ%V1INy&eyR+ z!dy=%xgPbmuj|qH&NK1ks5Ts5)L*(jel>3&>5uwL&wtQ<-!knOYiv2*zt_uFoigar zJatTU=j#)>-XCK51k0CLs`~W2=xy{-){*cHhU4B~PVG6+Tf=^Ke1~d8Ta0Ik5IG1E+cAMC$qyzMn*9nvc)Kr94DRfAvqlM~vij-)Nr082_2~rYwGMN+|N5 z&HX|S_Y37x{lY)9%T~@H7YCpkumVI5Pel&ZZeyE*_UXGf`@jhVWftQ2W zrKcCGi`XCKF9@gWCAk03d|lEj>3o>eEAo~()c#4v)ueB!9Sfh!`Z5nr^@?8|=l1y0 zKg{>{*gvHEy#sAGVwYYH*k8u}q`V}Yt{0S7rt8u2`e*GlsE-59pTw7*ZtTP7`%LmD z;dH$qf0?dF^C#m0d7jCAo&G%*E93b)m-K_BT<;PVKYep`dy1?58wZtB6L~zehW*L- zgY4rxj>50A8NCFLqq2D%h5L`~CBFw&-nr^SZr>HS-G4b)KW}kT`dWP-;P=an#JJN) zyq6o9tzI@VM=drI@4@`NE|WY^e+Q~B>Hdn#aU17rJBN32co&Cvb9kD0AI7RbBjf51 z!?B*4uV?ZXzm4WE_T?|Ved3gHj{iTEzB_vB`ocrZ5kNoQOg#OalAB@@Q4 zDj1nihZ&iz?&R-M|F7u%(|r9?yM2}0Q|tA8v+r?X`na~7y4Q?jb5(Uy4|%Glk^Az4 z-)=YY#nsOo@2?zA@OYBOH`p`0gr0NpQ|SIa-uTD7EXPCrMm#m|yRY{e`L0)=?N4JV z^HTDCn%cj--;^Ei(zqk8s9(lva=z~xvOju{jC22&#ri~-Emn2!yMD1x$LNcUtD88yulI$&Zx@ok zpuBEozZc@PV4W_R4;m5S>Gcp((eQz#3%{|z?>+RMcHr+}QqSq>$WkY+*7-=N6+FI5 zzg{-o{nk*smUbuMec45?7n<^ltK0Xhp0ID3`*C?rNW6bvj+yPGzUcnNehp5~WKNIp zXu4pieK zKf*BvR`@(ywD(o=KlbnJ+9GUT4yS ze#H2bbuIDKIKEmv%IEu>pUs?p;WF-*FqJ3vM{+xj@~v!7*W>ED3}Gq9KRMozi^!42Kh{ngj+1dYzI2XXL}opiCru9rcEY3n(z!l0 zj_yN>5BJwz;}yS{XNg=-Pbb!Yar&M#QtSf$4D)4aeqw4Bhe^iOY7VbWlQGWfk3EtmoKTJV`b7~vS|5v33z^G$SqXPAT-McPsavrtMBgu0u)eIDly!Q& znJ;~g^vEC9d6{t4>Z=@s(z z2`ukpDedDS4nGl*QtvM@er^)ppb|RzmKUeRfd>q5y3B_L0A$Ff~ zygx+bo=AGczS5o44#Zw~HReaNJdx!&EH7l)o@H;A!&r`IIhExj5t$yJJg)*N_n*k; z*j~aSCB5_=9P6xDFWrAU#YJEAqEhlH>6Y*7WEZr%^!_BSmPO)S&*7+@v{xCo({rwD zyN=pPy5+ek`IGO;bUjObW#Ub!AB;plZRX>0)So6?PUYN1^}TohLH?wjNA=|XJUq>h ztV6&l@!ZZNeUfhB|1=ye-#Q$>K6zC4d#7=@X+%cTe{ov6gZxT;?%{q^#+xGZ*={Aj zr_lRA=5_IMUEZtY{pOo*=sp%dB1K=M@JBhmbQxFTN7C1c(>Ls9Di85Pvh2Ej>Ay4W z6Uel$V8XYQev&vTGN0nRg5#I(KGc3>oE+>&k#=&y3}1D!YP&Gx7A^QsQZeSN2u z=bL<{OC zMVaiP*AFzF^!W~)BEjjN#BwrAX*UmX_+caCO2RjuO5>&3%%5PrXjtY&qC6U&$A0Fs zpJ-U(6B)f;;d|X)f^|D(#Of#%)c=b^Cd3Krw$G!p+#xJ-UnMjqdTtAZahnVksF+~#V>X|n- z67MxeW~nC3&y8@Ir`vma@f*r!{ZJq;U_Zhy;jqh6+6T24{1z|2*Ng1L@%Lc;^f1ot zi13^C8ZN6x{_q}X^zmIXgI)Cc{j4YR64OmMu4WmDeHc7moWoqgPq5uQ=Ff9Dy}iT~ zUEjA{%zjp|pJ+JUFU|om?Fhf_Yh=(qg-bh0595po)~9xhb$T4{yGG&^Pb1NeIUV9h z$|-991>1ka`fqT$zGwa`^Jush&qt)EKbE>*qxp}9)9r%tkaiiBvAZ#*!oP!z%u?qV znNa6K>T%D7EG55d^62@T?kDK~L{Hkg=sE01CXB+SzgC-086=V26I^IZ-fcsJ3jLX94p#N! zj~SV*s6We5Pnj@&otyi4s{bq{?Sa~5JoSA@!aXjeanM3zA6KujpH&=Qn<0D--=~sa zDHqANgh|HLCiWxcNHXBjb}s4r(%50%jP29)vea(V{uAoE4A+HAKIQuuNzBV-NLPxR zbsti0SQlXQ6Y56(-a_j_g6rA$Q2Q4D$MN@g(WCww=UlViAx6g45ge9wRD<~mMrJAL z_vN}sY1blYU0+;P-9z`2a7oWe?5`omM}Fff%G1jkry6rUsNcuwDjCAk{%Bu+mTJoJ zM0u+WT;}o8^|7y;^)Jk(_IjBK2jzYw_j@w_r~GDdz7k6EE8o>c%6dpyA9*FmBjt6S z3F8+N`MEFg-pE|y75y4K4ks!5R|%=_ZIHP8QwKFtT?q*T7X+sJG+%J|PwOZh!2 zsxMr=w^2AtHOkTDlTg#q$LfBT-Vd=3+Jv*zot!^;{~?J}znDMIGWs1|_F5iw?wS3QhNyb%c&ev6}C;J6&;4rn1m@42ft?S2c0r7f$x{u;~AK{E5Q!nvUd%Bzb zKfqGfd(Px=H2>0$#cmGAEBEhwwv+f@zUBIm=TkbD=MT;yx?bO(IIo7wlb#F0MGvREnd@=&C+kr-I6p|n z>*b4R+#q^K{cRX$%d@|9y-Yl&PGWzgFX#VY-FF_1(@5g?FgSdM(GRX?+FvK{IkJvj z`Ug5kO4iW_`+DRY99iEZ<1pcJZcLlXl&c!ZW&SeO~l1}-)6b%cP{qr>M6<3lU znFmV`2iJvDzJqnHvk#^6c$(!XmUpt8$Z{%6S?4b66i;S;KFdp44q@4yrNl4tgV!kD zPFzm1Z{P|O#{Nm>GJc$V80kxUknd~1RU>{Q>yKi2Cri2`!cdQpE(>)IgTe+<(TVv`^@iw`8nu-^H2WEJ_jj%`(GZk z4-My;^E`5EQ%*QV+sK6KV`R1(%;mSgdgKS|S=o;K#8Q65-@f7>v7hlr^`i09^#hOp ze>(sF?f9kt5;@+KcU;|TB);SFcce^Q?BuyUneFJjRas9FSNqGwpR7lX+8rqWdw=*2 zgOi{2IB)NM_y78%Cf~vH_$dz4`CxHAS1veb@gP3$F8%yQ?9XE^>6UdAGG8rH=CNg8 ze{{XbGLHWPmJaXN@5=MlojE*+kFU4oW2tz)NB*Z=5^0G>RA&mrl6>hBbbHXaKsb%NWIQeBS|-#vCSIHm%;UU$>Ct;pj%sb}%JFkGH)SvJ|GR!NrCZ*k zqW)czo`lNh{2ZuWrub<5n^5i8FXbEOgqnUUD9?XaPsZ7q(joIMnaWqLcQ)z7Z>AZE z^?EGjIVtbQB556aa2^ryxEjuOqtfiLuh8r-iK%kOQak>O!{b?Rl996BHKwL=_+h?2 zm*wfLw0&IdxPbi7I^wu`j<3JUlICf^A11wJ%%kb1{u#g2!q;Wr!bcq5&QkInn?%nW z$*1U3`;IFK%lVOqu)cifJlcfuyGG1UV@dOT=ok3C>=NcuuaXau!bJ)fDf4^te$dZ9 z{5G+X*#EDTdI%jtQ^$ZSqu)Go8$ ze=B{|ZnC&s@cz!}qjctQI?I(Q#{;#K@qOZct}NyLjh?e9?Ov|;W&JuQ@clPUw=dZ@6-YWSGw2^~F?KjlmF>rIcsz$?9q7Fr zmUE^`IQ&osS)((hgZ6R7)oj+2-!v+js@+*#V;N5 zzS(Fw?Y$g>{EPn=(&CGzXW!vyIe%S`%0t@K6h7}%&iRvil=MkGM*T>6i64?!hr#|1 zG`~^*Qs0YB{^M$;k#hb5=BxI~6YBSqDSuM0@;&4oE=SoPD*ISu{3+qjSnnH_Ke4>W z;d0|JJ%8jm6jQMh>i1=zvG|qpp>Qy7o6cpuKF%TFc*?Usx$lqSa2+GDf5}Mvx-N6N z9z4%zd@cL$aQ+QH=cIf{2J73?!^jKIBd70wkbUPd-v5W+N;GLn7sz zx=x%f;nDl|-?iV#`{TEr*q7ke4s$vPZ4M_a~S`#QIKj!7TRhcHs^GpwuUbpQSJZ1wuz z>fv194F1J#4PU4G8|#(X-`;s56<_-Gm`V?4rSv-TzSJHpZ)Pd|jYyAH}5}}=K;yMl5x#W&X=^?Ozln9#YuakdXauOrcOUeKOf@UA7^p@jB_uz z9QJTLB(Y!PKK;DNQimIvVEyb=e_q@0c{k{OrU&wrk{N9DOr+#YB0D3NyjHk{A>&y8SDg7ROAD4bEp$4(u-meG0r}4sT z6yHCUw2m(r56isZON}Z2>0!M0{4K}1#e7c8a8CaU)|YXc$msJ(zW2Y)`qDp(KanzC zlzosAEHxPeCT+ZTG z;=7;uf#zS%0gSc-SwBYU!+FL1sDF_4EwWBt=7Fd_Fs|eNHOgiGviOt!R`!9)`3}+d z4ml4({KWQ6JtMkALn!I zujhJ|=f_som+#B+9{4@;JuGu3Q~Ak#U75r39duuk>LaeC-X(r2Z~Sf~KR2cPk7EDQ zFCEL_eT_%uJxQM1GR_j-fc?n4LMAEkO8+76jWW&nYyHoo}wk z)s?K59>)Gf<`S>;4}+OcGJfJ}1cwV*%J^$Mho`WV{_I&34#p31zfgH+rOt0j=P~}h zlel`-*yDF5Ogl=X^ktkR<*<_Dm2vtz9NxfE%Jqwg-UP0H@h4&Nm&w0em-wPm>?Hmj zCf?xuWcn@^4F~5%$$Jo;{}fXvbfD+nubh5q*Rn1^`kg43?=R8sSQN%StNZExx}Wz! z%e+ig<|N~)c3K$wtvM{R9)~HPG5)=rxT?=w?o&C(PM-79U(@}G@oMB8w&?vT-(zL{ zVN}w&&2rvzRAQeYpI0RN`lC|HT_mmFklz`_x$$Pd+g@ab^-NVSr+PbvWg}A_adjS- ziDqX zU*dJ1GOnU?NP_tm8DEf$tM0}x%8RAwAL#Q|#$8wOeK3skCH=beAL92qj&C?i$*0V_ z$~>%`FDK`RN&Aw0KO(O)^Sp62kmq})f0Xtl>5J+~yrVh2_pp4Br3rC`o1Ln z58c1n)5s%X^&lMR@$$b%R&oa*LkY&71aAV0Tdo=kq{RrKXMu}Jxy zJo?>JtmiZD!5DwNM)z?&GmZ<^*$?4-%05R4%Xwn*JQOMCz!Y%)M3352Ock-eNBO$c zuZ&-#Ve%{U$H6><=gq;qVtQv2Vcoa|%#dp79zc-~Rk(X*TnS??tC z@k^P%el@it+5ax~rF>nU{}L7%<`D?HtQLPf8q+u=J-#D4GBvsL_Bil5f&V?FJIx}ON8+T(f-Z(}L#SIYH5<3Fz2 zb9_BHJ_^Sbg|pNE=4-B@{2ypM(tb$><6PmDOu5F@FUCL4cHnZR=V|agMV_xBrQOQ? zd?nirV=3QHMauh-^e578rTvkfq$B7b#lC>cLDDVy(q6~$bqU{V!a=_#_vEG2qUPT9SwzC~Z;Yn-n)()`90 zaeSxa{*#_ZL4PXtAG4k4Q8`FICha|@NI#xRm-JsEcW^prejC4&ZN7iwcm3GEoEss`4BVgV*=iB{`*;1kZv5n^clXQR{>CeM zIg0$3OZneu;=z7=j_;n6==_9j9R887|H0wxsk;5)JCBk0{R$4(Px+(v5a;%Q^Y*zs_RcY1 z#OaJ)7yfYM`riG;xE+ct8^4U(+PBy3G)v88zv=BA>)@C#GZO1gIGkSpS?b^%?I)4a z|NHCNY6ibAyvcso8d=s)db=uneIwg%<#eUDyR!P}?Xc|i^mbYHdU`u8dtK&#)7x)Z zJ(_=f%=<-qWZu((1)o1migY%lXgwC*@7weDCr-N!*X#J|XX>__68!T#s5l<$@|aJomZK85l7B@s^H zm?~jC$@dHnKgV)8%e5?J-L!nq5&5aPj&(Lh%DVaB_Z_5sB%Y6NqUY=Wa%l%Lj~1Ob zTEV{?_C2Rl-s5B(7nSmRg7iCr*#FOVvj0HVrON$TZW=xR8c+@}0d}WRq-^zFu=NRxjvhbsgU6wk|$OM0H%2ubbKl$EB_f23gob<9(?H>BM zpHSpC8|S7|I>;~P-OY8`Cl*NhJ+hc08CPesT{@TRF?Bv)m-%=(FIc{7ruM0r-~9k5NqLIDhnUNH8d_f? z>l(36i?2V=a%w+a|Clc?qWG3Dr*K@YiiFpha8^eCvALd5UuLk|*Y%zMrv1MB|KGY! z?F;9ob2(7C$@ieF)VZ+4W9pT$RQ|uQepJf!zxcYWQ>`?L?4&(Ec@EVlox6Y&ET_}` zd=N{S*AOZFDcQ%>!A37jRcC$iEA30ip345tX4#A-{q6uT=E{s{1fcZCGpCDnn&$Cnk$D7UrJG#%& zj~cy%Dvs!r-z+tiug}VG{Ykzq9x9;~WRc^-K+tJ>6kj?{bZ_;iu)#pFEpZ|Y)((55w9{URa^ZqyexgNxm>AsPE zB)uJEl^I{8-#7pB2IVwYY{|Ni)A@8$c?$7|Bhmva9u^;E_53)lz3 z;}aS;CKQb;F`vTyhK&E|Jt9lhF&y8S_&W6?dVdf-KWea)_cGz~JF*ggG%VlMKmOruloh9WT=Rz1iS*pPlT_3@FpsfFq z-{+Nd4CZ(XSkgEE`!m?jlni{1;rQKDzAoSC$sc}imH7fA<7$bK!V{{LuS>a+l={KG zA%3rw_ZcY^Xch5P~wRxY3Fo~cv(*Gm0A2g6w>GWg5GBm>cSFg-{ep9 zM8?%eoE}N9#7pD;tkk$)xU3VA_k5{m8HdTd))p=&iC5}J^uIILu`Yx4Vh_{vUi?%r zVa!t)nWbv-b&2;x4pV){71b+#kB|K|PP>lZTQOn$u03-K$CS+PWbzaB7xg3d;xGL= z);Zoo?Rk`u*{S*<9#=OSo`v5#AieQM##9GRr>s|#?=;cx?de>;3kB^_)`iJ^E#*h^ zvO#|$T z?smhaKau&^xGG^z*Av{|#8lLeq+k3^XS;bu26l2^ERMt{&yj=8{TR$QzG3uYsozbP zeqMebTjmF)oauRD^uI9IvlYD$<*4nfFXIFm*UTTGRshg5JJ&wcFjb{ zRCF&gB~da(Q7WX8xkA77?6p2;-)Eom+;hHns8{d(<2mcBz1LoQ?X}mQr<9-d8+2ct z^PBZtE3fREvvxt}3xaIDhnI^n}M>-_7!SSL@+2v3fY(mx2Cly*Kl9++4=*Z}n{o zp*QzJ4s*Gg|D*kc?gaP8o%Vipug`(_)2>qAQ;xsmd{TH`%{|r^gkv4_a;L)`n7Y%> zH9_c`{Ba+YE~Q5<+^08$KM*;=fzvzQl|z3h>(o>4bo@)ceuNy?cs>E?m$JS|Jv-6q zSckpMajSQ9pMvAI9v0ULSRZ4aKgCHm>kPJ@kc+KX=zY7sUeVjV6kEq=iLGO_#nv%~ zgmnzoDTp_Pw%lCc_rTu#9$NVO(jyvml zy00zZgSV^3CzJZQl0x2cw)F?*39u#e1=!YcjHS=&ZN@>FuQ%kK^#&W?>AQm7{y_hb zrxV=PDGCo)cKVYRUh6#%Z*+Qo&2iFi_0{xmpHlw)+8?{uCT)N8J<#wTkG@kG&H<+W zv3Sy*ja6kV) zoj;I%9}xSYe8>cA$8FzO+z(^*LtJ+s>3TfnC0(aSpRCs(@Da0TgL+@exrXDr0zq$OXTQcK{$7WrN6)3#@W=;wQixo* zH`xjP26ewe$d^nww}hPmNjG*%_!T@H`1Imee;-5dRSEeL`(-P6J{gSmM!8ze{nznu z*inja>U87_`EM}qygQtxi}Yf5;HF1i-*!949sQ;8?0R8U9&#~vU6&4c=D4;C2@XHx zBOLC?vz4a{`~Q^Vsr`S!^+P#L!z1rcT@KObP$#p5Mh8b9e9X?W^6 z_Q|4e@{jmTe)OsP3jJK{Nq!DEl_zyi%Rlr6xv!wR98Xx$>c4MXWDq{27x{qj?U$~7 zT%YW-q8@Ilr>CyI&*aZ*RrOYU=brss?E9iU;Jq8{BdvdE&qyaYkaUChqwA+1S zTnyf#FJ_>my$f5gYuP}M~`4d0(!F_(*UjWXz ze&{(b05td5UO|Wccu$J{4iJCnpTDY-Tj#q2EaZ&f(o++Cllua(!^0rUoQ+4)*acO5TeKX*^l+k`i{BUkEQ*Ncwdy+6@JfAw`H zr$Jrsay$*!ly7Oe@AUd&ZMVCW&);3osXUv9cSrQT2!DTsbIlfS9G5bG1;n56D(>w2 zXWX1Z;w#Uy@tEQI?y<{J++cP$cYviV6ktnc5A)9LK}#X=qrcqz~HjEDO_2Zz=Vh~UM}eQ~N=$@LlZ|3%_oFne6ZQVU9!B#P1rXzq5fp%}>+2 z-b>^8`F9UL<@|_xO65ncG#>6_B_6}O^9Q}t{~8n$4!Gy7rpL6rn0qEo{^+-U&l{uH-7k?5QR520W9My({mZ7ZCYKCwM=7BG02C zK7CgpK%IAXd0Mwsa3Y7jg;H z+dd8Sj5kT=lTNp0(%xD<9NRVMr}uma@-)q_)b*5!^-tnDEaMa8O6k!rz?(Lu1ER!^*E z+W)k4mz{amH2&>89Qlj?xp!GS26JJ44g8B9j`EfL#OUX62p{;a9o%!-IKV=-2(XmT z1*qq50&L5k0S-PMfj|9*FS}e@-fi-H(P1i2il=sRVBnL>r5^tL03%+HH@>xRhkB0qZTzA0dOuOK*72*&6X^W}0dI+|Q?%u6{=Q8;9nI})8b5M1$#MC; zm40=fOMr#PulBieuYBo__a`3yu>dov<9ctayCeTfVSE?YRd|;Rchq>ykmwdVhFRyjXtiEAvg2V;Dj^xqO?OIUY`j0r}u?4*jvB&k(+uFd78ovT0XEp_ygfj ze8fXLW8yB*Nq&1wFYx}(9a}d-Yp325V7Fes;_kpS z{2A_VFqg|3_&nwA2KD|gpMQYg;*XfTz~vmrpM2RexaaZ(_fPSZes`xoq=Da~?*D9q zzm3l`VP0t6Uzwau?rHqn1$tc{2vGY^9#4~d8vYCqf2GG;$93JWk30Nc@Ol6o`0+CkNFXX7ROdjsN`tlP-e@KAoJZ~l|CF6j_UB0II zkW^mE@kG}H{sv>a$~bX>N!2(j6UK+{^7sh%fhSFVAnw@7=YxMP`v$1#b^0c{XWV}c zk7wfmb^P=Pi|6F|%{~ZEzJn9*5x+M*9v`5-ml0shAES@yCEQ#go?PA*U?K1EcvJo+ zPcHX5-Cvz9b^l#Mc+*ENr#w~3spr80)bp+n!-~K#y)5IUpzoP@9OJBq?>ti_VrMIZjqD&=$qpE$lpxftpe;X z-?>hIWkOHiv57UQ+Q-`F789*uBEJyd1)5c?$iL zQt}t7@GY4wz|8$~_t$%^gS*}b=l=clySvjF)cK(S!!3G;rNf}gKVUesyIh`h`Z`{8 z+2NQMEkDKusQ0-CSjq|kwq&&c`|GnQUv3WJwLS_^=c5vSlqZ|7()|cwzATL|;;iGd zZvg#M9>7W0_|OlC_0^}n-nMl+%4g(n>Bz+1J=Awt0(~K;Uf;{EVmSIWtdrN%Q^@^& z(nWZ?r<(PT$e(@(<1FNC(z~TElRDJ#pug*jp1$PzNZsrFqa4(eFS}k-9KN~5P$RejEWub5BJnZNF%@_i=o+27?pJisU){!QWQ{Pf(8wb_1t)nGn3RG zcjB@2KAqo5=wEakI^;9hT`w^|Z~GoIspHLdXzin(dv(2lr}5IhJg0MJmEN<=`Ym<3 z*#AJ@a{`_22Mn-~>jTtxWCPUmsR8Qw)BuO%i2#S?PXTu4Pb$Zxypp4oy912$RzJl3 z^>w_Eb&6i{>-ghFv#+y*KJ-27085z|U`s}Yb=J1r6Yv-x>7{+RIPll~2fl7<>!&TT z_0zVmpAL1_PwRS&d^xxMs}LXW-^TOXlt0dU+kS$gQ(o)gn%ym2*$HR;13jR;;nApvIc{;+Q{m$d^d#OQ_`1`l{uepl>zuFc%Phs# zbzAa=_Y=}^><=zRnLbnhxr2Ky#=r3XWhvu>drKAzur2Qja7ZlPVOb^MqdM_&K7VY` zL*eVQx=-gBRnKJOGtTuJT`3E@-msrK%*E(92g~`1%+E)4-}m}<=f}Q4>|yH8HN^XI zS?7RXDkuF?#uY&5$Y<)Uqf6^Y;BPS2w^w=lyhKm^#P$Nd`-gLc`u?n+Cxq|)?#?)b z{vhGfcnOC;`znE~-=kkkPj`E5?{>#^g?f6U{Aqlw0~?I@xH9gcoPyi_xJ=k5Xa3kR zay7@d(ri{PR(6O!(s2d)Nag)q2&eCb*TTnmu?v$+-|c!!yhoKqvxzT!mJ9Z&@2!S#dQRd&lZ$oUb%KB4?=zOd z`e_qixPP%pq;bZ(K z`#SOlJ6r!{kKoU_u}sWg-z`ksjXslooFD#&1e|c#8!*O;zNvr6XW(1rw08Kd0qS~o zfSH&(^S!YiBYi+iXY3Ds(!(3f#rj*B?BebQqx-(@jy&)+7}J42>oDfq!edO16$8|Jm~N=-Q16urcuYV1ZgzKrx%|R$_(S)+%MV09 z>%Z0FV_oi_iJsvDgkOrE;(V+>rSG)_yp)>)%%rzJaXjUBY;ezIZjaCKtb;?pko&Lf(CXXF>)j~-g!xR5R|c3#3X#wF#_~cu_3>TI zXZ*+e{5E{R-x=I_>OXf)+3PeNuY%3#9vV zPY>a8xxmAN)32?^pLOP?+4sl1<;OkUz-=4{+WcB3>$zQX9|rwo`r99MfBJn*@xXW1 zSuNd+i_&x(uKVoA8=b*iXpg{YZ@|sJ5Zuv2Dv!;dW^C{l18<~44pKstt`horQ*Ds&;@_2yc8~mH}JbIx@&$%2L`pJ4vzW0~W-$8*d zU#0o|W;rtYM(UpkPV`m&DeI{u<&_i4K7c%(baqu0 zUl{ll9eVo9=AQXDO7C^7(dj&b$5+>9E|FVXL#BOe&&VQ-8#Y@8AI-`?%}fS2+12)j$`=ae7&g`48DcinY=Ny3-@ zJbFuU&Mm$1u$uQ|>t)tX>U}wZpT3jPlP~L}gU^?tpPN13e&!H&=Hm?Jo%iYw5A&9K zuhoiH{-6hk9)0tEc5ioGhxPH_wE4F>@=cjfq+`An`*_6D&wMQFI^fvvH7+mVaDO1U z>w79bKmD%&b^Wre%0a7R|G)Qm>hyYF!4$)H_y3-)$xHdtcjn#wl>l@3XMlSDTY!4* z@}jE#)O%1{mVfLIFsS?VmazJ$X`e`wJNta?KA89}Rl0xkcS{<7>Kp0<8z*GGU!V2< z-0$}weQ|#Y_tenO8w%+v1o!kl6WonX+xbN-{hvO;+LL3KGPzQm`={7H(UgAN)BW`3 zp2-bMSNwIqV~9uZqwx3$$Gt4fZ$7=O$z%JX^S52PbaKkX#`X*#JNQb_qc@WLvebbaQ^3KpGoE${JT zKc-*woC*3vKKv=S_4%i=a}I=X(AznY$Pah=1^F_TPvGx&xl%s8!|DAkVgF$%>ovzi!`XSM-sAtms0jwJ% zui<)+#R_J3X*_=n?zs%FSo!NY^#Dt;a4mV!!=-fp3ht%Tx17G+q5n_CvtN)u_xz9c zVC6>NgmfKldj-1BuCdZJ=G3;`T>d1lq)(Z7< zY=8c7yv>u)jxo;o(n?kSm7R8q_Kf!0+OJ%;59LAc6$r5O`Ta~7e;J=x4k&L;{2UyBg zo(^-*-Cg%FxI67ozkE#nU-J3mLGxvj$49%`Pe1oLy^VMEeyU(s(H?MrEYQXBW#uRn zbJzO}L;b7sg`u42`?}u0Y~pA0Cxx*7Xt>`0f1KGn^IiSqXFhDC_P~60KmO0Uem3>- z>gGOP?H6vpU8{5y@`8t_{YjzK!@1Y1MPq&XFXux&0iV}bsn_$AfBZ=&;YM9-`kgDl zTt+wGjAQ6`&Fge_Ppj_dU)9==2Lt4u)0Qk0=-RSafJ3s3hg;cUQ$KH{>;Blc-BXy$ zT7iGz@s%Dw``m%KtmkrU=J4sSRr=sOqOP9=cfz+i^4Ikd#~n8Q%Vf9ZEgwuzdFTCr zrJh&z%Kx1K7GmvuCNJD??k@+Z?}T_iIX#yQ|BnZ`blx*S?dQ7u+qwJ4-e>VoZ~boX z@$Bl5{u}4b4aRe-%v&&i>-2YgpTv!8n4Cb{hnLAwtD8Id%K3^Hy*=F1?iN1lwDa6|0EEv{9_~F38P}zJasRN#XL9Pe zKdc+ZaUbzQZ+IbgUR~c0S+mkZ?)P8my$S&@W$OT2vR#02yl&?lyY~TY?(zb$2m0H* zF9g1g)9>aGdhU-#Z(nnN=z-{o`G}L84*cAJ>wezE{k!f?ItNW3`2<8>@(p>>Z&UuK zbmRkkf$$}r*aPWHJKEd`6$9>MY+x<(KfcK;O{?Qh% z>4p7pnSAIU=Dzq*R!b`2=S9bZY7u>tU;ocOk9$zVEhV-}O;s7(h7dOgP z=hJffb{9SIS~{BfLN_SiRNl=)x?A!&*YlM1)e@s`chMsU@;32>E|oXsyGJ6=oFP98 znR2)xS>5y9>dj1!2z+wCr%LzbJ74Nm@Qc50_CkGT?M@*UFZQDEQUv~OSs}n7StY>X zPP~L44(>6$(YKtw-Jz#km^_6nxrNC=xu(2=Q$BGAZz``J_xG5rysA7tUyOV2a@*?A zo#*%8GtJihh+~9BS&Gz1#Pjfkf9=PJs~OZ#Oo@06c9UdSI^pSWAOi{-7i zd)Wzx9)Zxm8u-Eo`j(ex?&r%o_w}_dtJ;aYv#!QET)OUjOt9a)(+*G$Z6BS!^Aq^! z{*d#mUof)zAfBIZ;%DhA#QL43SUjXA_)AN(= zzJ|k%0@Qow9RGqt`eXHZXZitp|DV%ca*L{4^_}8-yFd3a;6GnDpQr2d?r-Pi;`tid z?WXahonMdZ{Jn8~H;#Lwech7%1nD{y{Vd=?E*E?a#(T5Ue#)Uvf2>2ZxA`tn_IB6(UkUvOyXx1H!bDHE-H)3I_%B?K ze{}d4r?Yti-H)EQzw9{f{nC@B134M5Fus8gkbCu;@{xRIzXj)qQa#RSocQbWSaBYJ zdYEwx=`pb8K$?41>AM=eXVP9N)o8)7MYm=y%ZZ+@N%f`v#>Sln%b6 z6Fo71b6J>=%_N1;oe=bv%P7}t9oKi=U9Y$w7I59~7NG6}c7N{knAiE3f7bE0aJfKd z@#TK6ZzlL#JeiN%bsjOC1LZt*DZ75m@|}FbuGxQM?OB`;WPhj48^-!|nILB_?{<0D za(wdy-`?@P9diF~s?Vc+UnJ!YJ5Jjb_VrPJ1J8N4^-p-m3wPvBan5}n>T)09^40M| zj&b)ie2P~>OZ|VY-`gm;j_yvT^4_AcT*g_AlF2fmwgzxBhQ~) zp1%gD_d@%Ag2!E6>RrN-{^1}`DYLp?&O2f>p6))UwF9E z^S9;nnIPww0plGT=-SSQc=h~IxZgDQ^cK#y6iYYz>e_OUudgf>_~f#x$Gg7cpLX2p z$+!+lzTM$^G=1y%Bj7DDy|+EyA-A*PPCCu*^xcDCuhBlHj^8NR!{wyjJ0!u`H;X^_ z3xW?yZ~YBj*LD8j@CS!K?Gg2g%}?w8`}djs_jcF)@Siq+>KmKq%;lI6ukJse#p)yU zcls_ye`4^Dbi7wKDBb7Rw(%V0%EFZ$JGT0Ve2eoj+$(XK%SSmq&-HYf!<4_R|9970 z#AE)2+ewt;8s~SDL)wAc1D*@*0Qqb2#(FJv-)`k9-DRgdr2dvZy+j?$~SO&>jS>Plz;DfB<63b z$9g{Ww-3TU8SEq%_KWnMe=7&}Kf(1$eFpZ9BWPxVnxpPo0|&-w?{v((q@ zGXwVHWB%w>&nYyN8~DBI{@{c0ryR%mQ|s^RUX&}U@kZ(Oa?9)cNT14=rknV!z0GBZ zZLR-hFz>YE=*PytxzHaYz4p#K@4Yw0AJ2uPa@6rchCE&K1Q_+Y$adDA^4>N3U>TP$ zwui^x`a79S8Rqga54VlS3%?zEx@&(U zq1)T(&_8wqB>(F9+}%Gg``3L9&i`=dYvWD5$26on_UEVUp85M3-4MM~u8=c@DP6yG z*!d{VNoAe)*HXT9Jw&_tx~JR56|sMn%0ar>uV?%I*bi9968;YLNZqHLfBox^86{U! zI*@m{?X5m&${*^1RF4OH|Nl7GJM}O0_3>RMGdds9-^lEI@N!X~4=;RwXzBYwm{- zyZd8CKl1+4^p+>pN6H_6#uGrs6)Alk?;bDE4%F$Q9^l_Zhd<{!mk;C3LTsE-%3s#1 z#*cAcvB{rw)#cFjR_}i|xf4HdP(AeHo5q)pvr>QKr~7<^ye(loX8poApW4(vWS*eT zSLaK;UwEnO!RBvc{xI(Uf%^~Ik9_T`Ha@&LgeyAZxu1G`4|#v~QOBQg{>?aoX}_(8F80^nQw< zpG+3>aK?}Q72Ny7IGuSQ=1U@^+yK!}n$Jz+G3uo{9K63h<$AE24|3YLgnd%Y^Ay-E z@w~(J*ble$_3rs`%5kb6^tOcO|Iz^#@~!~&-v0nw^6miJa_ToKI~kI11*rG`2KShb z_c@-%!@02y-2bBi>b)@m>bXz9Z?9iDMqbvb?7o%E$2+`p8Sn4l9x={~+5V^Wo*M42 zAsqYuy76>gcn41h<4fkPo92;s+-dUhEaUE``I185SGVKnPCN$n9jwn-yHLjq+12%) zy5AV&(S0!PPx@E~P3t}68L9j2zF*)VkLM8A+hGCfeWn3&&Xf8elQez1?ri!a9eX;C z+|-Myz9>(rJL$3aW^{k4^J}7Oa!=z=?c%VoZdG>Hy(p*0x<2alP+#&MpCH~ppXO7OUE)rEpdTOXIOUIfYM&`x zYA@*PTfr`LKYoDvZb`TghjoBV+DBNw;WYQ3CE%r;=l0akpZ1J-sTBXN$A6{s|9*hF z-zPvle-L0M|0z1sXY>4-Fwbjxi1##G`gFgxr{i{)8~1w>ck+vUiT(1cpFjMX>c`&s zAI~@Zx8{rOi^<)7y6aua0r~W1$-j~2H+F_SPi=Yr*!yi=^A~Q<54xR?)csc>TrR(M zKHL}gguDMfaevy~t$!WM9sODUvS?S>n>B~lgX6*yW{DduBflHUi+1|$JqO#U#|2M`C31|JN+Y#r(b(b z`<~X@xFeszT;Ae(w|SoK{T@wx-WtN?@+n_eA89(EfAuiSckXpTzCXA=&5jCR|L@jc z-Oe&u$mOcfH)LY+Hr2!3^dYsumbalKb9^8qqzrHCqW^b|FSiVeq_*8!K zWzhD#ZXdCqlIov$D7WOVg>TN^x4C^)joBv7-a`uySYn_Y1%0L+^d;>8|Ih zl6`8+2EK)?7GNnK@_4okFqVJo|CXJ7!gW0`j*kAze#hjZ9QLMf%Fpily2&oAo{M@y zKjbI#X!UXv=LtUJ>HWN?cW1|K-Y}Cr-Th#9Px+_mO6y6=cR#v*{+*GX^e}jN>n(3Cl<$$IH?{jTT~^8r>ooU%znH33D{k4KC#^N)^B$_ zBh^c)=M<;^IeQpK#(IkJ)EIZ4DSy=-7v4`B+%8jnr}&`yPv<43cD|5(I)lO^ygp0k zr@o%J@8@;`q&_=;FY6DC)PDNSpCK0z{^%v04@~D}`q8n!^~8qyyoo>KCabTy+pVT> z$WMEe(xvm6wDV`Ud_c1=>i0NbMEPFyTQ+}mZU|Qh?H=JS3GRBozx!Lh>v<8sFVXz9 z{}Axf>H7OK4j_GLeV*2TloQ&Q6sLV-{CQev=h$Bq+rOFqT5V~)_1~r+>X&|Y)X#r< z@kz^Xzj&=*mUs4-ru1n$KT`Tsp0r<^@<$K-==%9z<@He-Ke)wH27<@G)4)lJ{LFVj?Rn%vt#uX;du;3X17v>Bu)gEw;p_hT zo~^qRo_Gg?dS0iGT=jHj^6n(Qx;!J7mwaVhvPR&q@9g;e+d2WK{{er>i;lO#y%)F- zt|#Ip{Pao(=L0Ec8wUBi*BLgQ&U$2APWuI00z{QSS} zuMGF$wEUitwjlqs(!A6{pr|8_LxQp|Fo_se zyzaG|SU&9U`Z(O-Mt(!Ut!_2f8gt6^goF2n;zfk|EBnMZrl9ewgKw> zXj@vkXH@zw^7LIjgVUGVQIlPuf9~r7vOZ$_+1dXV*HtFE{MHZ3h5iQXPwYGV(h1hj z0Kd<})$u~0n_4@D-*23M+V4)wLpqM;UO(f*eoB3ZC6WKxz`u|*9d-Az6K>kl6XhU2 z#;?}TV4WfM&&cO=d_aH3(oH`H{=i&5{59);Os(9kTN1u$9o*!M>!g;RbUm1LmbeaX zbn#p;_ZX}c`nz#`6g{N*jvng$H15^u*5jbzKXP-`4shN+z9Z7lp2%11*YN389%6c| zJ=T47!{iHCC=gXT{fFMQJR zdMZEr)v=qjy`9hD^ ze&dcljJ`YFX}KYu)b56^wtlVgo%Vb-dm6d@G2Te+n|t!?Tp;IQfOFcL&(r0wh&d|5jo8A3&^jz-;#C)YaE~`#6_#WIA6E#xuC5IP>zlul!4v z-^{zs8Skkqx#c`)S;NHR}clP-i?2f-TpSXVpfA(?p#z{}!*&mh4 zQxD%nALkK>hxLnkI-BGxWOUGTDf4-GgOA1Ey|1q>7ksCdPUlUNd>-#|r1_ZQP4z}v z51IYP_OaeB#eQH~zUMj0#?9jc>|VdOdlB{CPq$kjb`{GR^wghC<&5&o zJv-Pd{hrjH^1Z0*)ylcPgX!y7}zOT zIG<>EoHyrwYhXHG1Rw6*Uv_=6o;T6^nRPtAGv3?1l>4U`uJrzEt1sQ%flo_b^7rtX;X^G)-yo?qScwjLVqRkroHc(3x5{abRZ zuPdWZ)^pSRuDh3=b=7|QO#SP6>P}CppE_Q2+Bf0***W%^+NI%oZrSrQjjvvwyY<}99#eV2Ngptk9~^%m=|F#+ zlj!YUj=0_;o-|$H_}BL>=z5y(V_3}h6`)7kXOPP`g1zgz(UYortMvC)GQr>6i!OJ2 zp9s24&h>cg{gpz#>+$t=FXgZ9Z}ouQx9R(X=Js=lxKmH0wQAOy_7W% zuHtwQQuwh_8osqPn>th-SQRpxIgMl=W{{AhjWN&Kc;to zDW2z{A5^C+JLi4y=e(=+_j9?^_xoR*Sw34j3SZYNJ>O!vr(B*B==Gkg-&;Om@8+J1 z@hjx|Kv(+ueaqhoY0Ip~SMd$W>;VqTTmi;-sCVpM8hz)url(AZb)6F@t zk~5ck0$#|k+?{a`@0hbr!g~tTm*|guuTO{YxxDOrI_vT7KHK+geR<&kGg&&oZhG8P z`g*yfKMl;~KtE5+e1`GKI_)jt)BboGj{PXhxtuFI>=$k|rz0HaC~W+d%LjvhA(pPp z@l3eqYIFDhT!5wQ8DL8e4Y1uA@0=2F)~^aVE8wIvzB^_26vTLt!`cV-^_0FZBa>^K z&yCKX`=9P~-1cu5a&N$OzsDkG=ghO(zJ_=&6Y({rn|Tbzx0e1me)@&SOZk1w<9#B) zth3(6e6{6UE>8vjLbmsP6_#(gyyW4aGd&ls=hF4u>gc&ie$!JfZ#X|oUmWk($KAN= zyY$!CJn;pGSiRCOeoKd*#}D!7JK`R`NiO6tyUAsKr#C#4mrk;H-{$U=2Vg(@MvrNE zSvK&?<-N|AdVqNd>&L};9q8~Uev>cjysv{h@Z>X1e`|Vt;OyJP4%7DfLr!mc(0AYi zUUteM{DJ79iCd56z-o_;H@dVa_4V}EzY?yNqI^+4VJnb(N&VJ~U8l%K_0cH$-9 z)9_7n$GM)Uhb+EWE=Ui2O%M7$o~Q3DhpBstqh}!cZn7`x#|s00%4=K?qCVZ_44b#C z*Ke6H{|kTW0qo*h=L;X?0wUkk+Jng*>v?N8WB%LxYJ9(EYWFMIezB;~A;kBc;c_tUPt=$?Ef1kF(A} z{lz@;^vcJP%1=Jg-v(z~PCk=9{QJ>!ZixEH!WS|ruiBAP9&XEH&5 zgX;PAFfP}7bo{;=8>dIS-#vk;yuH&g1{z2tV@9iYN2eoJPbg<8JS-f!_jBym@)AG4{-D%45 zcJHHk+3OA9VHa7ybe3_IJr^?f4QAI(?&Y=C4;&j{F1NQTyzq9a6xq0!zm6-WGMveg z$=f{qiVjzExQW9j9;nih$&QX6>hMH|X9uX~Bpf&TT+ChH9}arR8C%(!ZGdV;2WMXznLE+{o!{97DUB$wz~K zE~_NrJN(_9ct*+}KBRkd=XZ|h+cggFb@&!@*^7e0iqk5fyZ4`*Kv^W%Dt`ZHRsXuU$Y z1nUyis;GfEY`&g%YXMnj__(GNqcjORFP+>^_ep1x0c{#^T<$Hb>1Yq z=j%Sl-&>V4U4L{syDuaci;wpzb9plHw{ZBUP}enr{J8)7@t0lR)(us=lFOI?Ga2VN z^(%N&J-MjUGk;XayX)hn0)1QF=keM6yPg;K{kyd9K-0VSPs4bkkPn4$rTmYtOK345L-dRt86NYmF~5A> zoq05axmdmwoqRdj;{~1@a7}-JMaO>aUI-oMxjn6Lp0$U_-z3L`6^#x^c_SZ>8nE0k z9;00D;^Q)K#;x4*gFE9OaP(NmbzR!^PrdcOVA`C(7gv(W#}{r~2${R87mz525Y4F`^P{{;igWjV)FzAL-`qc2*%r~ceS zZ~Fl<`Q6?|2V~zRaOY1}{<-WCU?z3EkmHxO@aKAaVC``(;I!}Sy1e*<AHeQ-h_4gHRpBDXL z?dSSDEBAWPZj6 zcn_nSUm9-jAV=g|pVx|Zo}M3~|2tCm!TOPz^rN$WYV4n; z(n~)Zd!W5&;+w9AEEwX`{>{tQ?o_67&qC5Iwe=B-pzbtxXeIs2@!M$nS1b^oJ z!08{de!H%x8~+UgUiiM7Qt)RSLU?fWZ|h6wJ+3p64?yTxhaw+^i%jc zUG&FJNEh~!;<(q>`wD-zNb6}|$3Sn~Z;ZXA_6L7+kM^6=p|6|6xG~bWC?{__>^%3bEv|dW=8$8G!^ z*T3km4Mx|NCqw*0@>iGR?+!WlkNweKvHpGG{d?`d`TSTK-?XmFQ9gl#_Insdrgm@R zVXfc8c(LW}ecS7YAuo5sopMKcpdZk$+~ME4$=Yq|FZM~K{L=D8I^jcj+NtT?9#U@M z-_#D0?{kE7#rh2QMctkCAsZJ>{dnpHh<=$jWxjNE=f7eY@9BEC z?;|mL>8_X4d4<%jZ2qAXqic2MA5uE_Z0hk-uCOocgZxMS6O(=?{!R5U^DCeAd}03| z^(W=SaJ^6Hat{XqJx{Kol^ z8RgFWKk=tH@#5bEZ9XCHb4qde44N*+L%=k=m6Mpy;6@kA0rhHqpQ-kjy?&_s$9b)^ z9b?}l^z=Wdr@^VG4bSDS5KnC14Uc+)ZtuU^JQwxvq_AJ75Zlih@AYT>)ckYzFZ`Tm zyhk7Zhdut^1Q`9(c?tGq6R(~BiQ~r8{M^CQt~caFo~<9R#uwbzpz}WAKI^!j=VcFP z5W47Z=`3`;#@bEh1=zo0c;4C1xv8(y8SHi^y!C7H&b*WHXMC8+D!v~V$T|)4hf@n# ze`0^}Cr+r+rSna`|BHGDJyNcQe`ItE1ep1IH@PrwVE?`0>;uR;{^m~mPIzE{J_~ug z2BGgCPd(m3;KRPly4a2%B z$9ygGyCc8c6tn&^S^~YAh zpLs)_9}jpbc5g@~{rr)K@dDwH2S|KI7smy5zlPdvxMxG}GxBtSn;!IigP!g&TuO&r zK+d11@B;5&11HzF&c?{z9xrm+{mJqERn{NOUDt`6ANnx7kON(>2Rpys;dEbw`&)kL zI*{Wh2dM8kIKIT)m7S4q@%~QJfA@Wv$T1Y&L5}Yhnw*8_cf6O&=-P6&$A?~^H-4oU zU8_S!x{;$!7ti^gc7)CIVfWa((WmL5UW?GuAMF`^QI8}kA0@_Dffqe>W_O9v~s8SLq2Qu(Oqw~ z_8RcMR`P>w8k6{I&fZ%FM&X_&C38>m0hy7s5w~JlvzPjQit`-4UMk0^?K2@=nh^ zW2`rrT!qV7`g>CD7C9{oP=L^gB79P|EgB&wAOE=`&%!9(KJ`2+w;vnVkQ$jgxHOXf7tV-oq5+ zFXaTU|45Iy>-%!sILbgBHM+gGboUOA6;famie0cyE*oc0Kq%Jqov%T4vn zI~J*XlU&qKP5xFcqTIwwJ=_#-qg*veamfH9;cr@VL!m>WiP$${eQpQ*vHhiEA@Quw!4&XSA1S>ai?9PT?0@3 zoABvv=N1qBlIC^@drIs5dVBQdvupI6<|FyjuisJE-%R_MX&=4XC(74M`#$Yjx;s{Yvk7U9bvQ$TeXeyzKbHhxgqA>Py6j%o>l{9)Z+TK*r@waAtqISG^JKwXWA_zo|Po`38=C_rs@my+M3H>;c$h54itdv5VgM zGyU?vuK(`w{h;~YTh7VsF>S|cIO%!wjHgI1?E-qFeD$jrv2*wk9z5mS7`QAM*YxO{rJA9F|8@Km*yVp0F z54L?2xvrC0f8O+#`MwW*H@2|;@4o^pB`>XABwUj}e8>m%MSmk*e?PpXd4Uo-91&LJ^>3YD7;#S**ExzAVSKAvYA9q#jPiM1>3u640N>HpiuQC)iVc6MfY zt@_d3=W1t`*Gez_=OygErg-i=#s6`gVW7{2djM=cDLoIK`8n3qz3kklz&U!(hr+L4{3G>0Ka{glkpGH+=W>03h5Xq4 ze-dCz?hmjnj|Z4Zs=qp3$UaL{`i*pd2;uY|n6SQ{$zKDW%l((wdi#sc*UqQMd_e!b z-Aj4sy;b;D=Y9m}*pF)W5tNeh>FwWge(esw>D6A5kL(w~j#Bxi?B6XP^u6~VwA`NC zo%D~4e#rGP+|kFhhM(U0B)_d4$poDI#hv``4ZGJRjIPN&w)@r}=*AgOQZB8&DEyw0 zOdk7$jT0UZknqj0i9U5t_sR9kFZ2T)cA2`T`(n&)^NwBh)+_sbn)KM@jvnZ*cjN3k zNz*%{+ehvlNYl%`0qCs>*7a7%KSI4%N-@dm%cgVmGdf+ypZtoDdW-f5Ie2#gcZ0c1 z4DF!a^Y7;jY(EqC>tnteOxw*&?Eaxl ze!02LN7y@F@&2aq9^UdJ7y8|HKU^utCi~U!ug@ED&s@7hZ}S~`zfIs@bmUIsvHGDD z#sd~_SfYj$D`b-KGXKO_xL97^ovI7|5-23={dfG9@DQUb%hVe@--|}%x>TmqHe=FXnMthjjj})gJOw$EUJZ5K6f7bryQokoo z??Vsww?#U_n_N*(#A`5@JzZbOZ+r?lJh+!)i zTD+HUl#d&ckNf_(rwx7KpYjD~Uo-kh@h6tD_UKOzuM9AklU+a9IdtIAgYzz?rN5A~ zJp9E0a&K!PFTP^^8FSBM%Ko`r?R==`c%RhDEB&EdZVGgozckWL_t_MBbuLH~l{5i)h{z6`MI>PmaoM+K@pKATO zXrC62{l&Ng)A2~@^S6Whw{NcOB;Lnp^>z%8eU1urrJtW}$?O4-^{BOn&GMxFv=4JR zpC*6POG~Cqm&ugFcl*;GlioDEwU^v)+VcKz+x0ut$*(lre>z?L(qsOuH<|q8UkWKd zxJUa-^WkG^gZ<}X{l{)O2#1}da=|}!N3PWFai6lhsT?UD z*L~P0fPBc)X#b)88j`mr<<{!u!s`d-FI#?}a{qGEuRP+Wo=tu8v^t@9z?$PYT? ztMhBlSLA4tugM*`Q-9uYMSAM`ih9By`RjTbIUVxV<3qm44?5CO zkGDC0rZrr@dc*u%oqEFJ%Vk`sH)4MVcay(xfAY7vouYi3-kbGdbZ?&a`@c0^{qo8D zoAcZJo7-pnt^9S@-`IPT9QE+2Jw=@G*sbMPw>>wxr{S^tdi*U}#M{@-z2shQ?0)i6 zmUMsQNcpBX@nBy~?I`?=F7|KgIPSpM|7i-}JAT6VgRHBlo_+jGJiYm9JsIfQUG+~V zU#5~z{k@68=jZ#66OfnlvhND{r}bQrqr0B$$l0*(>p$(^ln>@!HtRj4hy0H7c$S{H z?qK#C+hO!qPj}?gWbfvl?wcc=?$=J_Y2x4Ho`%OB>+w!|d4})E^c2&H{url#8$alp z@&o@A&McSK-k_Js^zk1mw{P}xnU<%fa@XXZhNs+2@AB9b&(xJq>;^kZ;mr2c^xE8y zr#^eLw6AaWa%%b>y#1scj;wri_p3+Br>UGy@A_b*a+w}xbUCM9dGq(1(|k1jb=wE- zHoobeN1&d7u5Jh9$7Fh&LGAdPz5EZFA5&K@y7P6Ud{X%cU(fGp@0T~lWA-)ZcqX+w z@D$GM2bf;__XE;=sh4;38q4|2`l8w|^Y0&T_s%Esryq>@m)56Uc+YWuKmPd7_;|K{ z$@WhGcVEc%FM$J}T+a4&Tyl$Mm+m|L%^enw{TeSnZ#eE_UaWAPcMnka5!P^h2e`p~ zh2UN``Y-POM%U`l&Gv%D|L!oqmCIAxSLrC^JrA4v`tE+l#T8z-e<{WC@fArqlzd7Fs zNBrm)I|iodz@GQM#PV~a05jP*?3>Jl@-XJmx;|!K#PmkKu&;>n0N;6*HM-fuJ@I;u z=vu>(=XlSb+g~iag0cj{iBX*Yg9+gm@;0xrVuQ z7B*XJL|yd zg|DT%zy8b)GclNpg^#$|gTAv7@}>H}TZ?L%k&xxjf(_CjV@IQZcl=An&!oo-?6dNY76} zZ!n(oF*!3?v?gaJe|COH&t~;7^#hRlkoS^rT%e-Mh5FyhY3BBliRCje7prG8IV9*c zlPyB|&*f7A7GnLaOfK#0S94h+q_>cVoIiR?!~HP$ zm!ZC@&cRmoRWl=T|_sr}4-0AdvZKp$S z3)k&#^<=j@@erPV#+30UKHct3@uqTvr*?u~uqSIzGH+KReEkenuiDp~8&~#U$j4nT zzn{y>!>1gle0;<03cY?l_~)JSk>Zr2pM~?zxu46;1pe^l9OAPVS-CW*=h8j?I5lF(0;bOe8u*}`bop`KhB?alJzxwH$_R#9h>Bnzs+wG=wBj=#)_Mqv8pXqb( z{zWRM^#k->F7F34#b2juclrg;TfL;`L_$Bi^!ljf{oJS{Vmg9Y*!giToB4T;jG+r*U#1XuL@`3hdbfPKhsAcHcwE> zD_2$Wwd7v`wtapn#%t|V?)HfNX422y^0}KHchgtdX+P`yQvMOgexR@OOX&*NYbo;u zy=U?VA4eM0e%t%4-A&tN+Ev>7w|TxT>wMnpct895fYYtx;ZpotA^!hd|6oIxi~a#P z{g+R;zrkF#bDaJy{byjmal!a&s{U0jyE`AVNBXaMXP(0P4-vQVJN=V5F4@=l9Ji#6 zE0BwRu#E@e_-^0OkBfZkc+r_(B|TGyaooc^l(hqT-rd`aPkXd(CFr%$Nj(R0d6nO>-ufXp|fcFnk_4)q-*KUYcn%KV_U z$J)NTe(-0$koI~|ck1Po-rQ;5GGSi$YS%mS5xw2@9F@;w7+o$l->COhO|bm9;%f%2 zJ&5zbHV+Z!ElqE^-01P5hg6UC`at*h)ar|AuOFxnZgah#f47wfptX=Uk4ac$?gxTgK$2{DB+H<)dqx{k-b@rxvo#lKJ+S7sF#svS&`^Vk&OG@7_KkD-& z_>M^JS~h{`aS2owy%A*6$W~{-3?c>}vE+ zO`ex7Hheu_uUl?9rDN|YrdRKM`k?6#NPmTTb*XjDj^7&~^-S*dK_=(kW%92R-s#I^ zm5{z%43G83JHq&+JN!#WS^o5cFaE9+&IQ-YK`H z|2QvBK5XmhFqq4i9H+d}KX})9W>?@Qe<8S|SA)5i1MQy&eMg+~oz^4!hIn(i?IUI< zhX%Zq6I`AN4xzg|;DwlbDc8Ech0onx*U8s6xzqUi;XiVIm#u1ceX#Sn#oh0ANV}Wz zd&TMBd8x$@+;oEB@Lh5P!{6s{6NlS7B;A${y?4{&CBENoWZ{wH$G#6^mLHp)ujKI= z%scbWn>>0xWt9M1UY_(`pawdNCzB5Z`droy>+3rI63SuO+5cpA zqW5aK9!!t1{NcWy^Z96inSAq@Dm}WM7VtvMj>}GY!{6d7WY<7f3VJj=cU;#ooUh55 zNgCe~!5{n6cU^3Ae~QOraz%be`nZ>V%V{2;=>z%op5U5$Cg(eT zh4a0^@tbP6o|koemC;tegRhz3P5!qf;qP>u@b@PE4H-o)9#<*MwiR0 z4gSULmhVFW7BWwOrOX?kw%48>fx9bY!UTtGQo# zzj}a$tm}N5+~K#O(;*-H({P(6baxzCm+O-a{!M&{A3g?S`acoo12SQJ0km+@ee@4Z z4(yP8v;4|rr1& zOM!1G-+oHj>UiiEW2$na`*DN&kgy;0IFBE=+-X*ypE7 z&vE{B_srW5>P@ZxTwbepbsv2wKW&-l;hW;Kcyq5$Gq0z09x&9?y58@6pA4|&^<`UL zcD{sX9=p85@_}}Vc&*-z^}n?XE$81B<3Hr>!m!L9_>AhrW8tG*7QU1Z%~QqKlKBFC zl&h{Uy$2`gL-(}=sOuVmKJ#`Vwga?J=Z`<>w|v$4|KQ(}bzN>t zM|Zxae6S1Thd=JU?WO40L7l!kzWco&SEnnaw|jT^Ekk-r*(pgs@{Ke-*sp~n|GVRD z;%jo~zNkbV@?&3@*W`=k#`1B<%T*@d2z0sk6X@^9{>`_8f3$Dg-`(o;N6cPxIjbjK zcX}*-o$m|%y4W9LJVO5tf6`<7mP_wWezBa>4dw{d16xPP<3opm1iPc|RJzT{l)biNOII>Dju>bLuN zg?7VW-1pr?KT`K5{(o?Jk*}VftTP|`oYO(ics!+Neu{ac!rLcCva4F7yw|U*aKEIbqlRNiO+PJp@quqHOFRD*C;j-N zA7GQ7oBWNx?kh~r1@zlL-bCLo-`2gc%9o;(PwgwLUtuujUladSAF2LG=Sr@}be#sg z?qA4}VZSE(L3KPB=8ahA>GmhQ^$!YRo{(^Ox}`#j%MoP4tRcHNO3 z>^|-nvH9$l&*$oTyq72R(GPzAXd6!%pQ6JDIa7D&nWtOZJ9d+n<7Kb0`ZRT?z5!xq;IV80=*LNX2+|GWE>1-c28-E?AhVy8JjQ9L$a_79&PeXi#!0%Tc{~InZ z>m95srqISMoHvZn>RbBNI!;=zvX@f6xt-<9l>J+BW}s`!#ZJ$B?|i-F_Xz z@A7o7(kK0|yB^bW$3Eq~ziV=mKGvC1h#jXe<@?m3`bG&Gf?U-Izk!Uo*Rb@3gvGc-?vFd`Um) z87cmPr+cK~ded_+zs~3Sz7Y1efG@wc)!Tnr%i0n8YnGl|uJn04E62JXz+1`Ncd^zM?B~Yyh$$71x)F1XFUfTXzr}{0Au>_C*KG+<#@XF-7g&ZlICj~&tlt~ zoveGNwdXI_%6+U~csGcCRJ~q`^<~{Z7xs5j-s^nI#Gm+pBMqO*k@6p$ALVE;xpm)7 zD2Ig{ysh;k2}k-3=CbKYmA(skJ?Oaxco(~-s6XE{r;!u z(tKba{7Bu~!5-PSn2F&L(hixO=JN74tR6{m+F{!dn)^IRe6J+!S28bT>t(s@wXBsV z_Bl|WF;8*Q5>{`4|1Rt&F9i2AT$3Eg*(49+_d3jlcks+T7wb=DQpXE1J1+gbkLXYS zKyUmCzb7D;=i_%U{nB5fT(EzgeYnu|ODFTGb9lWoD4f^rf_cU?T-py>#QCGw?Z0E` zIMVk;0&#!mk>*c6rx3gQobPL3eXUNRK^6t+2J7*{TD(1}xb>BOz zqhb&G4p7(!5%Yi2k(FHWohWeY7shvkY@Jo_TXFhJ_cHyw-}7Ou01Mf`N`{cmU3x;E%}hYKV|nx<+4)HV=X-iC4zQHV0&K}O0k-A(0EasFOj|kR-5Pz5W<4uc z#y^vh@-=?L9scmCLp|r^;|bDnjh9RCRR8tx*mW*9hx8Oa4l8|Ks3oSiwpf38$j4X1 zV(;sXk~>4VS>zpiR(3IKXM96B8RhvkTY#n0$M-I-4DC@acYFL6PWN90T;mT==Y;|s za{UiGpHUtCH0js!E5`S;z$cS`1$lFsHPjb{yfr|52P?pqEbjG)mFKulMtxhaM+(`> z%f-`SoUP|Qy}Uo~d<=4LP$qRe-XlbPZS8IBufxyQNpv6XVO9=1^x-^7cmIKQ`xTeN zpw36`k%ag1k+z$S@iovBAM>E}^N0`J>Z9&`_|q%h#7Frs7}HHTvGSq&dEG9M%V4Aj zr~a(tG5@I7@Ta~^KBB+Cy21YAtM-WbUG^Poed}^A9QG0AV)N65tQhc8J{Vx6 zv;FtEtT4{{iC6i$3y}3$+I!%lJ}*yvDW9frzj65%n55$CJO}%`Qye<{fzNt)=3#;V zba&PdIBy5!9D((7GpX+rj{1Wi`*PT~O#jPZ^ZGpV2Fx#@FUsMdko|5z^5cLo{*C+J za0gQV5PwsA$VLB=bb|K_-^6!Ncj9NykzVRC>ItCLXYrm*I}em~?o+aTCUL*OR(|ez zbXcFvWkHYM(#1G5lec@mbIuAq6ECpIu2TE^^DI_xlMd1aA0YC7)Yn0mbUCnl)>Wxr zo7~x7)0BSXAYR}bp?%54?p@SuUCXx#~5X(_;TXTnFx#j;8cA zx%W%=vO%BmJQMj&el+Q&$sKbvP!mzew*rdpgflyq~BNcJ5I*S>9SjXhJI)2cb0zV%8~g6 za!P)oeizAE`kj(rtluU2{}P3lD!f$TcjfB*a#>Wakj3PC^27Xl`u|GxyHfqG((f8M zKEGDK>-4)`zZ>-Xfqp;K??(M@((gz5P1J9aemCoPi+;E2_hbET)9-fu?$GZ}{qEB5 zC;Hv3-%s_sN57xxcdvf;>GyN}excv}`u$SB2lRVTzlZdDSieW~`;~r=>i3v_zt-=nmUcW!+_ecGn((h^gp3(15`YoMZS}vpCviiMCzvcB?LBEys zTP=I8{D6MzWh=;f*&4Eb_8$G#*Kb4FH2Z*lYwEX#d^B4}zxDOoP&U&X+Dz#;Q}@l4 zesgu-T-`sGU8>*u@`-FC*;;dMNBzH3wzce@Z6*6>Pqx0I-@*DFo{erFpKT{6==V+i zPRu?lXJ-eu&(Uu}c1r&JY%jScyT5&n!fTc9b=jls>l9wE-w(2<+CR*m)$ghHP1zFi zqwLl8j}%T+e3E{*Xqelxu|s!e3lH6u?JIYw_1vX${xB=g8NP zdGb4k=23iX{NjC%Aq}&T!nfzg3@@SIiu!#*zfbG;IsJCjZzuhB*KZH~ zzN+65`h8u$WA!^<`JS)gFH*i2DZE7aUZU`3^}AEQ-{|+Gey{0Q6vqtj9KLbGyDI#O zeuwDyRsFuL-)Z`trQiAbT~@3i-z_$j%k}@S^?SLve~wr5dsV-G>i3#{uj@B!dH)=< z>G$sP-Z|GUXPavi{WjHav+~1peZ1UEzb)q4Lg5w)w^X>L!q1jpo$HHAzhn92oIB~a zYkA#VU(#<+{q`=GkbTNW=h{c%0g4}~{zsSZocmb)zMhVRz7 z?^2q3_5ZyJ?^Af6!WYUr=6O-Sm-Kt3d}y9m_4~Vi|16)H=XHf|==U%EverZMl&#rD z4=EhgdTO3oT6fGdtNx!&-QJ?#?D~IBg>$L<-1>hW{YL9IMrq%w?(^#ZvHH!Y-~8&f zfWmP~yP$px>9?@ri|Y4w{obM9V)`wu-!d9@Ifcu&cAEP=t@}r>qTl=UTTQGxIrj%e*P=E&AQV~%RA zJ?87JgU1}L|Bq=MFy`3SSH~RRdjDHb(Cs0;D(C<9`uF&uM z`d#1JcJ3P#{!rnK`rV}8j})J%->v%nSijr!yIsG#^t(sD`}F&HYvVD$XnlCh{R)4n z@WIv=^FE~CBdu-ceN?~4^m|W>>TPoXZ!v7jUGF9d+o9FsQc*lhsTaq zIJW)Cv5U1&8oP9R;@r!$uNb?G!uPfxAG@mlUseBquzl;;_1nYqZK!bP_7(Gexqbh9 z`?WV4bD)MfNWZUWdC{rdes{ni{hYusA;tur)w!F7km>Nk49^@hgjH&(yV3vMv< z_60wraOMA_w>7dXbpK`=&5;H!)r);ctaUdI6Qoyerw36 z;e{6-rT<6i|5=9j)$c(4){t3;Pu6cknN8hhQ@7dFZT8_)^*d0%HDpc=H>dueQ~$qp zc(aA)*KgeLP7A+%_>}P<7=CoF4-79M|4-rnQ@GafZVPUp|2NS8A5!=sg&!UsyU2#a z;}+RS@r@M!)bMPJd`jsK(b3HU@66<;eML^y|D$w$?66OWoS^?-RrkYn+V=0-<-YiI z3I8)4PVj$}|G#e|k+~-OKi_2k7n$t;5|jO3ezO1Xo$UV_ll@I4RMX}B-`D^De2+|y^8bf^MAPX1t2CZE+5b_RcG7r}y6-i*kkfTN zrgH!6PYXF$|4-5|AADZqBK^Oay1)Emk=r&^{ptU4dgtiM>i<0ryNA+WkXv^=f< zFS$q85ckr1LsXu7kJI-Z^#9-0{nIy@4AuX6=O|=Nb+4{C*yZ6u9#)-H?uY6Bjhz1O zFBY=nCgy&$x*u?v{jb9ArvEeDl5n!RZ?5#C^nWGK@q4Qt^#6e`>A70vS^eKb|Nl+% z1%3WX6+ybH|5dyXdR*{4NA0eXpS(Ck@eeBPH(a(#hRgN;d>(I=cK%m3FrSB={Qqy& zy;>qTQP&fzyr^h)yitN|l?*qpt2+9!?vt7<-beNS?W)6l)O`_+>t~wYuPI!><2(TnXnL2^|5Z7v=s%$U|7UCa|6%>V zu+mgv?|)2p6di2tTTSMEwd!+0tz#>i%YG@coB!WgX~sS-Qsq~bmMV{`xb{-_E0zE9 z?q12chWH3lLSwsK-)WcSKcvSyyt7%+Y|C5g&*8k*Vm98out29>hzgPNwlpc8w(*K*O zJ9)Z>@_$xos=pMSFd@0jfWN|XIxZL{g1Bxs5(DQWvk-a zS>3Nyd3I1eAE5uMv{(O+@&6Ug>63-MME_UqW95Fm|9`Lkuk>8;-(ACg`wq1yPw)Lo zbI-?heOTQqnyS21I;sBuf3EHauCpn9;P`E(p_$3Z3L(1gA2p68zXy{~<}_SxRfJ@@>( z=broL`>m1VxRUf8|2^H?@>07t?TV!w$&-eEL%R4-yaSfCRIY<^?O4mcr;)xt>Fe-$ zcs?#BzdOEbv1^-Lu(oxM!d(1wjPQv z!r%Dm?iwxW(@B4s^h)f?N%Y4@=Cj!4Ux*j;`JS75SI~YLe}>MxfVPR()2{~7t4_4} zS@n{CttWj0=>yMmKY*XZa;=o{c@^K*VEG5JoKw}5<=Rt&-C9%^N`I1aJ|Tb6ebgDs zBW-rqktWjJoNvKy&bMK=eoFanZp*%ItlZpK&)9Ip6mt z@}C&;>A1;ne;;i)j`Kp=FrRZl>P^OOu8Q9ykDNPF&IPhB?H5O|jF04T$II<2WzHkL zjy!TLkn(48T;y1|b@&cpl#v9>>T;t?AF6l?@$M1vF9!Ym|M#}7^Ou4TZBR$+-Hz!^Ce9Dyk?~{KKmcHLO z+wOZ;`uob7IT0%7b@F^d{%Z2Si?`%P{!QlCLdw|&%ec9H<-S_h4Y^0Xh5YUuT#H@$ z-B`+$xxG~}*C^_8_bOty-b$IfP`-@0>?{3xnSI^dmNkC~<;)|G%=s?<+x1=cJ(zSk zzT!oGIUkY#I6VJ~81E6rs|dSmbtK0_jrDt$w<+^Ih(@Pm*%T z^X=jo?+m<|eWeY5#XsWwo%neL*8^P7nkwhA>^s7~E6F1tJxczY@N)9JMENpLW)hMv z$3p52kMkw}vg?bazvbtbF_CM~%akK_&rmM?4bo@Mh?XgTDo4tfzKh*l5(n!ocI)sq z>^u8oyLXcQZsS|#Ln=P(${0^RPIB{E{*C7K#gB(Y( zJ2&MXz^x~e?#@S-U(QFdJHI5qoB!e>%9ra@CU)f~V!58a@1M)(l1J`cC66oL<(Kj$ z|F!;gS&oa`=Ugl84D~TH)JIt#R#WEIAK7_vBbMVRWx94|li#&xA?eP!q`NV9>&XP; znNPYq7YalBwz2P>l%EiZ@w%|P2ar7Pe`f2Oc>hX!7Lfd{HiAlc?aurr9XdmBmZ3d+6SJu7oLYVox$JHr%thaKSu7Kr7tu7&(1+tR|fed zJ;6^G?}88V(|;fG!6E1QUdQ|?#0&iVCu2FsrJOVTJko~U`7W2VLE5=1*L=xSPQToo zEM}XV+w$uTZv5qWKf>RYxs&q67f`12ijXh&UCzGlI7&J0Jd*ydB+r|alNib)>$a<_ zl62RG3&?*x`5(KEI`N%Y`-SB`O|FlU9{;vk=4UeVQ}Re%m(At(I$02Eusa9qu882o0qYU$*~xZb>Td&fV%4`dj(-A7C3%haX z!EPM-up4vP*VQX^ol0GQr>=p}zC)pX7l!&Mmi0DqmCch|cD8vkLVh<6W1;dVu&Yt^bb^<17W#F8%0FO7IE$4<`QGaKw3%fqwm-4VAA z-evS_9eKn*404Wg?8N`shQIkqeqP&oHPUzTh#%nh?4;gg?Ak2ezKq|cCXbA>Ykw-~ zuKnrQwLcTP`6hPrEgQQvP5N^5o-tlhjrHYv+93C((yzm4;9SyIP}f@0<=P?jO8zv` zmxPYRBb4LbHJubnm%3hK-}&ShyLTYcmtE+K%(129zw9v9TJq$E^8Cp7xVa(i{DyL_ z;zZjKpL^>x?~s$svIfqG*gM0@?f6^ReqHWfp-b1eMF{DpXzP$6< zNco3;62143edU=~#^e~%<(+3Sc4J1tbDKwh-TU^NsLPqD6}T@{j@Z>T zfL)nl*EX>$U-G+ju}hbBx^!{4Olh->t(4>XD0X?I9G5;6N*B8_N3iSrSjZD0OMcfL zNq3g~t}d}FQyk9Y`Y!vr_K016u`9=2DcV%U`_ zc5RTp$efqIe>{cVInsl--D;ZN-%7fh|B^rDQ=7w*$Bntz)hl_NyZ$G?+gJJ}pZhH5 z##Ahyzlf!eu065LG1ne(xDD-ze`V2Kc|GrAL;Dto_AT}E%lTD~T{)GZ{MDiS z3mL)!`XcvW@;=SISFFKqJnOJ4N9_955X#dOa!V+GTgV;QjdK@v?;LtU>9VgYzYn`{ z8^CVdhOlefNGSgpcIAuRag;o+{W6AfuH~~H<=bMH9WA`4Qj&``YByDr&h1fmQ6=OHHlD_Bxdlw|>F3%&OJmN;iVa81{UJUn6 zvTr>8$gk@+Snd1h&n>h$lwaEK#!B*ck^dy}h*uwA@3q96NH6o#2e7;= zlk_nx&rssYP`Z2;Ea~!oX*TyZx3cd>>P`BL{CFYt%5#~1HiV1$H*%y*d4}4RJgXac zw?_W`@OAhC%8~ca>#^L+$vDV6>L$NmG7noqZo}?ah-G~A-W$8))q&l)BjvbbA$EDh zZv3S^*|aC-R(sZy@sA7nH@@ZX{O;`gaPp5c4$`mfdG@@Ic8aI_?xIX7M~<~v+9UH^ z?C!m#zpw6P_q02bF3*fblwU|WvahVq4gQ>wwN%pGX9O~rTpwR&-!1qz-0x&xu^V%7 z744KclgPdaJQt5rrj+B}`N*FdT00ba^(By2NvR%UDUeo98{`ck4+XcI(N2 zUyhVNgxxsEdE}1o2zK+t<(Dz!Q6jj9A0v+|e*(Mmr$YNm`R@5h?8=YbB05&_*qx_h z_x@Ara>ub0ySkX_K{*YiyK`Uc>MEr!H@C&^8B^@8?_zo9AnW{E_C1Z~ZW$9<^Akq- ztm<#}T^D)BDBpES47E8qWSJXFUbKBvNq2oo$8HS~yS`^)*OzR`Gtb_qm~GLx}uTdJSmjjd~EdT3v{o;DWo zyE>~_Um0sz>$ZB-?7mavj)l~l87foOH`j)2(p~#=v1@-mcE_kNl&AQA^0;FsbHL5d za_r__Df!)8mG#P{mxs!%43$}pU72Fnh8pb71-aIUrC(xsPx$>hK7YY^_-Ef|V7X_I zb?knQuY4~>&KLRY?Cg8&o>sm`w~lw7lKwb(zB|v#kut9$k1PLLypi?~k>^fa=eI}N zunqnFlyvuH_8Ytt zs0dUd~GMe9F%$FuQAq>n;1EKVx zkVis!#zLOJuKiOX$Hqm|C663Cxu2AMUEd|$U1w!qXQ|8Oacz)!C3##qkI)x+);Qw< zjwwEwF)#Oh7XB-iHNle@38N0h;L!vlU7@v`?2&_ z(jO0%`CQ0z?vK&$en_6LxlfSqfruwU?j^spU((;gF3*Rd^s!L-=8(_Kjh30p`)apu z;t^(92V`!a(Q4!TBm16%C0)KZvXqOX)I0MFj)k9RCw!ytJ+X|{LVrF#=%>3emwg)+ z*>^vrO!>~OjE}o^%Xmt<`>v6=fHGx$i)Zf1JeNF)*v;)^?A|k{hVyWK<-Tao1?i-_ zbw=8-j52q+m~)2pclXj$G{8kdIy43PUaq zxisXBwC!H%I{j1L)#Glw%ft4$s@zw|cVcCIaPz#JeO>#dOlK+6xiVC~*qv))X{Wqb z7fWBpsjC{hzKGp>Hp%1GD|t^Y*NB+E^BElJuFR#Jo85=o=N)yVyK9-`apTZ{UE3tx zxe2@RY{9M#QjWV0wqaLJN61~+?c0OhzJ1uWLF~#I!0s3gVYlxHcKeQn(kDXcg$&8H z%pdtq-4yArJ+U*RWyWK7A1n87gB-^@_`B+fq`Umd*wvegUA^g{JeeV9hw|r!^5#)1`C??(2TZ&!1lE)oixh}ZzDJPF>b7jcY*xj?$_~~-pk#yJhI_%1f z#crN7V0Rs9!md3nlHY$0YQwHyV%IMz-}S5Gf9%@R#W9jL%d@}OJ@3oAg|9gla$S&g z_Zjy|tV`Q}Io*4p!`@w_V_D}V|2LE)YnS*y(q*j{AA%qA^XG?r8h*}Ce{)B^hvM&B zPP)`9`(6_AHCVn+(nVj~IP_pQ4t?15UF^n0?%CuT`YUxUrTx-hxz>v>d4~7KkMW%! zKV8M z&n|pWAonEe{dDIOPqX(SlD;2#WMA>ZkjuZHruS5mejMr2{<*Bfi_f0!6`#m^HO4{u ztM6UeCf-PU->HBpKc`oYSJ^<*FA?mz-Non&U;9g&y=UA%N^6$S~->d zOZoEsH2M5ge$PeTak=Dc!aL%R$Rqjp#$Vt<%5iH#F?Q`K{U5*onXL(u?)oeFUAox4&lF4h z7d^>6Ja+9DyKACc@3Lvbwd9epIvPLVd#e+sd4I>7N&hqHGJhn`yoE7dC+U@xsuG;?l|^fSMPp| zx%h;qnGejF2>rT`HCE0wcTcj^Z>P*dcRpUuO^eJK`Ms)VPUZfNGF^YguD=7J^r29_ zBiN0DR;Ib|-%bJJdUN7CI|F`x7skK#LH zq|YV2pZtemnQv|!=8-Pz!>y#x$9Z3Kea8#&pYS^0^3L!9$-}-ueq2HxnGcfw-!$4p zo(aGGFJPHh736stPyJ8&b) z*JyEfDE;-2hq3fm@_ZNamMfy^yM&y9UHRGAm0yJC`)xZ1ABMXaXV*uuo0DQU&&6`C zwK7&0Fm94Rwvx})eakg#rEgicrJeE}stx3Kej3kZo9rvk0w0k_{3+>Tc@HLID?WWW z^XGDV2XfOmzY9j5rR>v;f5GxzSJv1+vxdui5BaW(dp;{=p$1gXLJ?B<17ZaIVVwBj0h7^+(@nlPA9{0?$hBDnelzL?xuA<%*e!7gmq|3d;LG(rJ#>DNr zocx)T87@bzW!ICQO}cBF*qujGzI-oHKEr)CbgZR(Y5yhcE0*UhDM$PjmN_ru|8vOO z*W2&2%ery(m-bGW=6Y9>zVn%^8fv{wNO%1e zyD^!EZ=jrK$s@iS%X#-XWj>1K*onJCe#N)kC&=$g$bEvW1H+{EP>%R(-?DDV_c7%9 z!c+*>E;)aHC6B!O63aNt`@PM6U$%e2o(rUWnX7WH?fdO???BR>ySZ5wf>%2VcNxEFi zBz-^jb@}Dk$vDh>&&Fh6eT@Dt!;X~c`m!VWSKiFuKqRl^mwmtfz~;t-x%M5ub>xx! z`}~_V*`J4UEpV340_0fi^__jrD{XM+sie!?sQ;K}e9D)cy4e|~_dUX`5U-TsiXDuAJk) z_hP-hzvG^n{0s15zKih@zL(=;@KV1nxtCs!Zy^0-KTk7Wa=Mi%b8^E^yc=PD%3M7^ zhQCGU?${wH(gw+2F@2i8XK;UuK%U=En;om?-Rq2roFnUYpXM#0Z*N^kGxCYNW6y6 zK7fDM6yKaWO+O0|7tl}nzKVFOBkgkm@xNcT&o#u)hCGLpMm|T7^myN|V<~?P<-d!| zr&;;px0rX5U;L-+s9jx>?&=c1c1SeOg8FDV!;~-O+~kjgtj}xl6zTU-ucXJu$Lcw_ z&d>7@>C(1+NdE_3@2AUWMl?P6++XJ8p3Juw58~e|#5&)&F39=fuD^1wN&36|l;eIn z>x_JdT*m(<%9nLj)(!cN#7~s7l=5Z%#AKNd-DaA1GxuFmmwbLI_hWJn{*8U*`X0k| z&AkU(!7*`jV2x`Jc{XD?e-EKdv7C3#qm*-`pT61m63YKCKAv=G^Q^66y;FT}gO_1B zXOplzQ_FpIZq--s#ay9^{OoNg^JV6tKUxY<)Cj>xqqKR$E_AD^J5UcA(zM<{eQ4XcvTuK&0d%sa4We6ALuiX#CEqZ*NHr2@6g{PB zV`%zrl5ZTHshS8hiSE_3DfFp|Kcwu3Kf?KUumdS)A;Ix_mL_CW$h_@WTJajEGFKVDjOY;CR=mR6)J9by!9&i z2P6Nb%12+S3Q+t!*`p9$rz%3*?j>o(C{I;_u2+?!K2;ey?Dvwd9F?mo(7UQil$0)8 ztI)YB`7T(CDuTA&TejAqELAPKNL7dKQq`juR1L^q|Io*p)`X_-Bc(K>3{?v{L)D6! zRBh;WRXf^aU)iGroucYQJ*qCWb%t#1M#rgo&>B@QdR*0qepK~KzWrp60c5QjL=R}% z5PDBFjIP*U@{OSXQ;ni?50JDm)TA0mFRLcdtW4QDiOyF|q1k_sG;ca@Jyo%R;?RAX z79S`9U3Q@C!R5t!P?dzEif&b zQNzKKF9+o=5apsvRC(wwRX%!MRe;XOmVAZiDOC~rTvd!#A0k^zP`9cS?Q*E3m7ywC zIohnMK-V26TPxA-Iif05uc}5TER?he`pw~@8ni%Fi;7is=yp{-vLo1lzR|Qsbjl*x zw+X$dYDNd-N?HqgOVt{v4P_r8Tiekcst(kx>O>2Ul&xK;QPquJQ}v*fqhxC@x=GcC zdQ|=BC)EJjD^KzbqN7wps7f`Ao>q;ZUsR)L-eTEf3@uZQqqV9D^o?o~J$1C?n?hfy zxN-OPI7ZT9(R->mw0pj!aqHt9r%FH%sS?q!DhX|OtmI2ZC8`uOu1ZCVkCUw#XvXn; zdq{u(W?F%$mb6<`b*NEQkM2~-@5Su2MDlf_->KyHV^US!XddERMG@5?TC0-Zh3QZY zqt{TJmp6l}c%djCO`OvMwYJ!{j6!dNcM>H`p~qKME%IxBHs+% zQqu;JwOPIu8n0=?Xd9J$J9K;1DB2lq<&B};G)+E-wR$s<)tiZ|-Yk@RvecW6tOYq} zv8LrBt0NB`r)l{}+Q%G1|J1ZeWOc|l$gB>E^y^4R51%S^WFc#5A+mf$LB3*SEhs@Y z2BpZxpbXg(3h%OWG!e6)P!tYnvu1o1zB5K zk+r1_SzFqXwWR}DTRM@or3+bGl4iOQN=8<13XgJ{`29KZvZ& z!$F#SUMOi>d5yDK$Ip;n%Dap6RL#g{Yzz7;X|udmRIO@5t2JLQvXc9d^{5|N3zFhp z3zCuLOF@!vD=!tjbhaoB^`kg1f~?Io$o8m3R&r0UwKv$>7i{fEHU&tuPF0noWvU8vmZ}n!tE$iis%mtxDuOOk)u4!~7Ohs*p=(w3=tflo zYEU(zwW=m`m#P`vt7<{5s#f%{stx@^)sCJ}b)YU)C)%j$LNBVi(aWkH)UWDAZ>jpw zd#Zl)p=tn)s0PvJsv-0r)iC;2HG(Epqv&VV7@Bs0jOjSqQZ<3%Rg-8N)fC!Zciq3C&j}qeE3GXpt%v<*Cxpv8r@*f+_?3S(S;3Rat1M zDjS`t%0cI-a#4jU4_&0nN0+J!(3Pq}RHG_FYgEPP22}~VMOBI#Rb}W-RXMsxRe>H* zH6WYWjmTzp6S8^VjBIANApiP-Ms(k{AYVJOQaX^8(iv>+3bu9!TYHe5J-x{4=tFk) z^rO$UjsaxXs6k{U4Y#l<@w_#*` z8$s5$QDiM0L$>aE+qhQ6B5PG#kQN`LB_Jy~G1!_EY)uZfrXYVzkw2!$>P<&hue`DP ztWwU}EF@P+?$VI;JO|lW<)Sgomltf!54IK{YjYv8_7x$kqZrvyC_#1XhB5wsNhYRJ@+&Q>Ox1Xl#&bQ^8Lb9q9Sydsu&fjO3*o~ zPPBK8%8~W00$JZG zk@c+#*(|9>HcKMNW=RdQSyGF7R!ePlXp^cQ*{a-tUe&Zl^ror_4XT=vodGTA15Im1 zAFJBXXQ~lo=f^0z{c1g0sUEMfMTzJnRT8>Om5jbsrJ&=kmwc({>>EUB=o(cz+WkhE zv3a}mw|yE!`RGJd0s1rI_#!(ODv^z36|#}6MmCZWWFuLFY$R)ujaoCZQd*FV6>n=% z2X*ji0s2|hjtXy;v<`HusuPW=y3nHAWNSC7QuUx`RK4guRUg{scFEU|j#dqzn^c3S zUp0ibZj^k(l20{)R;fnOv#K%lvuYgWu9ZC|(2c4|^oD8*?eI6*>g~bXeN`;FT@{B$ zRPkuu9g;5rovBJh>r_eT4OKGQu1WHxpaNAYTBAxsJ*sr{t11H(-6?xyqPtaDXjGMr z=H4Y+bI>wXF1k;Zhdx&2qj`5rz5-OPDwKSxA~dNgMn^PDz7kZaDn+lT%23)m*;vT=qB>O_dRJACvRY(o16qjUyhbF~H{O?^ zeBIiNmZ(}#sj3xSq-sM~tJ=}6st(km>O_yKx=L*Xt=+-ao?vS)vYz)LE4d$8&j*n8d=Oci zlha&nDadL|MOIrHvhvcAm6w66ykaD8VwivE<@KUcwEu&mGPFchj*3(j=uuTA>Qhyr zf`?>}YIKGwf|^t{Xrrn&NUK91lNRT-Asef9WUcQ&*7{Clt?xqC`fenx=W|X}+a~Ho zH>&zjv#K9Gq#8iaqBw65Ssg>j>KH~=#|W}IMv;xs7_t!`M|S@=fvnAw$jVEf=h8Be z-F;;uyZg#Q_Qoq4UGuQCB?sC4S}w9Z@{sM3ALJ`QR!1SS_7x$U?ZwEhgC)qWbfw7J zSB9*YX7xL9@)4wpmo}}Ms&Zb30eD^ zk+rV{S-q{u>TN?-Z#%MjJCN1UiJsK*x{!@VH?qFDTTM^ z=nK_2`bjl`X8m2ZPNHp9Q)n-hw->9ZDi-Cd;?Sw8cvPWEKr2;==mu31x3re&cmHb_ge(RQjFw3jLuEmX!aF&z1?xEM0PGzAsee|WMdUUHdZyr#;O+CSk)oB ze$*oywFYFzyb;;>H6golHY00k3$i=&R%CbPZOHD-+mVfU2eNVQL^irz$VRst+35Bl z8{J-HquYmUbo-Hw?f|mU9Yi*|BgjT*6xpbaAsfkYWNn#1*2_s`<1&S8)VzJ%NXDYN z$7P)3&`qj%WUEgCvK1*2*-Ds%Y;{aVwsNH)Tg_6@ZCXkix(Y*_ybfgL4F+jLLE12~I@0I6S(1TlmSiHEC0WR3Nj9=sl7sx#BP%5j zNp0NKAIwU3WBu4Agu^lDaFW2DM8lKQe-VHL)OxAWXHDxS(}@X)zOTsycQ(o zaX*9x-qJHK*R5O$$X2dIWFwh`Y~@NuR&olmwxl8}ISpBD>Bz2cImr5(8*I%(vIoz8 z=;pVj1+B<>*%st$M|RAIk(DwMY#l>(O&&+q<_TnNoDP(Q-j&N;`Mb_px zvb(-iWOsOJ$nNmck=@~Cpi}>?qlT8Lvd~$oY*enwMb@J{WIf79)}sPsN2?H7ZAHjx zD-KkGtlm;&ca~+y?kvmE1zKALvgesfWY05I$ew4akv-2u(8an(4Z2KKi~MIC6w$PL zv|81Gu2l^qJJKV_j@u}*UXCH_+c>heOaxmek&QvZv2Nc)^tzUugx*$FA-N>GWdXPN}^`ea*$}H(a_Egl57DOa%0NJ`RglznVk@b88S${{7&F3*> zZ5~Iq?*y_HX%hM82eMMU<4iNWIFvUmt3^CIT9t^FtCEm?MwN{0GpZD1pHZcvbI8Xt zEV5&siLA|8$ZE?*cC>PkwIvtX(aJ-1wDOT1tpa3yD@1nOijW<*Vr0jy1le&bMRu;% zB5OfC`iHi!0ogY>8j&5hCS+}HMz%)_vbMCMCv@L7WNmIoU7FS*X$8{rE@b<5BkOMu zvi|l;KJI)u(#ZPTkF1UXWc{r;-t(W2D$y5Ohx}bTyCbVc*3t;FdTWr4MlG`EiQT9gz$+t=QZYYxblG$DXTCICTkUg{4AbXClMfPl8A8c(1wl)S^n~;C@ zAe-YY$ZBgva;2N?4WoqJV$cZMQ8kKoMLchv;#wMyY+TZi-9e=zTUkqjw9-Ij$j*Rr zRIBAxAnExWudbM*rD^p@^3C)*(Bf1X$xd{fstd{b%;=&&YFZB}LUCR%l2*<1rjYIH z@#%r>8;fk;IAr_ABilCt*}lcd_ANoSZz-~U%aH9`j%?owWTVlB{8c_kOa6=7nuBa> z1ldYh6Krh?G!Wz~IL+5ZZNb&t4ZZq~&kn{^4uW?dq(o+lw&xsuUAx^GILRFti0X})HA>1d&*l_I-e zD??UZIl4l(R-jd?O60%MM0Vy?BRk_Gs8;jUAiJ{HqU$xS4&AJ(M>h5i=ypwOlr+6l zXhJq>&4F5w?c0j%Gqg5jt4}+!dOMKS)`{#sq6=Byx>1wX)`RRmu@|k=v_51d_oMqY zZ2&!_8br354WV{T8%B?*Mv(0>itG+$3_Yb=$B~_v6UbJ=N%VwXx2BMtm)_~FRk6s% zFAmwgPdu_Z63}y6aw4+Xl!SUTEg4y>Qjm>lDzX-&A!|W8vVAkqCfzqPNXtStLfObl z$w4;Gx#(5hBM;e8$Vb+X0%YI+D?~QBMaaJ&M>dB_kj=1CWXG)x+3Hx1-qboOkhQrI z*_c+LLET!7K2SwOtX#ZvL;h6=*;S(s*=k#lY~KcCJ#R#IHE2S11~el(3N6U?Xhk*; z+K`P#JFTeAb@1Y2_hwgS3J`g~8UMK*fPd0+k|b zUs<5?U~5H?R*62=xmJZfv-yX{Z2qCmsv2a+w>D5+pn7EY5e>*jqY>GWZbEimHX~bO zThRAfax3~t)rRaCjUihx$C2$jf$WNte1^M!PC?eHRAenkLv~F`M@f6iH7WzyxtfV= zk1S;E%SLvjbC6wUQqOebmxk;rl#b-yk#9et{R~t)mj9|6XdVLN@kI$m(r5+qJY6Sz0@?)wUZ=>AsW5 zX4n+65vnS8N52}`=thto!5U<1NiDLH>m(n~AoLvBdD(#E-f@;Ugg)Fi2F0Dn-*Ms> z?q_-Ns1U_@3Fw-KW#2?(pC5IiZTFY0T_{P_jdoG>pj}nHXirriN>}xx{Zs?!KvnYj zuDldv<)tDkFAZ6F>B!2C9?9Wkd;@Bth@-a@@kNk zSBp;7me!$Vs(N%5;(Mj2O4G)X)jNT#-brNjP9dw;t8n$kBC9tJS-tVd>PCjxzfEwaY7~{J#?aZSaa69FK(cXHw>y&3FuN)BC1v;p(|9$D58?zHMmNZifUA8=xS9u zs#Rs6YgO`F2X(3}bb~4z)vI#Q&8l3~pvpsb*5)HSYm1Pro$^};c5am*JGV-atUi2; z0ol1#j_j^Le*eJUDO4i=xfa=KQHSgp)g!C7Gf-EswL3`bLH4$1)K81^#*npd99jD& zkhO0TS^K7twa>fIwJ#P~`{IzbFCN*-kbrFEPeeVdrI$&_o}!b{2xaogD6;#eRAl$S zX~>?j(~+$z8OZLNGLfzPS;+1bvXQMhImq7Y(T%DO)S&7_YgJw7E>$6PdsL0c?!ueU z1De*1?7qDPwP{)_dPLQR9#^%ar&S%OTh)nPP<5eysk+f?svb0;>P7bS)Q9Y|n0{oR z#S9?(EM^edXE8&_-Zu>+`%Gj6*=Hi7$UYMpL-v`-II_<~CXjt5GKuUnkty`9wnhHg z+kdO%Z+_aR;c@8yG%X(4y;K4k)wD$Pl`08++AnKKGWtf-QqT{oR5YbZLoxiBF77eW z7OD(1Ta}3tR9R>{RW{m5m4kk#%0;QFJTy<0kM>a&paWEeC`(m@4p9}O!&N2dC{-!S zSCye9s&e!vRRualRf$SeRp<;=H9A`rLFcP#P^GFCtx(mWD^&Gpm8t>Nsv6PtswQMl zHqGc}O>057t6EW$stv7EwWIr09q1udCu&!9p~qC+=qXhXdQR1gdQ^RAld2!Rsv1CV zss@pLzBPmfHEkGupc+9Rt47ghsxdUC8b_N|6X<)@B>G7;g}f}85#B}I3#nq!EL9xZ zS{08HRS9TERU-PWDhchTN=9j_6tuT0745G|LkFqSQMM`rEmUQqBUM@G7*#eZQ01T# zRk`S7RUSG`m5)kQ1!%dd5S^zgLVr~iqbgMix?EL?u2PkuYgFZ^PE~ zRW)i>MbLe!8uXy57Hv?~p$=6&dQ#PZo>euX=T%LpSJjMOQMI5qRITV8RT~;owWE(z z9q3b4C;C#=g~nCg=sQ&p`md@N{i^CivGZjf^rM-o0W?Q7h_+P?p(NEX+C?>jc2$j{ zJyl~UT{VvOQ%#@)Rg-9eY6|73yo>p!i7FNyt%^g(tKw0iDgm9ON<@EAC85(*$*4?~ zg3eW?BD)t&Lv}Bkj^ti+hL?ftem4`@{caYrJKF3ZEhk9JMfRCy9Ag;S0icj3@?JLyc%RrO0~$Il-BKh9tEUyfmv_PJd%h56v=XIbS?x|;aqiBb0**b<&&?xrM*;7I@vZsU= zWKRjL$et3~kUb@|Bm2%oN1#q*cM4rd?iBcB6#b1c<&#nLq^cJUs`}8ks(v(wzhlTJ zqiDWr5G_*;p(|0GK1+4r~#vN<$@Yz~bgn?qyB=Fm8@IW&Q6 z4o#wMPLf$Sh3stfE_HJ#7TFw%Lp^#n#v_|K3CLznBC?s2gly&{BbzxX$YxF|vYC^H zZ04jRn>q5E#x`>@kidY+H0=LJDpVUSjYY&43Izs4dN zjTv4kvJo#sHsa;Tj#dSdqZQ{>BCD+mS#8zGN{%2axdutetYOH;xenPl*CRVu8<352 zBeHRBLN?CL$i}$^**Lc%8|OA;b0@NK?m{-s-N?qd2iZ9HA{*yEWaHeA zY@7$sfBqr;7)17NWC+>2kzr(?LX9B1KNv-Fe=x%vLvk)~r+S$?TCvE+FAmxGWg`2% zgeDD-8 zzo8$Gp3$@f)U8TH&#RKqH@a^!dQsC-&?Z$X>Qkkm*Hr1~4OIquOO=TRRawaX3P3ip zmgXR9X)cnM@`)X?UJfJc<%n$6tMe$b-&&YJ^2x;vujxv=TjFmkp}!s}pGdU%iu2l$ zKURS{k=UA1Bxh8tmmRTlFxJaKvQqO~OvqYVf&3$l{3DI*ysSoc zUPh1|{TgIPzZTii??RGqrq_#JSS;Tq=|k31Z>5zy(~C!s9W7fEkp11aG=D4Ks15Sv zAQ`KfUT&Z~^fLSM7YTxV<-yj9K$U1n^VJ3U8iTD(ftt}*ny)j+*Ar~*^~JR!*g6($ z9S<~tW*sA~O1R4PG8tLxQ+)B4LV~Ti!PdM$`RH^lr8LM_5p1nQ=ba%ltTx!%5NvJq z#eDI%@+l9hqrBN(A8J(fqjg%x5V9jU;%l}yhJMj}<7mr#>Cse>FLssfG24qrd$M)5 zmw+-fU$Vb7&Pxp>f2l(1nC)ev<8_ZLByHh0UW0sjLB0aCT=U6aw75`9E)MdQ2KmZS zo#v}Rw`#tsU~2?Ds9S50^|#*N%I~uW`I=FW=4(M#TU(H?Bgog~Z=LORqfxD`FUU6# zXaw1rG7+RD)wpvj8QHm&g5=!d+lFW^e?NP+7t38j{t2QubfPK&*?N!|C@D~Kpp-zV zzWALawB%1R&Q-|H^J--0c?4NoYLK1hwaCuyI%IRB9@)MP$o6eSwr>-%eVdW(+k$N0 zQDoP~F=U_8j-y-uEUn7F+Lc#;th_>G<5z@iH7iE8!j&NT*6wt#5p|vvgPPFOs%G>o z;_r8&jhfaL%4Z z*>%4REnp2~wMDinS0MYOzY^JJB2~!Vv-Tl*GdstNtL4&PB0Y*nHzVG6B0C-_$Xc3; ztfgtljz>DOk<37LWHON*nJi>$b~dtc&OvsK%0pI1KC(IrkbjJj)lr13&F$zTjttM! z$nKFQk==()A-fNYU*k$nKvr_*wJt3SSz0!-+H#Q9mW!-MdB}Q{kE}-p$a++WtVc!2 zdQ^;hn0awt39?p|B5PF{vb)1_WOs)Z$nFj+ksbXiWJkXm+0l<6JNh-qdR~j{?ywHo z8BmYx?yv#bENMhGOPY|~9X2DoJ8VIAci4*TTVZX;zEjkWY+iLBn^&F4zE{9q9sON4gN%kuE}Zq>GUq=@R6x2gqL!kR9oAWJkIJ*^#b9 zcBHG29qDRhN4gH#v8+cnehtXRuMye!H6a_nW@O{nf^7U+k&Ry)vhiz2Hhvw*#;+6E z_;n#0ziwpX*Mn^QdXbG^AF}c5M>c)~$i{CF+4v118^2*><2QnA{6>+D-x#v-8%H*N z6UfGI63O`Sd#-hSW_Y%&lSyciDjA)iNwT+@JRb$9TcO3b5|Hwvd64_ChLe@U-2G_n=WbKPX zwlzN3nhEv_R=WzKkGWCbIIfkc~z*vVC)qjbARZ_T?e# zTRuAP3|Vapklg9Vd4Jj%p zAp6c;CrZ_RbfI~w?qF-rjeHYE(|VEhtq)n>`jPc*0NLmcA{*TyWTQKbY;;GEjqWJ2 z(H%op@;I`RCyyee*fUM+3Od_jy3R%70O|ITpWc9`&t2Z9` zjSOU?k%_G2EMz5TBP%%vS*vo9wJHzUXyhXsjRIt&QHZR~MaV{@82NWgXp`PIl_LLc z3E4HE9@*S&K-Q{8WUXpK)~aS?{cSt?raEV35FA=?^{tbGZ{KU#s3f_%wAz7%Amn~H37(~#|(j;!Pi zWF==Jt2YZ-&$E&BJO^3NbCLBt4_VLikIvNfj&+4@q9Y|SY_w$hX$JHGYE zN@+k=N+YsTnvj*!jI5LvWTmtsTd&%XmDi4Jj}Bz(X(zH(vOwm$k!xNzvT^A_c5d||YhNF-7W5-))c}&; zB;3d2n;%5Rq0`i-;|0FY!RH)g>3_t@e)^+$yBtej6w1TPUhgbF53eGEeYt7ky{F~B z1Ap)PUOe3|=OG;L`xxx%eHu%PrJV;5|2)LXS%7Cz*GI8@dnd;7Y}%4f#o|Pf53ppk}n&+dCmu?kG4|(pKX!_ESW44Z_uMBw& zcKw>05X~e1l9lUw>~HvXh(A{A5~JxGLyk#`rY{V6^=~mnUM$v)$D(UMWvEqFMuG$>+Zr?_|==-QY$I_k;*>|@eqUrOnq|5sJ`DDmH zn!UOFdyfB&rZ1n0dbgj=Ihp*skiMmynZHEyEQ*N<#&cE3&xgEd%NT9Lk^Gy9r~5t& zM`l_29Jd}j`+thH{_m>bLK?zH*Xzv$~I9q|0e35 zZKGwTBu3Le_g#JPG_QMSOYfi!-M@oib7^VFD??rv^2U%q3VE2%Af>+} zYh%2e+-R94Avg1xf#g}x6ypv1J{y79OR!Il=8Q2 ziSbf=AC7av>3_34-?ehy`Chc1o$j#odYt3?Q#|bZkOxVpty2C?c-Z$RxVzcX4{xI{ z{I}$(ewhDzB|7HvZ!Np)Z^|1{FCDe~XL6lNVaEmRCg*id$R#01LT(PZJLHA`u`;ir z%%#2`#w&fljo119RlInb$L`*z@9^)I`rcxY_MB+x4|DE#e=#q9k#P(AG)uph^kLtU ze|Tv$J!M(6oQ=N6D8KnUOMm($#_jx&F9_{>ky+|3xolc6hRt=BE_Frjil&#`ZRtmC zq7A-RiQ6na>tEEn!CZq=A7kBd=Vr*IA$LD+`R^rv$`j@RT=HbJOz-JvnJF(t(^FrI zmfzAJb?)m?_q-AE2T`Z~JLE5-?)f_Eh2KT{zBJ_0?WPC)D%~+^Z~k;W*89K4eD+<8 zBfi(*Vc!qqk(}thEr&;)x+veC_t>b1msop#pstcXndP_)pK4yo zeVSKlmSdOVyNmRakRu_>Kg=id`5Ml%jTf1B`GJ4OJmi>*qv>-)UKsMykXMGhF657@ zqUFS_h}jkUt7JCKAm*H{^vOFAaHR$np;r%D64# ze#TpEmU|oT`su-W7r~N8&Z&~7XnHr6^b-eSyqf)Kxt@L*E#LdErQgkZ(mg9y^JKh>4>osyz+4Ksd4Z)*k{-#94eCuf zB_9 zl4I$Tzd7XG8)pPfgVBCDieaCrM zzJ<2`?O*Yo=51$iuNZd1mYPS>y&Chc+#e3#U_NR){I>bYou_%t??j#Ru6g_9Xh6$-L-zQm2VMT|kcUI|My>ppvl&C*TksAfC*PnLaD1E5=LU zihg<=N1C6d{7mKN8T{mhi+3hJdC}pW#m^jmmhp2sKbP~9*JIw<{LJFN&yoL*<@e0w zCllgaen$9tp8PleJD;DwC0_+U^Z0oIKQs7wAwNs_`B#2M_*u!%Tz+1}Pfi~1Vtyv^ z-9oIwpOyUgYJP6Z&#U7utBmn(A^u6cM!ZRUL3~5}NX+2f&Q`=c zVllClSV7bhcM$gzj}cE3&l6W%9OKmxHxX-zdx=(}op_P>fcQTm{*oAPM~H z77>3W{zg1Qd`0ZSd#602j5wcILEJ`cB3>f~iD9DfvKVg}aTc+L_y@6HH8Y1;Oq@!b zOI$|WLu??PAYLF|C1zhv`-oM<^~C)|7x51(@dL5_mDEp^ z6BiIw#MQ(d#IwXJggoDzPMlBF6Au!-L?7`U@d@z*F?}WDM{G^(LF5yK#L2{2#3jT^ zqL#RUxP@3t+)X@4v=jZr`@|ks(OzN!kx$%4+(Wby|0Lccz9qa>G2YI^LgI9yil`$t z5ivC!KjLuWDB>KVnz)*%CmM*miTjBzqK|ls_&4zx@g4Cik+hm;K;kaq3F39)9pVGx zQ{n{PrCdQY5_b_T#3RI8#D~PE#81Sf*U)Dog>M9|<>z{01Mw))PrOYG5nmF&t))&P zohTrRi8F}h#QDVK#44hRc#7yD-X%tfpNMH|I3I}Zh(8buh!SEsaWC-<@gngGvDLNA zWg?x(A^u35Mx0Mn5jPXf#0KJVViWNW@g)&+9iKiCTNAqy^NBwa_Ysc}?-Ab;bLu!w z#2&=H#Gi>W;&Ng&aRYHX(MF6AGp}b1h~>n3qLcWHm~#WG7BQFDi`bvYAx|N$wQn+tRZeA+K9)AZX&Lp`A;MhX~Y8JIHH7jp7@mbp4jy!`b8W@6cHmV%p7|pTrKt?}&YgBZ=dQQ;6lnMZ{&q&BO-cdEzbNQ(`mmGqLS0 z%wOU(;(X$A;%eeP;`j#6U19}s3vmz8PCP?=LQD|bf4)Gl^?M~WBEFwyX z)x^!j)5HtJ%f#!1cNgPJ?07fV1>$XD$7ZfgL@seGaSm}6aV;@U9J(&XJCQhp=pqJ* z&xzQ3IG2eXh&_lyi6z9T#OcI^L=|xpv61+Nn0GI8l_({yAX-#t+#6sd2;%uUkSWogDrV~Nv*w zi0=vS0rn-fC6b8)i50{R#NUZ$iI<3eVw9NO%K1keL|jJPMm$11MZ8RmJVZa*SPzK3 zi7et4;y&V^#Cyc&#Mi_Q4|8mZTw*D4KCzzo2eFZOh4_g0oQV57+lfnv2I5)b6Jm!A z%xz-(cKS^$B#tGDiHnH{aV_x(@eWb`|LnbYm=x8bcimm7n;d4kXQpR*BE!U<89+oN zXOJx52#Dk?86<;qxXAwz~C4Z}W+r97g&gDD4=iKwW@AKS0 z*5b#ms$IKwg|6=2)w9%Uxd_TKt0gbQ!<$eQ(xD@)fVHp>&O-L(94k;9K7qBc4R*ti za0?#8OVC$vOo0aqL1}0Nt)MIPg3&MownNsHR!a)hg~9M8EP;J+8SJY#zTjK91&$2b z2@_yCtb=`U3~s?6@DjpSbKF38=m+cJ4wP8Kc?9jCFU*F`a2QU*1IV+M^9?FM1851u zU@@$NgK!6OuA}YH0GdD-SOy2-1U!cv>$!h|D$sEQ?S`H3BbO`#j~hT$*QSf7y;v94Qz*>;XGV}hwvP7Y~h^;L_#4b4i%s&w1c1A|}!d;zmy0c5})xB<^W-NE%9a)BG-Aq9#ZKhayk{%E16w2rFPa?195@2JXW%2-(SUPzZ`cU1$m&pci}sBj9uR0_MPc zSPrM)S9k>0U0mZJ5W*o1N?gbzR1b_?D;XUXI10ZxS*HuV>G$;?X;5}#t z?V&4t45MH&%!GNc3|7Nd*aJu41pErO;Ssz9eINY<0T2QakN}0CD3pSx&g>f(ozJ-;r4tB#)I1f)iImmS!f*>4{U_YFJ>u?9E z9^xJW(xD0DKg_uVOTh6X%RxbS1FAx2SOTkHJsgBna0za}eW-eb?S?PlBwT=-@C=Nf zxQ>GxVxa)M0p*|r^n#Hv0p`F8*bE2Z4BUj5ko73nWyl9_Kv}2;??7|-0D8k<7!8x) zOZXbTgVnGRet@6h9z2HVW4srDl2922!dI{eHo$h+3+LfFWIxU^0EM9}yaSD(BXoxm zFb<}|Oqd6Y;Q$ktD8&=9_c zHLxE}!*B2a0#5Q84DpZxuR$@W4DUfJ=m1@zFN}p{uo_Olc?dkk{)0kL9qK__=nI2j zD2#wa1idn6R@9WUqTD$0s~+w%!Lg20S>?!xC^!m zT=O6RLLdQBp)^#5cc2-xfqw84oP>vhRRSI8bB`?2BTpz%z|%VF>HiAZ~|OcI9K5fC<|4g zK6HYWfH$EW)PQ%P3ABNZ&<)1J7cdJJ zz;ajv+u=m3s|{fCNZ|H=z=Af9KI4h!HST!s7a5<;(YZGfUs z7HUH}w1N+yAB==ma2l?FdV}?W2VRG=Py^~gD`*e>VLU8_^>7rs z0utd3s059m1N4AVFcsFp9(WAdZ*d+%en^EfPzjpAa5%{)=S%r_C+vqK5OJGx5Q;!4 zs0_8CDV%@{@C3BqIhP;`>Occ%58YuTOoG`k50=3i*aUmvM>qpl;Q^?3*uM|}A&?I| z@H&)*`p_1-!Vs7Ub748Gfz7ZBeum5N8~hGWzOH7E!5p$~ix%V7)bfgj-%+y+BcEIGgh`5+PA zgmUl})Px4m3_gTuFb@{Na@YmO;ROV0jFE@JP#u~>PZ$q#U@>fgGjIcP@ZEkTs1Eg^ z8FYg_@Ci(ZIj|U3!BO}P?!q$&VtnaDm;(nO#HLuHAsz}qF=z>$p$B{hKf((rZ&&zM zR^b~^#WESrK$a|uB_||7I!uS{@FSdpsI0UV#=~0psup8$`S&=SgPTw&n__7Ub73Ex zhy2+U%iGWt=D`sN&Osl;Tktm2ht|*qK7vs&0ltQHuoeD*@SL;{%E8;v5Wa&`5Soi= z@EX*BcVQ5WhncVhw!>jK1wpwLOC%J4qEG>9Km+Iq{a_5tfn~4(egPZfFcP6MG=M%Z z1&Zb6T!1#v2NuE(I0U!h0lWZPfMN-Q6nG7Ofrf#KWh`t0g|Qu3AOPAyUx;zCui-d6 zgWw?A4h5hH6o(p64;n!WXa^HuEnJ41a35lVSq_T9ThI*p!&q1d8(<$ihO8m<6C}YK zP##*tXD}YVfn{(2&cHRe4G-WcxLj-}6oYcm4!S^Z7zATsD$Ig;un00>BW#C5a0)KK z19%2m81E7ZDNq^Gp*ws6U&9i(4GQC5vO#C~6vn|+_!<_#Ww;6VAd6eEPJo5+G*zKC420qEIZT0_a277ZLnzC*np)5h+Cmq|fX%QUeu7hQ5sXOo zEkr?aC=0cqHFSdkFcKz12CRo4-~gO}a}X27{)Kul6z0GZSPMVEVK@WV;2C6(=6Hle zC&CF z9Fs5|=D>Vd3T*sj(RDpEp0sUbtOo4A{a_?ag|A=*Y=Zr88IlT82h@VbFczl6H?Rt}!UJ#>QY`Pld(Z_wg0V0Y*1%S9 z^SnepNPa*LmLQI>&%FcikXM3@Z=AOp6+Za4ww;0inj?M=m!3t}M|UWYPJ4Z6TQSOmx6 zG88SwwE^COI`BSpg%R*IEP&N;1Wv)Pa37vSmf~EeARG!oWvBsNpdSo@@$fCIg>6u< zgkmWHm7xy24{f0@41zD=JIH`Q3-5g_oQD>hWe{WLKDOkwOk(WVWTs5DI4xCpIPh(& z%TmW0W~plpx74#n^0jd^_qxfJ)z$)*HP(WbJ=Q{&E7oFsmIxk0jZ_+1qLue8`IM%X{7Q36oYKk? zue7x!DD5oCN(b(jJ6qC}u9gBycS}L#LrWp0kL7ixpQVU0&{9+xVJWVRvXoFpTS_Y9 zEM=7mmU7A@OL=9QrLr>J@|H5wQdOC4sirKjR96;SYADMrwUyLSz0RBEv=NBme$H`OB>~mrLA($(oT6~X|FuCe4sqFbWmPcIx1FcCq=b( zRrG<66($YFpX=k0Qd|>@v>0n){bhfTix?0yL z-L31Cp4RnBAL~Y?pLMe`z`8~G$huYe*t$&_Y~8MWV%?#9YTc;}v+iP0({5#?b&v9y zb+0nox=$Hr-LH(d9#AG)4=R(ahm^_I!^%|aab=qIXXQ)lNo9uhtTNkrPMKr9pnPq; zsLZuqQs!ANE8kjwQNFWYRTf)+<%{`i$|~z`${OoUWv%s=vflc;veEib*<^jBY_UG! zOVX#x57uYO9_w>upY^5r<%@dQs;Eb-s(RF_smH9k`m@zg&sc5hIjdd0V9lalwq{kY zS+l7(t=ZLE)|~3^*4*k{YaTu@$*VrJ2B=T0f$B4>Lw#v=s)`b%s!FhGC?Tp%301Qw zVQMzTt!7uk)tpL%npcTb1C?mip~R>mNHFnye(K1(hVVs8UOP zQ+Zo0uGCXYDlOGgN-MRj(poL2v{9=lZPlvE2WmZ~quM~}q`s$gRvRf@)W%9zwVBdG zZLfT&c2Ih%9hF{c52c^lQyHN4Qa)09D}&U2%E#&eWw1I(`9%F#`BeQx8LAFdhN&Z! z;p!-5ggRCkrA|^tt5cLQ>QrT{I$asB&QvC-vz3YJ9A&aPPnn`FP^PMjlxgZ>s+*N>)UC<_b-S`qJ*0f69#OtmPby2)Q_6DnjFO?A zSJtY(DeKf5%6j#tvO&G2Y*ue8Th#l?R`r3hO?{+nSN~9Us85t1)aS}s^@VaywW{Y; zMZKVAQ7@|5)Jtl1^;b2gdQHux-ci{bYF_n$8lXN@1Jy@ri28>bsy>c zpQ~}|3pGKtXo;$-c~nhHQSDkmHH%hQ&88JqvunlG99nrbr&d|btyNR=Xm6`dt&SR` z)m4MFdTNOFj_T6V)o|@yHAZWo#%c}K1g(*prZrIuXf4$@wRUPTt%F)h>!_B~x~S!~ zZfZrXhgwzpP_3@@R%>Ye)Y@8q^=)mST2~vSHqbs*8)+le_qEY#6K$N@TKimWt4&Zl zX%p4X+7z{`_NCfgo2B;B=BWL&ZjTd>gU={b-cDqov7_q zCuxV&$=VV13+<>nO*^iBsr{@@*G{N&w3F)B+G%yJc1Hb1JFCvq&Z*yO=hgYz1$BXT zQC+BAQWt5L)g{^$b(!{yx?H=euF!r}S8CVPRoZWA1`klI)^4b4w43T$?UuSuyREL* zepfeWchrsAU3HUoPu;BDSGQ;n)UDb>b({7`-LCzi?$92qJGCe3F72thTYIMN(VnY& zwU_EX&7$qs6z!mfxHAM{24brD=M!rtA4ML(i|-^f=9~CumvpL@leH zq-E2Swd{I|mP1d~a_VVXZoQzEM=zx1)eCC@dJ!#9FRD58VwzJgt_A5Ov|zoI7NVEY zTzWYzR4=cE=@m7%UP+74D{GN@6)jq?s^!zGYq5GwEx%q{OVaCT$$DMQqrao2=;>Oj z{;rm$H_!^`?`dWAMp^~^eXX+ISbJM#UE_y6EGyuKMR%H+{U;L!YSi)F){l>65iV`c&;>{R?fd{-ySbK12Id zpQR1e=W4_BdD;m5TWzF1UmK+_(8lNswXynl+BkiYHeUZ;o1ib&ChAMHN%~T4vc63F zQeUA>*H>yY^bBpLzEPW{Z_;M#o3*d>E!rG?tM;|NO`EIl(!SC6Y4h~`+PC@vZN7d; zTcH1>E!2-`-{~i`MfyqYd;OHQSU;^T(a&g0_4C>?{ere!zo@OyFKa9HU$j;FZ(4?a zU0bc+(AMa`Yisp8+B*HNwn@LIZPp)ZTl7D)t@=}KoBl%Eu3PmTx}yJ}Yx+)I*LUfL zzFW8Hdvv?LSI?^N)3fXQ^_==4Jy1WaJM|-auzpl`=_m9s{iN>JPwV0O89hS3phxPz z=~4PkJzBq|$LP29eERQttbRw&uiw?<^m}@|eqT?}ALxntLp@1M!*|y2W@+w;6?XyYYse#VD?4HOlKbjB0vLqq?5ksG$cKZ|i|Z z9o=Qr)kBSXy4z@|hZ~Lb2&0J}X*APgj261bXr&i4TI+?4HhK}Gt^TIbUN2#MpqDW^ z>g9~CdU>OpUcu<0S2RA0OQ4dQW4H-pBY_?`zD}2N?78kBs^HAY*|(*jTK8YAn--8Ef^C z#(MoTV}m~0*r<;+HtC-moAvR=7JZ7bO`mRT*Jl_z^qIykeU`CDpKa{ZzcTjgbB#m# zx5iO@fpJ{_&N!hjGS2Etj0^g5gV zX5=w;7!G5n5p3)>!i>E}gt6aF-jTd zjIzcBqoQ%isA60(svB30TE=fiUE{jZ$hcv=Z`?9k8Mlo##_vXFcHl`R)j9JE0<16EtvA}q4d}q8cRu~rBD#L2qWGJ?shHBewXtu+K zVLM{jY^M#o?Xr=@_KT6#cFV|a``yT4du-&iS#7y(sx6N#yDhIR&=z2G;&2>k3$i8H zf^7wCA+|y`m#vsB%vQqYwpC9z^4VJ2Vr^|~akj23)!mk4 z`_Ptb>t*xU2D0==w&J#rZKZ5OY!z&uQo}G?CEEyFW!p$w7283A~*w)xy+BVn}`$n5;-)htC+iW@Q+ikh*yKH&vyKRB? zeKv>vur1jBqb<^Y)D~?&V~ew2u*KW2+Y;=*+Y;^fY)SU}wq*MQTdMt$?REPT+Z*<$ zw&M2Zwi0%$y|Ue~SFzjeZ`rfhtJ|~M>)5l|8``tmo7i*M+t_p3JKA&EyW4Zyd)f2Y z2iWu42is%ppV;%+KefMUA8s#ZA8jvgA8#*dpJp#-pGhs>*sI#Vv)8aMw!dv(OKt1y zb?rOt>Gs|BckPGljqFG4@7sT}H@2T<4QK34?PqyDXfD6#V7_tT$r^q?;cIXD%%3Ls z({$3e-l<>v)8Aj>AitsTRSR=%CrRIU(?!x!Z@NZW?@hmxL|Y#DQ}kWmvZ?&af|%Nx z-$M|or$23d!SDLCH`j5{pIRpHT~Z&jtOLpEP2YJKljB|6GLoppobQ_X*wH^Q-(Tzd zxHn@{ygf9MBx;`EFPrU8-;u=H%%^um+Urke{OJZs)OVjG>ihdWZf=p-UYX?f#*Af5 z!hCaktJ4QFy~%twK_m}Jw6CZ?$zH2WnW(v2c}A}+HERi+!*`|LWY+wyH+8x0+i!B& zIn39}JN55x(c!Q8PS(4unTjtn*Zbw+rIRt`J|2B`Rb<9x{+S8wyqm!BkRHutLN=QMwr ztXZba)}gLvZ~Y(Emw$tAPgfv`R>`%=sgJtx`-k3RNFHml>=Dau^tK@DM&EYGsof|O zeK7i!Q{{TCiS(7XzOtKqJtU`Q)(5X%Vk@;&GJn>}(uON_H*YeZ1Q7eJ^k!e1-2tvA<^bqnK7yh$$Gi?S--vcV*=gsf!} zWi|h@#1xLE?%pyvpIlqft-fnh71C1gd~cI7yy-pC25;KEm)9ikev|FHT9&_P;+>ko zd}6sc&D>tm<8uFc*j}-JyBFd26TQdUX4>-CGr)YF zLG*?^KV%O@6!%U2#6MqVZON>qjq38(4!M)$Z&m)_t3_TTj#DPue3c~n-n!E_Un)u5 z8JSzO%v;NBu9%|r&6&E#Tb4l*N5oZ*&gb4T^ZXSxZ(^#-?}YK!W&WG_#9hYUe~mX^ znZ&VSUggEH@!BrmD~>$#KBjDew*@0eOT0;5?{Dy!ytpdo;4C}uooe>3xD%AMG}`U! zA#)w#>~2Guy4l>eb8q>|%wso;x2y|O#ZlbXpDd+(SAO#@W4d>~(|df!+Hd}39+P5= z3hnhxm0Nj$GO@Q#`qM>{Sldm1*?p4O3orar3-4q6r+2;P(-k7g*Lb;(&x-i2T>F?$ ztnEjVSVv|{)ZNeB<`z1`vi%L;tI$S{d$EMP7Z|{NpL^$%eI@HV@`dkOVmsj5m-1CS zmNM}wF7JkORP>ddr{){i%sRohFH6!}{ZY+%sZr z@kbajw$1E=;-o)c*}wE1P1XEU-y@0F1$mb3piJBe4*bb?=EFP1xI&v4_ zFVT3fwrBY{39;Vd$9-)vpI;D1?9U`|COb>}`YQToMr!Ub+nhiu=}iSl<-MsRsh2mU zvyK7Y)ZzqVEWN2aNz^x)spq_9<`WEJiI9`Nw1c%-e=yfE{6pWdA=4V>6K&p268)F& zl&|bHk|=x3IzI8%;yCS_nn2p>ElVxP-|O(Eg_Mcj*gz6@(&qCC;uXw%Ns?6_OT2#7mr9bv)Y>G`sz&Bi?>#cjww{7o}M1AJ3 zdy1*$tMb#iTtE4%6aLpX^S7PExgh(>xbJ%nkbSk0*HF>Yd-V91-dfBt;iBem{Anpk ztXJN@6nNk}HcFAikyw=^=Ic)q*F<^E-sr!|XZG&Nd^aZg<@JaEwe@mO%NEGjZh7Q9 ziMRZDM<%ZV@;ymrZCS#7r#MsO5=H#iI(a`N%gp;-(bBWDR3!709dQhmkKr%(dS6N8 zK970iJDSXQa%1rFZT@GbE*BRs@>=2&Nu%sUy;QrT;#nJU&H&!dm5z9q)_(+7%gzV7}s*q_Gx z(-;0U+n*Nt(@KBJY;6%LqinqIj?FR8BK0SU9v@5+t^bT9?!x7-rkUR z=JLHuW_RZ1d!dns%%1%F_ule)mDw>~)bO?5JYGcWZX4 zdOu;Rc-4^W{rm3{>Sgt{pczTjXI|mNS+<5U(Sn`+^b2YCk7jRVcGZ(xxwtgdZ(J_hq%JEqfFcv%WX5VGkVQCpNk~Uo)-ReoV4CMRkrFs zU(LiFg}i#3S732}Z(e~#%9evsYTk1pz@M6v#MlqJ8H0 zbFnt_+9Y~XUK8bAxV%!!*KYZGDVMF3+qY+`kwjbS`jgq?Vr|11@2BQv+?@BjgvI`}&7ThYQ<>7f-nc=T=#3$P zj8F95TTJ$+d!%=dnQa;CU__la%^`_1O1|%2NttLtPN#2b1WDA_#b0LL!HeEC-?fS3 z)O^<_(i`08-1DwOp3~Wb7&+4X++RH>i9R*&^+J!EZO-gouR^e| zeYHuVE%*Ip=JVIP-}f4-NCRCC;DZ5q5wwag@mOSDw@I+&ayC z;*9#mpEA3Tkmva==KJdzpYHaptu;y1C$9&YwJNi=WOkm*TIBap=7|1Jem3{?8Im|o zCo*bZoFDSsl4n#Z~ZIS)*_j@C=E7u~{A&w#W-l`#E^~E0C!G07|+r%(t z{iN9z^GYN3k$HU)$-L%>bikj|8D+o9yF|`AzNw#6CicQ)l2}5%a>_fhTDtGkyvNLE zIc2Wb%BXsAZe{l;xommLL`!S?lYDjlh_a&IT4XX?zs6hEimRwN#!Hv+rClts$vf2? z-M_<|&Xe|fQzGpX%Z4&uU-qB#wAoVg(<5;X$`-U>Ec`j|RJkulf9bnUnp-~OjJd42 z7sQ#owG!Lxy*kPyujrXw@6CP|+aZ_8o=I6L#>tEMs*yxXm)+pm&a-9<%&S=zZ#u%% zT;BBR`ea*V`(E9adM)`YHvXE$_L?K}#dq)Kr`=-9u$d=0n@=jCC_Y*(jFP5n3PjZXocF5NM z{kzS-zr?gszIR0nNaCn7-)o3Z*+-V)$%KpM7G*ZyW2TC`e{*}qedqYHJP*>x*9U(s zlQq{Z=UZ<((qGq`+0;$UC)Q^6khpgKqwC#V-nS0(duQ?am3f4T{bAPf*DHi6x5EmTx;| z`_nR#7!R_ARLNV*rrO-|d((c>R&Tn>osoDInpKMT=)26dWspQqn&TA265rP4T)1XV z{jf9-yL-1+zGAOos%X_8OfBb~&wQ^Y=G*08)_k=WR~318^Rs_hvn`?pzfvYXb9+n@ z`$&H4mRVoPspdDMq89UWy@B4g$oC6-(tT&Q{9MKSR;%`J=C;YbD);^wmJr+a8%gx| ztB+^%cuDuxXC5yi$rjXm%Xgg0*BSGEL-dP$#d)`pZ@sNZVrn~o>PTvF-E8y6{v?-> z-&btUWa@Q)E#{dbdRKm%v4K0Zr8mr)GfRt``Hq}zmH4FGdkk%-Ok7!A&3$eDXVO_} z5y#&@Q_KI0%g$}<+wvv;w4Ef5XZgBn-bcu?(lJ){R$pGh#9rv(Pp|F+dAIrM`M&Pp zyI0O^zJpBt>)!uo%Kp9U=-I{BxLvc7*u z`bXFMubD5iT3%iD_m3CZ_d7@Msk3)~WVUBynK_^MtmQ6A+&BF*$$Sqh?u%nb`u4f` zDa7d{^IVYW2vfzKjJ&Io_fqoy%KSV>+)MpT633AFU7JX0pP5hen>EYN8RR>E^HUr# zpSj+t-uYybwV0p3i1|iSpGZ^vNgl-Z?v_o$vDo=6Iqibs9dA-^55Wd(twQU!^KwCVAZdYtlE= zERG?WUS0D)I-mJo{8mA;%`&z8l5cqmnPr3hX&gzk;MG&dO!qBYaR$F%;;p4=N8c;# zw4VHF3~yOOri%LHI$BUB*4vddQM7<0J}LTF*Z0{hUkhYPpW`cQ?@t3rqE&L+-egqH zyRVr&Bva9OzN1Md`PwM&pyWMLX7^HNnYc#CZxxDu>pOPeB8hXgwLfhkiO;0XF*c&F zWSKdpSCpCW^Td2_%=gtIk0H7DGfQ$SMk=YS zx2!lxd@lMONwlS$|8tAhC4JY^H79*NKH@aL9phbg7D@Dr+z#_u3bEcSXMOu^$0c9Q z?_Bk*tu0B+_bo~EpB%lj>Q`UQTm9*vKb6`(g>}UUc*GQs$5B-TB8u+s;w?BpXQ@lT!SKD6noW+tv&0-zqch+K`k7Pcvy#xHO zZ<$>uZ!lG~AhR~h_I0_%Zx(s47MZoB?m*vGnn#3a%a6Bx{U?u{+rRsIs4aH@V!c-e z^D9Su!oc@_{v@vvJOAMKZAzO{50bii(`iyKZ<6eI$Th{y@QS^aKaz0sBnk%rFuYsZd*&FiNCAq{u+8evif7{&|nyf+Y6uO45DC&+s?z{bh@s>YwMr zDu0sC(*@S_-AiqL<$*?}~^in(6%zD|7eXr2oqO{7KQ4y7CO ziPXJ}cc7xp<~s+Gtl{*o z_uJB({uJs@(f*X+Plf!c1nCp5y^Kg9iI&#!rzV-CSNDe8%2#jOt801n)PK!7{fh0NdCthS{j2xF|Jywz`$hKEzq*J19qahty}*-k{_9X?SBw9- z9+F3n+|&Pi4E@LbA^S!4)xWxj{^$1ZzoYg4j&=NR5B8zf5$pr{k}(@ z-Lft6Uhma+c=EGwIchhvyHeSLSHFkNY}vnmN1NGEvL;rwT&Q8bBi%|8??^K%>zLp7 zPQ9ByUFJDMF>a|qbzgee)%P92V}I(y65_K0`8jJLer9ufEpxp;u&kIW%O3luRtxog zreIDTRoh&mR+6tRjY(qJK_u~sL$)|y*(}Ny{vR$OYmq6l^$zy<+A@M9`fr9mJ@-#l zQ+%Jv{ES4+PWGQU!dbLHj`x;raTfLU zUw(fwdq|8LFG!i#BDoH^Y-W9Ruh@UB&r;l1X8sC_7{~n|YRP0ox9r`#EGtF{xcn)z z^~&v)Jt=!v_PAWH?3a!ueeE;*{`~v@|9ziX3*@-@hu%GmfZyoaa9`gw| zF}`>vQ^gf-%Lw1M>kBCpQ_bHe6m8*WmjAq7$)^y58>oNw-zY{2h+nnHN13QEi6q)w zge3OIYSQR7X6s*_O3g34 z^_gS4Mf>EdhJ3zOw%I&a#WMqP+cuZ9ShNmi`?S`+V@N(#Ca=o!cMRn(AIkIN)$_?; zK9s+AD3{Hw=FFCmXP!LY%yUru_M&XF-bS^!JDOW$p3~y{Jnsly?{gE`m@3Nt)h)=Z zzJGK(GFw*mb7r-eTb|(E%FLF{YzaBvKeM+oTQ;+`nLRF!Q}bI@adetxBAMSsi&V3% z`sY>3{1pXpPXGOH&i>U){QYk-&1Zte6~uhnL~NV+bg)SByp-1$S!O=}EBeZO{#PXP z`SZM8%%j+Rt|Qo+zUk?^9yIRFU6r@214+C>eCkh`Ei2bnrjKg*&b#beq-EaJz@G~C z_g#T2l0?mKlV;ARDV7E#as6xUPu)qPEkj7+`ZvX&eq=kuUR~-hJ4_O_%o^ak#ts{( z{`u^>+{(=QYC7|Y*M)EVDYO1t!c@`s>-;IR=XK3z=EW)F1Tr)Zn$}#9{BUT zJ@JIRz45ns`{D(8`{RXq2jZo92jNwD2jh&qL-6XnL-Crt!&%F^yd%kLy}p!p6#04H zG5AH^ao7?t9$Nz@;@km~ao&KbI3Qpeb_7hv$pJHQ;egq=V!#~SIAAVr5-<;s515Z9 z1T4f80~X;a0gLg}fTgVc3(Pp-faUmXz)E~6AOl|xSc9(wti!hhHsId_HsLz~TkxZR zZTOFX9asz8iS@wU*a+NjxivA_%XjlfH|c;FRWBJe6MA9xK{2)vG~1>VHf18?IRfp>7tztFWq~nxd0;GF5g3Pm2u#2`1C#LHKo8y* zn2PrY7QhDr3*m!-=HDYd8dwA$3oMFn1Qx?L154m1fu->Cz%tnGD2KB+D&VY+N;sRN z3eN7RigP%sUH9U;K`vKTdZH z#0?ySa6`vn+|)4yH**Zd%^kyWJI6@e-Z2V);248DIL6_Qj`6swVf9{x%r#Keksg6Z>reiUlZ*=U%n;iS`X2(Ii#c>$# zbR5CE97pj1$8mhnaRUG3IE9Zo&frsybNIC50>0?DgfBU+;NKiq@pZ>Fe8X`a-*nu> z_Z+wJea9XA#BmQlbv(eD^AXmakFnu=igP)i zaFjC}j&|n2G0t2#pED0ma0cK)PV?{GzUBo#}Y8 z)BKCQQ=AR)RA(dng|ji9=4^_;bT-G+oh|VUXKOsu*%r@ow#Vz79q@W*C%nPg1#fhA z!`qxa@OEcUyxZ9u?{W6Ud!7C9KIcHZ-#G{$aGJjwaKt$T|KuEsPdSI<)6S9jymJ)( z)j0-VbB@Eeoa6Cr=S2L#IT=56PQ{O$)9`cWbo|0O6TfuM#zxQ_Yzvx;vjxq=d4uNT zfS`pqGH4Nw4_b^9f|laMpyjww&`SJfPzEj*v<4RsT8B#nZNTM&HsK0ETX4moZMa&{ z4*Y)5PTVeNH~uhaFMFpa<`p?;Ke;#N%ndq7?(22`pu?2(^Ljwg5y}T*_EgYOJTB-s z-Vt;HXA3@svj?BSIfBpOqQMvNo57cG>EJ84Oz>4)C-@q!8+;wt3%-e)1mDI@gYV#G z!S`^R;0L&E@FUzg_%ZGh{1o>OevStOzr>#eTUCBFD40JSW*Hf*q;ElmSczbXN-Vq#%_XNA~-eB`L(DnsK;lsf(_{ZQ_ z{Bv*|J`tRN&ju&qE5ROoGdLCB4laNn1{cDA1Q*7RkRmuNq$qZW6vO#LO5nJVQaB-` z3@#p04wne2fXjze!f%CC!8Jpw;#wiqae7Ei+&H8*ZW2-lHw~$Wn}wv~Rw4Cq>yU=H zO-Li$A*34~R?^v2Ud`r%5Z)9r81D`lg7=0D#rs2s<3k}M@v)Fm_;|<|{By`S zd?I8#J|8j>UkI6uFNaLUS3;)YUqYtiTOl*??U32{LC75ZFk~)%5i$?I44ID&*Fv1# zwFu{MEyjVarP$$Gj-9TRILMWOgI#NIq-!0Ha&5rzu1z?>wFRfSw&4P<9k`HdCobyR zjo)9vadFo{T*7r2mvbG#|2)x-9g}1n3@DHw7yweqj_qh`AF;@~k?(*Ohu2g)|RREuN6~b3t=HEL1)l~#v za}~wEyNcmEt`hjJs}#QHDueI4%HapD3izR`5`N^Wf}gvp;uo&!_@%2RwuIKkMra*u z3$2Ikq3JkBXnmYBv?0zF+6d}0Yg$~BgLWf`_Y$#U4hGQ*k zB-X=5VIyn|wuOzu*~7-;9AOi2-mu9yAZ#iQ44Z}>VbifQY$gs0n~j6R=HQU9x!4sp z4~K@$#|dEzabnmaoD{YgCxSYlUsV^};sc zcfz*d^ssHXQP>Xre%MajIBYj=61EpN4cm{Kg&oAr!w%yXVMlPwu%mcD*l|2C>;xVb zb_$OUJA=oCox`7pUBDBWr zd9cGBfSqm!4si!zmpcSUxkGWZ+l^z~5jepeg%jN|ILRH0lihLH<4(XS?j&5o?ZGA8 zskoxM0Iu#Xglo79y;aBj!@T>T6_%(by{5t+Q z{3bpdej8s5zk@G@-@{kKAK+iZAK`1^kMZ5`r?_~;b6g_gB`z6Z83_ctYi;qOa;bRdA_(Vh!J{jS`ry^4E>4*aOTtp#!F`_X3C87wv7GeGt>u(Xo z@U4gv_;y4o{Ch+h{5YZ)_mx^>CiZ zbeuP`J`Rg)h@&DK;poW5I3}_w&KKDnCq=fz$&szGC$cRr7}*{ditK=2i|mA7kL-d= zMs~xcB75M{kv(yR$lkbOWM5nOB62z28o3hhiOj%zBiGJ0}V*avx)KUC))Nx!l z>I7~Ob&9DCG3Qa#8Qdl69PS!*0e>2G2@j3Bf=5JM#UrDx;R#XK@i$R7@w}+pcz)C! zygce2UJ>;GuZ((x*GE0Z2cw?iLs8H1k*JsWrzoq9qcMsM>}vzbP!IB4#8>Bq4@P^H!cz#flEY3;TF*`cvy5S9v&Ts zM@1*ttd@n*N%*^H4_*|VO8N5W0(eDqA-poWFkTg1gsB^&i{eeu#qj3n5_n5=DSR-x z3_cWH4j+!L!2IW;E0NE8eKEQUB^NM%MJl=~`I6UHqN`K#3+B2JU6cGP=DdrpP5uq@ z8AWs*d?UIZB{wl=VstwBHs(x>u1~&$xz0p4B;Uha!=oFKA7HNG(T(w==%$qXfjM)d zo0FeluHn%w$aD|wmxMIw3Tq$NGZV)pH4~rRt$H$Dr6Jo~W zi7^xL)R@WmitmW9H)@Viw|^F^llt zn8o;D%u;+LW;uQlvl73I$-vfpYp|Yg9S+L30Vn6%gp1|df-B|QhU?|qft%#piM!_8 zjr-@@i^t{LkH5%w5UQk9~w6#y-Zj{7h!Rel>e((CB_StyC}IxT-Ta+&-&aQ*zbaF6_X@R9rh z_(XmOzLr0TW$)z=A>Z}-asE)U66eNxTm;S?7lrf0#o*|;SR4}zLqaTV}_xJr0U zTot@Ft}5OVR~_$;tBLo-)y7BS>fjS`_3(wbbbLLoKE4sx5UcTxaQ67d*csmx2gNtX zsqroG>+!AG>Nn!slFP=o$K~QX;5zZ0aNYPWxM6%Z{9b$y+%mo=?hxM__lWO{KaB5> zKZzfRhr|!UY1L@#FE?_=)&#{A7GD zeku-3n1&q*({W_NOdORk8>c4B!D$I|afO6=xLU$|Ts>hSZjrDEw@+A%J0>i}of4Mg zZV4-K_k;{QAYlzYmaq<=PS}8NCv3uZ61L!b3EObd#2vUq;!a#DaW^iTxEEJT+>fg# z9>jGM594}?NANp|M{#=Mar|!L33|0YrpFRb;Z})faO=c#xJ}{(rnXDGL~iT#hly9n zJrl3uUWwOm@5JlO(>L)ZxsTUF6K|7;CEmfq6Yt>>i4X9|#7E5e8Rnc#e2m8@KE=}$ zpX1qyFY(+&E2BxiN#u{(TNWnjctxTOuT0E>HzsDoI}>x@BZ;~2>BKzv*Tew)Akl#> zNkKSkQV0%83dIpgZtO{lz$r;l__d@MTs|olznc_?>nA1PR!K>?Pm%}kOiIPOk_zBM zNrmwBq{8?{QW1PNsVKgeR1CjJDuG`nmBP7`%iui8<#68Q3OFFS5)MqRl7+v|hMtK*2|nmB)QZJdx?2d5?1!-bR6ana=ZxMXreTs657u9@5z*Gq1S>nAtIO_E#U zrpc{wpX9c9KyrILFu4OBk=zN7Ozwg|OYVk8CHKIilY8PZ$-VK|!1XfldO%)hEIJS82EOR0~)N@<8U zq%^`iQySw#DNXT-l;-$KN=tk#r8WL7r7c!d+hZ-Y1J+YJ;gHlW*p=E1ho<(x?$n+* zIkh+Tr1r%rsr_+k>Oh>9ItUj?9gGX64#9;|hi0`}Uc+ox>Tp~%btHZ>brdd^ItCX{ z9fwP%j>n}_C*rcHlUb@9<_Jlhir-J2hWn>Z#{*Jl;*V2jr(bSdHe+<(PsTuf})HV1<>N zZo`jLci<?x1u_tXmE}3=^H%~i^KTJD*U>-^F8e*zL|C#-%7iKZ>QbE zkJBD7&lAjfTHq0Wy})B!$mQbYs2#5%X2nYxYNVx<=E&(Y&MGA=g z-_OkV+})`EzWRJJ^UV33d!Bh_Hu51@7x@URk9-U^MM5(u=g1^57MTLZBU8ba$Sz=O zWCoaw>^6hj1t2qQWG1*JG8-I^>;WDfnFAgZ*%LfAvKM$mZ!kw`cuDy&;BU*1179dV0i0HG5;(o$6mUkxso;JU zr-6+Xr-Q8(XMk-LXM&3=&H`6eoCB_|I2T+~aXz@V;sS7>;zBT6aS@oSxERb=Tmqg_ zaT)lPip#-ME3N>yR$K|5UU4-!{~E|>zu+2h?t<&UeHL5~es;l);OYf8fom4r3=S>0 z1%vF92j#q|u zrEOPE0>4r@1w5~EDtKk(F5p#_Gr+4WcLRS_ITO6MayEE> z3#-%Mq1A(6Q}r;|T%7^eS04o?t8-vq^%!_u^=9z+>T&R->SMr@tB(UOu08?$ZuLpv zCDo^ZKdU|!yuJD~@E6smgLhV+0X|)QCiqPCS>UtP=YapHJ{Nqi`h4(%>I=XRt1kpU zU2_q*XU)an+?q?k`8Ah;k($fF1vOWIl{Hs_>uatClQq|X{WaHt8)~ixH`Uw-Zmzir z+){HhI9_uLcy!He;Au6tgJ;y-0e-#aPVnrSyTNm6?g779b1!&q&3)i`H4lL2*E|S* zujXO!@|s7$YifQ6UR(1Rctgz-;Egp;fHrCqhMdO4opQCg8k73a4^~g4n<>NKH36~MccsfXghdxbTN2L zv;#ag+6f*PT?QT>?Ez1TE(gC7T?u|YdN_Dy^a${r=o;{w(Y4^Y(FAy2RDPf7{AddN zR&)b+Q8W#HCprk;8XX32i)O%|M~?#Uh~~hDqGRC0(aqp*qvPNs(PO|zqsM{UqbGn* zMNa~si=G1hF?uTaeDpN%h3M(vi_tT{KSj?3UyYsxz7{y#tKZ-U-HQ?*?0H?*UtD?*-dx?*o_AJ^*&qJ_s(a zeHdI(`v|zY_IKbBwU2>E);O_+IUP;Lh6p!S`#!;0Lt_fFIX>5ma>tfz#>^26w4D z1l+AI0`6W{0nV(e1ZUM%gR|?R;3w2mhSX~FWxULgiQnw6TUe^Pzs9O%Mu3HHnQFl0aWZen;bk*IfZVRd*%$bluh9Gj-R1&(>WBK38`= z_)^`C;GgSm0w>qs3{I)P1uUz-4cw#tc5u)7JHWa1cY^;@e>b>y{XO7k>hA^jslN~W zZ2be^{`C)nU#Nc=4A(yb#_E3u#_JygTk4+x7u7!rw%0!e9$x)=WCZ-A%PzXg7^{%!E|`gg#u)&C7VqyAm+>-Fz}XV$+D zexv?F@T~fez^m#%-aVxLSRb0nw}YGl)K9|vyvCjNQ{cR>@x%J5@ITP_&-z{9f28r# z3unNeqwxz1cf%ZBI1{uM&IS)yxCeOP!a3lV7VZfiv~Vx*e;3Lx*B!iYA8_8n&w-JJ z`+*A40v+GQQ%h^a^R^AW8haCHiKt2jDz23I0ihc;W+S$h7-UaG@Jxp)o==Ucf+aR zFB?t+?`b$4{8hsl-~$b3f)6&F1wPbp4)}1xx!_X`=Yvl-TmU}Pa3T0g!$sh$4Htv2 zHCzI|*>D;7*M`f%w;QeicQjlHzSD3u_+i5};O>prfuC=@9^Ak2M)1(ao4|&~o59A$ zTfnBq+rZ|=+re1l9bmliPOzo%Zm_lS9co~n%;)r*YpmUYWf@4-}ElHvFSbVTTSnSmo|L}{-Eh2@S3KN!Rwkr zvqI`eAY)Y1B=D}LDd0U#Q{n##q}4U;g8A1VQoE)Zm>&Zfd75^^{Dj6Qn`XlKgU0Pm zv*ACb@tLMQ;6Dw9)N@U9Fh8sD^`<@H`~~D2oA$!|CWwrpX>ZJ-=6x_Ljk`8~4)g5h z{lI;i_Xj`Q90otvd;s``<}ZTf%?E)M%?E=EnhycbX^w#3Y_0&$ZLTEF^FZd-=4$X; z%~9}z<~nd&^FmxN1R2Ad8!%r4QcBHD;ML7B@Q=+c;PcJ$JCZLnw}US>F9u&~?f^rv zPH=i`8Mr3a1D+mR4t^`P61*sOIQX5|5#YtKHQ@JSYr!jG3Gm8T61*yw02tzkv_N-vu9vzXv`Xe;@p9{6p~Z z_($L$;va)g#zV82spFHtr{Yt<_-(62{LCkXfMR0Pv%hFM|JS zISBqIS`Wq?(m1L05X_TXBk-qywCC0ea7Jq-Xth>@2ed}PFSgc!^II2!9jy)E($*%h zvo!{GwYGrETHC13>tZm|+Cj{tAd-sKPRv=5^;PRK%z2RZ)7pc16Ug}4x*YQs zjYqexgfp)3wARDnZ`Js<)+6Ab4sxQ@x(4$Z8ZT&F3uhb1NZXpg{B4k)*qX%r9gv>b zn!@~Dkab$?2F#ag{9bDs&Se@eZykjHeT_e89fp4e$jhd!8O&E|yt?%$I6u^QZEFtx zH5%_~9m9NY>t^uRt>fTtT8{xAY&{NqsPzQ!;ntJD-?p9tKGJ$B`1{t=z{gro2cKv? z1N=klnc!bq&jR0QJqMiJb}l%j?R>DT?E-LW+lAn6Z5M&Nw_OZ=s_hbR@3zap&$L|* z?$dS!*wS_-cv#!j;NrGxz$I3ax(9$`RR8MH*oGGd|bUfzep|4|J5nBD}scJCvwY{dQ--Pb} z{3P|*lodxD-TT;-RYx2Pu0G=U-Y2H?!FggzKb$9~48WN_?ai%|)UMM$d-^0box7fU zLtlXSQ9e}x)j@G+3B*rysUsnNXpy^w{0bR#JaiiLE$9m9C(zHKUqKH+k3;h4s{B~l z;hRFLA36$}y*Z=~h1Np-&?#GJQP4%ukDxoDKS6(o+Q&m`2+BfVg&K|ysWYH+p^Knb zpua+G$Ar{UXapLAj)itTmfzcj_JLl2-h_5SQ;rL%awrG=5?X&eQcP$EwBiJ2c<2o1 zTWLwB5_CHBJ!l8C_eqRf(4Elz(3ejRsRT3%ZGldJz6zZQy#&1h?RN^d z9ibZNFsK_^4gCQ68T2^xBJ@}2J!s8WC^sk%T?_pL+U3-cx)Qn`YW^zmKLKWH=vnA}D0C*@0$m7Q z2K^d(1bPyB9$NH`kXig0nKJq|qwEk2)< zj|Gz1+FeH}Uv`VRDc=vt`f2Kp>?4)hD?KIjo>j~hekGtdE071RQCK}SM; z(CNG#a2|9C^aS(~RB;n!18s!Hp&vni`U&3$?Sv-ZOdp0U=v?R-Xx&e#cjzn7Ht14l z*IPpB|c^T^uqnL%LggT-_pJw!Ovs75AZnQ zq2$xF??3TtB(H{4{59T8dV?~7{_zfRqkn!szsLO17ucU2%x^4)cY@vFY4eoo3C{(4 z!v})P!!_WF@FH+!con!RJOCaZ-U6-;Zv~GCZv&4EUj?oS-wJ*?d_TB0yd7K@eho~7 zcY^D~(+*K88J-LFg%1Q%;To_%ya?P7UIh+>2f%cA3%D`76&wt21Bb#_fy3cj!IAL& zU?#jB91Xt)9u?jRX2a9wD>TREg8A@);8?f@+!S5}ZVs;kw}c14@$eS#=1^1l|%}1>PDS0B;L#0e=?W z3f>;x2L3#J6?jMZR`3_$`@uWI+rhiSuYq@mcY?nRPpeewp731oSK$M}d&4#0ufvPL z`@*Zh`@;j^1K};;Z^B!_2gBRIhr(BZ4~K6Be;d9Zd?dUbd^G$T_`C2<@b}?qE0uaI zd^Y%ac-@zkdLq1KgHnG8Zv&qUZy#1_dwBE~UM&jOOl6b{p9nq^-VQz+ej&`f5WasM z@~-d;;Pc^IFJV_0zUvE<)t|zn7feP+@rGlks6U5ywv?%t!~1?bRlO3P`{`-w)$pZ_ z)6{F>xofAX*Td6Jn5O;`Zai%o+KgRGcTsPK_g%k>dMmtbU>Eh*@aTKHsJFwn{%aSt zBfMkAboEYn)~?gl|Aps+e+zeke-Dp>?}l#x{}J8+z89Xq+jO-vJPN)a-u}PS)d!UA zboF6)d;N6v&+zv6boEiVt7E$QS9n!^y81Xg04htJJ6(k=b>4LK2}_+nT}`s~1t(kk zUN~J%u@1a`x+=2{ykR<;mIvNA9Zkzc;4appo2RSk)}q^{s~Og!JEp5$twncFSG!q@ z?wzi7w-!AxUCp!>Jv?2_vSz(9UCp*;y*gce(wg1c1x+GB?LAFC1E+iKi%hWd=vxc3b7IU7GW1AWeERWs1%tO576 zwsp)vlXKg$8S3-aw&gR_{?@j`XQ(e&+t$obVQX7rhO(?}sTt}3Yg>ATI?$SS#tijE zYcBXDYYTXgbp!an)?MJi)(&u0fF z&J4A{+VRa9s?wT&(F|2(?fcyss@l2~tg-gJWQK}bmx8s{zL(BWb=IX|y|wRUGt@%s zQt(h~$HOyJgLUa2W~fGM^tlMg5$v+I?Y660W^J3jtLnD4&DmA; zSljm6RrOli_Ssb}x3+fes#aKcU9+oNX>kmxR#{!(;np^AwYBY@UDXlRw)=KfM_St+ z+*Pfy9{tCz>dV&Fox7^F)}_1crq)?C;oVfidf|%Q)OzcMzwD-xR%6fZs?XZGe0PbJVK?XEUhi>}^X4OovxW~#Kc9o%T$uzaQ(v~C55tVfTWsfMi`;D|MC%}kZC z_60|+`QTAjBbc?iz?`)X%v+=2m~|q!$vPX{Y+VX&v2Fmzt-HXZtw+IQtQWvztsUTT zmiqEcb-XnTJi*!*JkgpDo@6zGCtF?MDb_mhE7mA@s&yjxRqJf(&nNOiQhuslH*&0?)Gc1<$tTgXdU{;5V%<@LX#hc%C&1o^PE9e#<%= zyui8?+-BVXUTEC~e%pE!yvTY1{EoE)yx3CfW~%R6v%pKNeZfnu`QT+%Bltb53%uM~ z2Y%lg1+TD91b<+i4PI$o3SMR10A6j~1^&=_6uicI0le1Q0bXbANX%3}vbNnYQ(aHL zo~drI2EZGw*M2!u{n*;}wfTN>;5-qs-IdffVWuF-kPaywQ4?`scy5n zz@J%Lz}u|@r_WM9w;I7atWoe6)~)|DOWkQb3f^U@y=SSrt?dWSQopozg7;YSzc@?% z%G!DGEOoE7u6CCCwRIwRpLHvEzqRPFS?U360Q`-075JdF{fJrWA!{f2u=VKBEcIJU z4bM`KSnI$?tyRa)QopmdUOP+u-a7EUS?G1wd^AfvZfz}}t)8&1O3hY(uvYzkwtCW9 z_4sVH-CFgB*=T*QdV02c+FJGOZ1s$_>do2eS?k1q%~sD@YWGj7KU()s*+V^Vz3}T# zsTZwnzx$N>lXcbOpHeSbTRuHU{n@$;eA!xc;2iaeH8(m(y=u)}Hb=c?ZR?(cCi&c+ zIqEOgHt-E=R_`43rZup9j(W>_?eID3uhi)r^|rMQ++od1%u(-H>%jlBwt#=Lwj}4M zzgt`S=BRhA9pFE#$tUvL^6anW-6)}_a_pHO=6yf@P1a~Y-0u0d$8f?3=abMr{NAVY zuQ1$0zHo(Zm+9ZxHX#e$#`2PCX}5bfdv2dW@=&XyYNcACwy6u$b?RF6Bj|eQ2IxlU zr|M>P3v?@V8}u{icIfBO9ndeJJE6OvyP;n~_dvgb?uC90-3Q$dJpla%dJuXDdKmgG z^a%7Q^gHPH&|}c!&=b%fpeLd2&{NRU&@<4p&~wlqq359&pckP(K`%jnhF*qVfnJ4P zgI3r&M|fu=*dhGv9zgLa2zalAGg{3Nso<~gBH zg+2}K3C)G}g8m2E8~P0N+0Z_r&q4b_`$3Gh-5I(YdLVRP=ue>+La&Bi4!s|GFZ9pQ2ceIkPkv(7Ctmo(^PhP6 z6Mz21|4uq+(!tO?=n!Z=6oJa23TOdT2~|PWPz@A?YN0x)9$E+;3N=8DP!rS)#h^IU z0<}VI&?2ZEIt*G2ErC`{T0Utdv7J6**?PU3&|A=7p|_zO&^yopQ>-ZmLSKZw1Z|o!Hsz5izn${fl;2Nz zV#?!F{ygQSDKA5>K(9@Cb;=jY_Aje1t1D|PYbcAC#md^sTFVwe?a-34#bt+=ttuNW z%arBIa%Cr%om6&O*{NkWmfcX+F?Gq*rO>*mYp3>4O-wQme9q zjuyV)k!4>wM$E4thtyD;bDuDrjuV9c0=Ta1*b@cc1dr7C{V5wy4yp8{pM$MsUqX`G zS|;YW#ycjhLXO*7_DLkT=}CeQOg{HaiU06#gw&UI`!4wL5Gt&mc0tD*0@n)uIw0OmP^Y13~ns@4YJCOWgmbGmBi4baY>n^yIQBRLYr5V zor@%L%2dI_blBE1$<<+HWmgJHoJW?;*Ji;ZHUCJ>-&kgHJ{j5MD|?CmSN3{ao2A4y zmi_)3$&bXA)3~v$0*U0tvP(7I4zAGrDO1JImvy~tEIS@4CEp9aJoSg*YRy@#IeSdI z2KnR0vZrp8l9v=$mtBK&aifh_AXQvlCb+I_U!;Y(Pej3WW#0rx%BpV*soi$D2plOB z+*mgKXA;k)V6Nu+Ab8v^z2L&>KfDVW zqQ(_vm;Dmi=nN_C*!2BhMTS2^N*h@zx9s;*lhkKIlhyx*_Eg(GF&Ekk`XAi(hUlL4 z9httdq12%&olkAa&l^l-M^Z!c`i6#7EHjkJs&ucg7aO)_s36p zDV49>Kx0Dj^%su*;HcK z7rCEJJ|=MK$tOno5~Skg$}>t*xq=$dd6?IeN{(gI`SAfl4^cAlmhRR}wmX$fkET;2 z`7TOvIHw$6i)>$AW9t{E#!GOA(n%cL;0llUvV$$prAIdK1(EWa(hlX7RV5spFIzDG z-#AK~j>p1$IedRM93GK6#!}AyX#MW?SCk5hlYEEchDjFqt39`FI zEzOLiM>eIh`SkiBFHWzQ(+lX68-`L0O&d}@>7&JvA5dMHG?Da-t*5pJ)Czp@#b#V= z*^*C<7+jrs3rC ziR6GZnL;<_tS-u5XQq`Rm8mi4r+s8F$dFc8#3}I8l^GhBRvh%#l^HGk2%UH;+rjq* z{dTAN=)eWMbaFt~N08l1HLcY5#W(o~)q zwIJ^+X*=2BqEA|x?xV5zGSEDpPc@JA_ouS{nUyt&j3T`WdYCpf(Xsm}4BN_5WON{% z%$X+IF4YmwCN>xLP+EvR?#vTs6;I>_Qhj;|iDy`#7-LUnDD5!E7(L98P}E>eM&>aI z=Tg~u(j6Ftm!w(iP|-^>eKb-Gz0?CMp}fph{4sH1knu-JAQ=FSPsc#(9Masw7>l%_ zWu(virLcO^#2im&2`~Mqw*{WWoFW^^yiT`cJYet1Wb<7;Fc=a?B$`}CCLFgpADb1Cx;c=bkByCj;kQnk+PDFelS zC_z1#=)jD9G8VUw^k-E2FvGKqFR84yi-XT9?a*h{Gp;VGf1jA0ElRp*6!*0eyJ~G; zCaDb+x=zLBw#S%{8ALiM1HDdcUu9v zEnD(g8b4WKJxa&gFs>Zm+F8~zZU{ZO1|!j|VxQ7RB+?0z&`HMz#zqDmk8bK~Cw5y~ zGhY?EDoI1f<(ZsV?MbtJcvP8H$^`ilHe=9+^hj%3+6NQ3bj9FUpeF?xJ6Mt=Q@I?o zdHaTuOg80KSkFLavqaUDWwYaQJ2IP6OW~T4z0dKqGMyjj8BHW+Xbg89L*> z_yZ0-sl53xS;hohBufl%*}Ri4$;^^eLROXk-lOYkM=p!CLZ0bGY!iJYW3tkn&K2Gq#K%V2nlp~~75Jcu59k#` zDIY_bT*}_3xWU|Ajmzmuq(xBRD?*uK0}O^pc9?p{YS*qANM%7S&7j51HZpRK(5F+n zn#2qJyM*kbcmg%DlwjDDw_xywXgz+Lb-WDO5l(SMwcaiS|V zq8Vo4Nl;xldMV>r7Nv%0$lG*@d2Ui3uB|nl8tMxMZfD;Zz3-s8@Z4V-FrBRvt2ZNj=hm?iy?il(!lo_h#E|MH1b<}5CB3nY#-A8t;!X6W`g*fz zy;<)_Q4dS#yGSDcy{A})#zRhJcSM+8cJ7%hGfCfAQo4i9mh6opPD>2&(My?}Mi7gN zAV3BOwH%QR1w``tOC7d|Lb5C+SSfie)D!2G;*l_IAfHa@qNPK%nFdqT3~<^&n>YJx z8gfdqS6p%8k`ouHD0ipb+9P&m0;&abJHGf1XBk?QmT}Ihrk+@Pdrc*>FgliD8s4JR zmH;yrYwzuCO$3?o%+gGLKvFb|=|G}6)jGt-1Y%jTdSz*QR&|e!EJ=-Q$PWZO@J&v8 zDNz!3OH$IV=4&k1SfO!&YA3jec=RY1%OE&q9Lr@8glebGSX*(VOA_Oz#|8}@DUr_ zoa!s!nOY7o`A9_VsUcU?qlZJUITBM7Hj%A~3}s1bleBe5iYO+%1a=K(lgVgfHa55; znP5ImZ%Wx?Bdq33W+=svacX0hRk#~Rj6L5FA~9TiGp@0k_RSEJ8j_Ad-5AN5c6Hlx z(AF}N&mz^%+ne8(5oRnmXI>{*kr)~?-OQQPbTsw^XZDbkv?qusf40rYHr>qVzRDqn z-fMTK5J{wL(bE!HTQF7;RO-h0yClO{rAK<<>Ycy%7~_;h|CQEbn;b`~Q8wZ@nD+l=w^pNy4|UU&$p^~y0>d|Xx z3F@q;bNDkWYxxB4^aC%Z#ffK`yWDDCL62Z06#j~I3Xw}!)+s8Tmm+4ML9XO@gUt0H z@MOPmG?F)`BqYLBMAa*qCRg$iz7EIuIXud!<6}urCzA~DEkn$ZMdKaPBNnUMRL3?n;9EL zb}mN@teP`J!HCTwvrA7g&IA=39t|?$>Hhv8N2e&w*mrher$Hk}EU!$TV_qL%U1<8b zSo8u*3(Q=bGz!rQ1lkS^Q6RxBtOS~Wam3ft+`jEwr! zYH5aUofwv`7-*iWI=WPQVkF`ClQx=5RV^s;H4kylkn*Iv=A?_?*a&9$v%`6_4+x93mlh5IRsdM`FwLh;6`b7}fJDoKX6^vtrYtVf(N9`AcOnAOo-VV)hZ;$OCfBwGt@EaSEns zD4odZkC0?t+u8;5qk5$792pw-4Wkl|Gr(u(-9X zzP7wdw+ksNUkK9}nnu{sNZ;tw(uthv%x;+1%#l%YJjUELl$zI+8;Q)*-qfmSON??c zT`adKGbAgF(g9XQ+0u=2=)Ngp`_oaFp|`z{i7MUCnw)cXiJ#AuUM`2WrQ=iLOOG~s zT@bL@r?r~rDtw5|TgRB)2~(b|x{5L^B|!^H<>s|_>V&kk!1gT>nOtipy{ywb#F?TO zB8w3tz$oCVc)?+ocK4~1==dZoQFv7=zIswejWI!{v=7JPOC`wCUB?8=+aW|pgaI&U z)BebmJsH}rx=eiD&D7yck!~!0wvHACqkgQoLW_!5vNo?dCgs6Vt`>RGiS!1%Kb_=Q zza(3?ixU3rtd(x+P3n$vc*Ny-6>*Iw|ZD;gjAFZNEaz9rzqRxcJw zF)-Dc6V6nhFT6BJpVezk!t^&5`3|R9;8x1)OWE^a7l{s@m!xDbW&RZScNz^|1GSCk ze`mK7h&8ddUOp-(&GcryXDQthSi-3oogH8O>FluMSy(b8^brlRC6+|eZejx&WIoz| zXO{jizO=17HHw5MCtD!lv9=mzU^W(6B#fc0F_1v;Cd)9Aw4)bst5}PhZ7e(F%eo$g zJ8~PG+)-3P8|gc4GUkdHqG_a0??TZV5p64cQn^GkN5mtV21xVu7&9SH9TdOklFR!RSjc zU~@i8Gu=#}!jVm{AEP2CYG4Sr96vsz*-|!*i=LSBNxO_$7_}_$Df=pEHx!f4z_(-2w8QFX1#i>DW zs;65F(k+Y*qs|DQ(zHQ#&o0+p7f2r}aHAxIt2dBd2;s_7NN2b+i+&R_w@lVcwo^)s zVsc$1gl9z=(&$I3PTGa|b!*aaf=OvlG)Mc$Ts}iKya{@xtx+2E@7mmwmE5>oFPlD| zWYG12cb})8@(oB8^k;iUyIM5ER|dwj(WKD39sx!KQTXX&;6tWw+wB+& z>~^#+x0ka|)FINQ?q!kM_-e(W*N7>^V z!@3^sk777>j0pbBq|lpUx2Z>X{5Te85Ro1w`%4ug^5PCk1yR;=w%#81`i<0C@d*pTEhWJA#f=9^tkeBLrQt&BL4*$~s-vf{x%Cqa#X2S6yrfqq8GOcYS6Ol~ptx zvsBWeO6dhYmKU|RoRR=WHO+HvZ>sbxjE@Z8Nsm!jJ(7UadC0k!5v?UCdmt~YvlI-Z z?=ZcPR8@fw|AMb7;zhW8gtnZ`q0QkI+wy$U`R?WU{u<9=1%m+7{1!il0}(fWEs12| zw@tLi#H;SntO8|n)}3WSzd6Q-%rM6iBJ-3H#WzY8I3VI9J`q+29hQyJgJlz{MUX=@ zVc#MVSt%n0v$XKN0pGB*L;lpVLn5zZ)LE%clhIz@Z^g? ztac10xLL~P2&bnTJr3Gty@b?UG0`vFMW-_}=^Q#1%8=w_VzUPs-xtcl%bv#Uq82}_ zoU_P)nG>0J^t=(^IQw>cMsYvNwmZ69Vl|Ptg{;RadsVZDqvMHdR?1y(Abe%ilFQk} z;F>m->qO}t{*NmD&K2dVZDj1fZG?E$lFk3tzeqt(&u2S|LK{T+#osGKqPc5t+UZX6PGh62SzMRSmKyK!Y2OQKHT%LAN#rbZ&s?U98P zL!42mU7XI@${$|4o2ZeBUlbCK9+y3;f4UJKL$@>fYCqb}4TW?vo5^MR|LeMO1EB4k z%U8zMCw-G(>FVrA=aP!-CyZy(%#|2^(bT5Fj_Tn~hEC-$yb)nJi$alw#kdybw+yu<5bL|GWTLN5Z&sb@&BdST zj^9reb@Swmpqo9a@UH;Wc^@YzhPkM z-+~M!$t5`_jL1}yCARqmneUF3`@5~`&LvX0&O{1LJa;(@&kMyeii2#XVDZWlzAJ|| zlj~KAT1puyyjw_Kh|;}^sf6Q-q`&)N9gRfFvyNomv6tJDdTlSS3A$qsxL3^|41}+m zAM}qs;6GX(^dAkTUtYc-;Ep{IA14Vx_sXh(dqqvqe`R^Z@1BoDDvIpB{N*DR1^%%I z+^Z`5>E|O=1@U1I_>UI2WB0q4R|Mn39>`yLMKq9Jc};=;nxKEq-~#@!2jZ)&js)DX z``wENgPvjbIqrxs(Qu%BDUGOQCbS}ysJpoVb*C(HcbyA@ju+bPvmG6Q*R|=>p1O$_ zS8q!0{Eg*${f)hJuW-CFS8#AYGK%s+SH#zZ1KhmR(0yEA^9^uI`1Lns&oA6ketpf_ z=eM$|gx`vCugiIU;g<62>mEM8HKp=U=^Gk6zi{1nozm+qZZ;^Mu;NOJ#I4jhVlV4Q zTQxHtIRb zU8%}s=gB_x^2JEeM9J2OyzE`E-eCBW&_^YHyKLR02MW*L$LXCZH<|5gXsmQ>`79>1 zEpqIPxfse)Sm#%+JP#$t^-+Ta(z15uPIHH?h$DSX#hIy`X-es}BHFur)N*}5V+&8T zPoEiik-la>gxHEx$dt0DpY~K<@0RU_zY#4Pxw4isvluGMg-r8ZW7$!Z<5_RHDNB<6 zf<$MP1r}RmTdB{qU*E^uCFa$X+|gq%+`(OI(q-D$R&Q4uFXBjp^`>WC0(QG;PG?X~ zVZWi59}_mq=sYL-KGl-XrI(dBWJQ&AL3yN_Gka7;II0)jcxNx9ck^mlOcta@C9JzY zSy5dPEfrA@8su^sqV+oL)~Qu!JNtv#H4O#5UJ9WbO8j0L`H`=LmZnwW73O7!{PGmFP%;~mm zE-9lPC3ls5{n6&+6iaJgXYxLXp3)+Us0AK$Z?u>2zHm85v`DvMxvy9FrJPyhFGs*= zTBTYM38wBz>sFMzU+arT^6&Q}2NC?-q#cQ5&F5M%QQT4qZtOWFR$m%k35bJKt1brm zl;~?9Iuo{ibFYMpw`~-EuFWLlS-pHZspxnmId69(PQ}e4z6f<2MoD-0$tiXfPK(5> z$8Np&#|zJ9w8SJ@qwQi&1Zg*pGJ!!KUdNM!TGQQ3!!!tf{0*XPlk_Orx`=^jcO{kkJvcA9bt ziXP2roXrsdcNTe=a}2Gs<}ew&+|4%%AX+L*Pm}X}<1d>m=c0wAMiAGCDIXy;1 z>4emuN)ZR_G^Ih8<<7!RLBa?YMo;;mhykb#FCt?PL{#`8uH9?7#Vip~(8@i5mFrnP zw}tx;eJlsva1*q5+*vw~@@j!r@oP=#g9j7@^-`Q%a~=WWC2d5y`alYfS(7k{>QP=V zA9^e;TsQLXey`4z6PFZQVXM;V+A##is8T$) zLY=A(gm7IYQS(`LB+aYSqvd)5t~FkZM$M*C9%D((Yd#upDyDt=$27z7cP7Q6HaVy- zdc?Y8sLktr3re=hF%JG&PZcGBHjY+KFY<|lr*-Mq;L)oBdr3l7{u@v3B!o&?&K>0a z;mMIW73myF;*LIzxB4mcI5f{;$;=CZIE%6!s0lj~ z_cL_IgUP6Z{F2VnXH<3lT2uvYg;4}jccvj*B?Z4h&cE^Ol|V2^iEs2g6n(g-*tpdf2vm0H3Q@eR!&uy$FWO?TCJ87y2xMdg= zlX*EqS0YxoLW53nq+u?F1^H-Pi=w4JmO#&@oyaI?6qxX5Y=KhsJa}x)6GbM8+w|1ZvOT57`M8pI_MTmbejkHEi-6 z7;)r)uFvpQD|ZT|9ynk$s_aDJ3BT-nMe+@L>MR=Z?0f~f^+cnV?bj6Ngr3qH0w3;b ztp!SpSv#os=N3DYt4|-$kv9o_6uqgQf0od#fpQ0u*J?!Sk8+AU)e4@ z(H>;Y`jNPz2}fM#^)MH4xd@|drnSu%!qr9Oz%ihh=;bKLcd}Aa$E&{U<*-Dp$7XxAB1f0SHDo@{?kn+99i$Y`(Zloln3J<2j+;QB+|8>%6Sl=g zy7u)I$f7zidiMCDA_glB{dlKV*ATfukso2j3c?9bln57dM>@eNw3kFn ztmCLnrFf;iAJ&x^l=Df3`}8QQrxGh0^IE8z!aN;BnSiqK>6DAv=8|P8X3lu+miOTz zU5W7_?lJpVY_SVj6#3Yiud@M zV;|kJOV*FuA$Lma$8LjGS_RKr+CG{X;G2gs$-x4zUd*LjOYz!Mxq>*%v|%%21F7Vo zG8QuHeVgem-Yg_5JEK|Y@$8kiYUg!?j9w!YRj*fsCh8$GAx&6C#@T3*qW&4Ny)RBB zlZipwvLqwIe>tmA9r1G4w7@l0+9n_50zr4|0r#p3fBL;hZiD`@2mD71 z+_C%JQ4I*hhdmG<0@$E?O@aTKpnp^Y0{*cF;zKnc;EvsOZ!bLxaalWB9j)p!S2Q^& zo3N83htN06*@U15^A7t!T>GzOgMyZy0c_?e=@g zN)E#+q6@_8`SjUHdviYz?K)kHuEfJqTy-}+&9?fZj@dk4BwAz74U)@O+2;1Z2;75u zZlZj?<8L5=LS1!bMKr*$EgVw&Onscg===z~FXcfTbj=J_wpnxr z9iy92doGbk+T5iHMqy+%J)$M=@G_~v6ao^H6S*j98&P}mOuoEz#}>H!$_mdUiKVf^W7 zIlxgpgXz(YB7rjB-0PNF?*J#*zGmav?a9`)Q|5&%rh`IOWPbSqhc}WE8NHL@=8P|u z6~m)W1&MKSE>U6oTNP|utMYD6<*Q_MD!Pc?oIg?Da(&2UUs-T+Ga(mPEtnPI1Z%(EgCGU5UMCrP)D%J3s@Z+ihKonYEdqDA#kKQ*Jsxy8@^K65l=n2 zp$S}VO~?}EZ}yYyB^-@KufmLz@7T`g#Cyq+iusk~)njsip?f!-xs{hgGrqQiQ_*Mj zn6_yq4ishOnU{9Xl6dh~Zhei8+ixBPt%$hf5*v4o+2gBr9v?){$Fx{cfALnkhGz?1 zCh96Y9$?l`wm;&uO=g2@TiV5@1vKWvc#zDvJAY+A$hMn@CDAu7yQdEc)T^e2SNDD^gWRB65<*m5LU?zWgsaiL9sfm<~^zQo(!JJQ=Z4T`?LMq%- z#gfCmY!`fmisf^D*A_D2+mFd1M_Vv_J9f@^xQl>vc7kIFvssr%EOYblZgkX(!bwaW zG~P7tB+xFkxFE>F=54I>Q9~{=SQL+Os9GQ{zW5Bgrhwbl8K^~6-2IiR3SAi-x;Bgk zYo03fwgLAgU#;c;<=#N>zfO*UH1iW zyi6R*?JP#(Q*Va#=JU}%}5V%?9$ME{d?jJIwQ2AEBFHFfz{bPGPC=f z%slUB$h2&a*ui1Np0M3C{PVI*-LCR_AU5{|Jira;@0e|EPIL_>OxvCvyu^L8ed*l# zJm{!P=g(10@E4KxUNVQss0#Ix^!lP?4(yeOn`OU#zppSH{fS@>yfsScxQ%K=$uR!7 zZMBM0RpZ<%@$?YfoH~n|iE6{=?n}(!mSUC4fb0L?JyUi3Zb$)%k!nIk+8Y}cPQTUS0)blLg*1p0M2Icg+0+}|y zs1x+XRzcn`a4J>&^)zR;D7JAP7h9#@=B>|y+=*I(vublvkqa*N7sRqgDaCct>i}Q0 z?$H6FSS`=?xoP>92q1k!r_Wi>pn)}@H_m4ZG}LXmR#z7JMR7Rp^vc!vrqOii=t?`Q zcJuP4UDKiu;6zjCU>1*s-1eTXn)&mi^W)j|6H8Sj%lx(pK4ST-4xlXp-YhIjEYD3A zWS-6bB+qlZgmsGwofkB8*(tN%&TRI+#Xe~^jjCv}(XMh!Q>PQ~Tf}jySTY$%TJjlS z@Pz@p?5sHw-%+912nTVuQYjK0$}Wn`&C0%gawh6a>NQ zo^Sc1@_8@mF43grq{Z19`2qwT@Y$7OsqOQi?Gl4IQu4Xj9B~tU8y;iwJK8K$cv`(Dz$ez zMi}M=X=3Zj+0XmX#r1eu$Qv8753yXeH2oClva!?{=UpsYwaMNW%RxEvPy4rzd^+g9 zOQon4nR~B3XSwDeuYD2Rz9VBe6MEHGo_*d6?Q-{7wCBy5JW{2bv2INAq!@KmKHuHB z!l0&X@qoO{ZX3sVjKnsZ=yg-_X>76lZO6x`S*0E@Kk>BYbrz44yJ;!qn_7fl9bPJDBx-on1 zcH8tZbc6J_G;+f^tDf7PotiJBrJPdSd@;pg#v`p=WU5TWFm|KDlt%q`Xk0gL9(u{s zYrxsKi3<>5yQ<>3MGfnpAd)bds?uWsx9 zYq&{%+r~U7Z|s~U;Kz+cIZyS7$jA0+e@0OPaxcqF!8%1#u5R9q)s5K8yI1!uT3sQh zTSiMvpWfMKeY?xnHuvh&>$f7_kP9FBsd;n7OI}gXq0wiexoaK!_&~}i9JtxgKBU=t zaq)UxHqS4S$xn>f55T)Dr!;I$1G^*&olT@pH2Rl8d?Tcn5^D#)WlL)|p?@yYZ0_Z~ z2>T|8HtJZHrZ(H^*S%>ijgUM3!4ah^+waBd6hF<@Jcynfq%qqQ1tTODyZxIAwi~9i zHu5c1jW>I*j7UW5CrB23Mt$LkD1#%3?Lpq(oyd(Yq?3|(_4^n8gz1i2AnARTbQi-K z(VZ;lBIii2HX1Q%b9YDRuk zir0*6nEFMj5W%#T9`=m!n5e$rX6}v25|*WA0aw>4QHo&13}`Z(7cNrth~`Y`Zf?hyE>rWRJ43{7vu9*5Apkgb_9Vi60b;M z+$DoB21K_jz**!*rE}u0Ma8$f+{40?fdQXJ(IyZVtfKh+%Ug-X>D4~nMA)s&L_!)qw6<2T9750H$N8}xUD8JWJiNJ>Fza;V(U1Krl)w*Ca<)-FsxXb zOh47?_x5E~-Skn%@5$!vpKRsea>8AU7h5y;FU_^6V$ZxgiI_eu&tlXV|kZWdNKJd>c~=PQW|#V@XjD? z2QhgITAjSOTwgMxOJkDJu3`TYOQ65O3yF3_$u@**A?u$b7 zcYINZ)fa_p4MZW_Koo%w5e=X~6s0~yJb6)QEg>%#+UiB&S^c@t+&~omT;S@@g=Z~@ zB9IHwMe^lBtiD{h)<6`(_2oj;mjY1)av`od#)2sPxzHLqlsm#KKDS3zR%R&y|!ztKF<|i$%_tG zVWAnlE??rJ<`oFu?T=iPzI?7#{%^A_&bQH1MG4H;5R5et+*ij&V=UtAhX)w zQRhYOSp1P|6+AC;ZS^9@>W^GojcZHHU8wULZ1%kAcB$4;ovM=%{Gi>Q-rzg2>Ax%| zm^=|IWVi;+?``KwyyxIdapFt04Q&ee#x84-=8{a`K17sz&0QjN<-J4j3FlLvA7^+7 z1dL}me@Qj<_4z#VYcpJ6mMLCDDn19UjLO0eW!~!L*oaq*0dI7&!=n4=}*m1TGwxh_}Ml!=( zcQ0_UrR5X4w|g$Ct(8H|iO0O-V4~*;Y8VZXj&8HBQa!!4L6*Q-e&cRaiD%|@-n;oF zc_a@=4&>oU{Y&rir)s{o@gq#wSR?2^l~wWw1#b0? zHqlvDaqhF(`4=w@SsJpHb8=so3GY zM-lnO&cTt)<`LV$R+;i8TzKvvx8(U1Ag#xu&}FgnE?O;t2NA?A$nheTxA;okPlg@b z*As_Vidf`}yu<2%lP!2lnOl+E&>qphQeaD0BqHZMe~HA)3v62ujLM#48e8v}3c_g_ zlFpU)K7xEHdsTCl-k9}u7Gvxdjc(8AC2V*Te2wpI1qtl41|l#IE{G3P4bF*;FbZB* z(4Go-&ewEtd5IttK%kH`&($f=Hb;pdU?~pdcym79RZkH{Gk2ztMUf!u6!WM;z~TgQ zT?+y^;x!$}u>^|6mB2Y4=rY{v_ND#1&i31Rik+&(<+64^fE1HMd&bP<8{i>i~h7*DKncJB`c{qN!Q57_fC!c|vN~v>!Lt zV!v)a_@SqH1kmr}^4AOEq~>J+=YYlBzm??N*u)|uF>5a|YH>7w?H>UX&DR=V3Rokk zzKgK3=wrk$uiZIU%S3vlJ-V&a)A^8virZBr&iUTxO$j=uUlLlfwb z+GV@`q4OL!81&IoUSICg(HIZTakydm3c>AV{;WAyVC|#13BNlnFRb!QfINC5-=XiO zQQdx?{C=62xaU{+_7H@6g@o{}DaC3BD&R^o5>$t9EKdBUzJ}<&WAf65uLTfaf#=eW zZjP9|L>pN$QV_6&i~qYF-)>BSI9%?3NZwaLl7p=NrAPg1XF57LUqI%W<0y+Yu^Z{X za|T1e`$yhK_bEg_Iou;>SB)PS$^3| zZ#3Qb^fPx}5N-AR2fdk)xl#8Mb3BylTVrC8Vicy`p93?h-eGf_$CdKaKlqg|bI9gZ zTG8C(I?eOEb1i;&l;Lqie$PZt6n?wB$KyG1X0k=@Gw}GC90$waCFv2~KKK1(Xd%OS zN0Eq@j||gs`}N_uFGOj!Nz3o1S5bwGVsDZwzc1{@6ANzrgR#ywr*Po$N=JOk?>uPl zF1vWHcb(?xF^S`2xvawI#Iz@?3Aqzz+ic}Y@dyD zA``Q6O->*wX)<8WeR4`p;sPg)tY_@@7-&qeo!_E!)?eOAOol|6!^d(1>p6O|S9#`7 zCkloA+_dy{Ykj;$TTTUjO$uuuBhIaECeM`bhE97Ia-J+l+yAbh87Wm41taf*@RC%P z@tX9s0H8@u7c0u0Q&vi%Ump@nnefxD+}aR>oZ!meUU{zeFnug5o6dAnGc>HW&uN3d z-niNhY!1%tl7Q(x$fs7@O!s{Y-@IgBt}f+dUPqhYC1IkpUz0ED&H9xRSB_Bf2~{t& zcy|4|?bgUEN@Yr#fK&Mta*_uue9I2=eu{n|#WT8dhG6!Ub~azct1U)1E+1aMxh)&8>b%Mj8b#l`}_JkBf@cart$R@f`_D$UM? zHydfEk}uNgm`guaTNs=y7O*OO4c>Da&Y=}0>JL5R{NxOhXY)hz#^#@i=IJo}o~$=H ztSd)R$&p{#A-JSxcQjO%w&drfGxo1{vm*~qO4QNWXX4l-Hjci`m*2q_=^OFujU>04K{>y?3X_fwt^aGOl?lBU>V)x2bR zi;5lIv{c2Lx)-l)>ux$++0fFmR4r+5Thwc~9ZhX5OM9DCXLr-mwieaZzI3tbZi%av zi`siz)Zr~lmUOOEC?00AJ-mCEGDck)YMy>essnXTDvLT}OU4A5j}4Cx_GY>_pv_h@QIx{Pl_dv1a5JG7gli*xvY8Yq5Ifpj zQ=A{dlBcocZck<`uYcY+*Rm-!(%$b}HC~y>$!|>R-(dHt7xIRx^UDV9Iet${>WnWk z`%u-iiLH$ET-~DEa}+_#NM>xq0N?G-DlBLHzZHh6l9Vz`kxM!fH-6+wzKm7eV);!Y zDYPLLj_k&blAq_!(>`9ovn*-TbwM{t7k>>p$yy&^0gdgUVT*|3I``ZFMsnw>TEGmvj@O7be_4UU@-!8+AAYHEZ_yJ$< z|0I!vP+!R2jN?sYy7Jz(Ob~cAViA)~K9ZmLOpMcrb#XP1uN>|-lc=3H?St>s z%vC0v6ZgOF3huMy{EG+^>e_bZJ$^(_w!P^17 z8M`UaootVWtjez;v4c8bQW^yf6El8Lcgsy)wz&kZSt zwqW-N#GuR9^Df`qp0f+j_Q)J2wZstI*Pqk-RG!UB4iGPw7-V9x8<27P{|po;tsXA^ z=bfURu)FA^1-I4N>1n3UVjxD%Hig>M&t%=6C)?dAj<)S4#H=oI1(D}hYuQ>K^Q-++ zh<4Qf!AD-&vIM2G`d0D!BI*C*?A@E%N|JT&^@%3h_Vn!85pTQ^LK2{EKpIKd?*2z1 zKwymlF%q=x`Sj=aJegH%Nzk+RIT1Fh%DS$)WM$>8a8a?mFBtiMk+0>r`ZVEWmy3>@ zG9;rNWTb4XWQ_RyrCzu?&En31W9V3J4Kpu?B?J`yh?iT437MJky6)p@v>QPf-$6rU z;yJ$0bmsQ3ae@+L)ncf?am7{dsTTZ`%>S#rG>S^>O;6;5-GaWZ_H{~cyOL%&bP zS1s+j&W&!cS?wM@MZSprX=?3OTSuNii3goKZvWqj#2y#kXABsQ<1Sb>(JeB6x5owOsw}x z7FReqv|nOB)TJvZFEQ`9x@;bQ>hpFiSUVrC8cHfWdYq$-_j$XzMdZH={{3E|cmEfK zXup3AMRvRVe&fwP+G!qZZ&(kW64VOa*>>-ax1qCFC{NDYvZA~O#_&9$^Mxa{8--4J ztuz)T=V~kw9a78$>&dJFWsG+tJUu%uegdiBe6yKha{%KZ0V8Pi3fev-#i#m(%8#ya zWKbN*y3^*?JnNdn0n%d6=7dMjeS3>LP$hc;A<4W_%Sk3IenEw?(2ONOvii@R?&I@ya&_hKJ2_Sh5MV`DwYZ-s+du#0g&Vbe0> zP@Uoe%r`K49<%+|=g%$=px!Zq{)<#x%QmP70%;A{ub7wpFs=> z11Dl0e#eB{xmSYfCtDOQ_lS;XIZ`VPo*?@!q` zN@TUb;1NL`^C(z-6Z}sy#NlF+Mmg#fU2I%i+g~3{WmJyv+`GaGS;GJk<2N?j^oSNq%6L=vz_Dh-BIOok(FDbK(8{w-QocBc#EQd zcz(R*_)^}`%#5Q?ao599=N9+t{(q|FhTXy`LX9}|0P~=u815(>OF}ayU&NUPoLPC$ z6PI3Hlwm|3K5x8Aphy!v`7|Xl)s z-Dv*D42?4R^!W~IGnzU^aG@jT=tnP>?_QR&C$1?_ z`DIOix@#X5C~cA6J6W^SRtYAF&R%+ZQns zU`su&#F59(Nbe=ei!r6^+B^fB5#jUut<#rSXL-!@5WCJ?)eEdrH$9yx7~i~ra2g({G# zY*&Yb@(#R`&0;b;dVHtG$Ocu?+wC^j64S6_&9st82ssopS57FVwd6Kl^{H1TtsK!_ z|72Y6IwvqVEbU)bK4`aV#FM__m=RRT98tIHopeH616LRJ-ZiU3$O#UYrZ*9EUZ<7+ zjcf;*AcbT3i1YsZvbFXZ%j4n6x?b-QH7~jERi;UabDhVSirZeSq4@@u=J*?Kg}T>4 zwC6-9qq%goyjOQYuUMILQ0{R@3!2$=GTJlATM0AEo_12x@G9=+S@E)>u0!?u12+b4 zTd)@gTKyvBeP5)hnp+JwEM|b?oFZ! z-ZlG7igK1TzinOuUAbpWL^8H;>%g@XL_`K%;-T71i}w;6`N~8;wDlPO)Lopxh-|5M zmDM0X+=(22Sp~HfL)T+l-J)_%o{JS%+8H z0cmJ5gP8~qY9GL-if85R&c=cO6Y^qTcjTeU5bW(PZ`;sTpB8pj=;55*?Lyybn3H>Mm^pVKLhTJYyEHQlgE6nNC3Z_Yvr7G71e z>qbf~{JI8yqjZ+6rMPS%wrAZH!cY$Vn&Z{;HBT}80r?I+IuR0}xHt7Pks)lw0BQljgrJ+O zI8KxD9(H>7aox(98btjMQf%M^y=L}luDj!iPTC9%5s&UU z4?jD5P909qXN&XR?AOJ+#qoH$m<~UVW~1@N;(asu)3gr@a-K|wz4>t3&Gx?2on7;S zjBXz->~#ko*bWl#AParWHIMHmyUp2~K#&Y`zgO$%)(XE{Yj5l0a2gpKEYyTzc|3s_ z5D4+Gr(R3GX>Rf&$#c42S3hz;=OUz6UDb}i$GL9oA;*k#RPS>W^^$dmhg0V zKK?ii`0!#7fu|)(No6JI3ko@|ux?|milI36f9XHqPJl-OpnESL*Dxu-#zsbzGuuBI z7m_nrTix57^VxvG`Zh%%-h(P&B9XRASag-zMkwMzy)Ai4z!`R#>Wz;!%K8RNu;_>? zD!QH2lR8h4L!DB3af%6wetBH;HF{s#I%w@@QMN{OljU>CSWqt;bMv68+#Lut>HCFe zB{v1vUF!mke4QL&g_pUsa3mK(fI+V_<5S_vc-(75dVJ;fq>Tf6C#bu6fo8R)J!TMw zK{RrNCg~VS8EWl^#?@Y9)fh`i0nP+%1{PEg&k{eLzBF(RZdSJr?>Kh9DY?P(qMsRWm|6OiKjyG|sYKCaP3$7( zzu26VCD@6_e9RLI29I1Ls|wgQu^LRsSYSnpdVynrEo3FJ$Us0VMH`kNBBBf_Z~0)5 zMd|uz$D&o|8RrZ!R1xx*G*%CRP|$wJHaknvxK4Gq`#kVpSC6@y=8YHiCq+!9OtQ8b`Z)(u%PD8EOzmKJ+`9y04lnxS!RX?osetpN;b0Ha@AXg7hi$i62ItS-`XvS` z{Gc9`t-@Uv-ER!@44h{4c=@pAicq38Ffe-$wciQ}BC)`cA!z7Ixv2FJq`H++oa@rM zmubaDw;NOqV*}>RY=`Bdl(ovl)U0f^SDBgN*FY2Kp1(LnWQ~gEhCiEypx>Mo7jM@P ziJl-(Ow%6?ph7O@i}`Pp;o_%*`1=NjidXCBT_b$MQ_?8EfR$=ruOB)QvCQ>B*K4=! zV`-NsZzAt5LheU4eZTd*a*-P21IuL9@?Lo=a%|=s?3Dg0m{*(SGl?yMMMxO5)|yAB zXQbV8s`*3g>>Qs=V|a4IGwPCFn9h;h(C2QH*@Zqf1gEYbWfz1;1kE60pP$aE@U6dO z{T04heyf`y^@Y`Y)Lb=3gMP!)i$~}DK$C5oEB`&3H`mab4c6ZMS%dP-ra}L2NG@oO z+s170=A^l6U}S!2)*Rf;Kbv2gUz@MZee=+qH;>J~ntyO3XtsCF^rYFYn^^-ndi~hU z`WFq_&vP8_-8Az-Grw)-zcllIHuJM){s3DEyTE4dUd+EW^M5ygG@qJp%_l>!XZS&t zSM&`Pb3oT$QT)D>x)QDfF_n~Z+=)i2qr5>kk`3NGTn|uw{?cGs4qMkjhO#om8+0I1 z^hg}q-gowts@b-2wul2o!(3LWpz@)h8|4)vXV!c!V_+CY{x#&|60I|F*tu#+-DEGG zYlC}=Zx`mlDj#svz9clkuM12EV7iqT7}|>BYPre`&%SKFIZjdiPFE5`P=SRg1-Rzw zO&HnQjTk?wpJ?TsbzrBP?fTyg?ebo(bTXqV&abWQF{ohet-K7!K@*JRMrYZ46Rr=- zubJqZrnkLrhCF2Wq^D+A|K@cZLHzhHHzTgdFhDRv-d^)FS;7@c_FC``CQFe=;vy(C zMD_k2lc+5!Wi7M=V|-kqW?ADxYJ-ng$e&V4rm2)kyNpNOv0IlLrt{3fC!;sUw>dkQ9L)VcpCTj4-4Kq6DN{-sFlz*J`u| zY9C}0b-5m{PCJmzyw!1l8NJ@ zi-*)^hx1I7Blf|_d})OZSpk(j#QcjbVi{cO`hyWVpAu9`%r*N>3y3u>8A(v)$aL?2 zOw$&l87zKU%;r;m-}(3Er{?Be^X+?cf3N>PnwRZ&{$q<0XNP+A$&}(58Hk*bho4Kb z`3KTJbNZvXNp9C17g_{IN7~atBm0ma4X`^J&3|hgn`3@7JM&#O0GAgd*b?W9S-*ES z3=oD&!)foV3;M@pZ$QcUVmh983YVO3L*14tk7|$4Mw1Ac9nxvg@%=h3FMhokf4XRX zA5O>3!%sgo5AXQ@(>s3o|I>SZgHB*m?jW}wOn)#q;dV^J6duPM`htM{a}8ml1xi*y zsj?bgW4|%7ym!h|ZM<7Rww8bxNZa)RtD-;*E}@3Ca*@20GLDt%#5FR&eaWCY6T!Mb zy8S8QB6n8|$vuDycLA=V%~Bc$O{pjgT8%{}RA~HmoOpru9rdW=6VkpLjfIVbQct=_ z&J?4I2Jby8?lqbjS^&F{n-bq48}blGi6r<#CTltwIdgFZ@U&Z#uepI_4}FJ3sxvgr zpjTeO;q{}7OkNWe+s%E}OgoeH>U#B!<5S3gxn;xQLSX$6OkNJH7d&pDti1<*de=O? zmvtA;T_oFRIFchkg-{a(Obm2WQ<4j$kCP;eqc;!5_~3mQgI-7F$U`l4 ze9cc6!|8N!K06U3;R%`tqBqFjx@MTHNZ=UBuuhZ;Ud(^;Y;}wIBrBO6fj?JEOjo?{ za2=ZICOtJu;t#+QQB_jrD1{Y8m|?juGg`wMdGQLp`)2>DL1YG2-Qzn%f!%0%?C%HF z9v`Q#h}>#5gG?Xnc#(?j&nz{JObIS=mXIP2sg4u#%BTQFV0qFjkBX`94u>GNiurAflF?#bq z)bxXfK7V_VmNtxF6lfAJN%y{aYS=~klP)>X@%={bBMGJOv0@J<4t*&wK{AGYr=-Pk zHN5h-wo#D@GN@ytdG5id+(IeAaMa^OttU7|XHznv?SLsg>R;gM8E-GFF`{H1KW;cY zC4zTN%{>g`doFvZvOVX|B48;BU2trN2vm{}gw3c{G>>A(GIsY*UzS(JX1VE|O-_49 z!+GJLxAF0(;k4hI4cjc26Qn2x%{OOs2!VLFt}`^mwq68pCSRgQ^&gVy8p_bCuYX?W z8_C@|-$?eR`SEbIF{@a!-Au>ly^FBm~dq^+UfyBJ@LphFKjqA#J} z7mG{84U3-YsKuRjyg(FF`Y{w|(KR&FA!3?35JB3UY;^Z!+anw$IPHXo89rO$TeC3y z;{~})xtwB%>9V*x&|^vtgH_jsOMHqwT7t#9FIm`H<7<>hh2x@-?wYpV$oMbIKbAAU zrIi2>5m!`SQc8&(_r;_*!AmZ^yiX$}p$WX{_(X+JL4`J~tH4`;LNEy8h#<~e6M9O~$AAM2Z&gz)_u4pr-4 z;*%xfAAZ{ox^Go!L}^)?QW+XuNP2q2P33en;9}EErlX5_!=nCuJU-X2uMtCqK8}W; zSOUGlVtjG-TXQz<{|bM3K0Y})8+sckJ)ny|JW;MmGpao~4X+I>IJHuuw2Z8)0~lY< z&qjM$CYQ6*PKIa+s^K@iPIekN5hXpD2|LtqQ-`Ek;@B&)ovm#zQ|J^ z3ah&eS%rgBJVX^8v1J6*$&0O;l z>SBz!XjLY4r;F`Hu3z{+1EiQrD9Es`N*&5&p=^Rf}&x z)k&KA!_@AFnY`aoHYGN-kDSdLFEm7ed zJ;Ezqe;HKY+=3`hDr_Tey|7C)mfWj>!OXM9Fg>=4(_y^Mc~?l}R`TZBh0|cH)FBdn zI!3<3xMy)K!&EGmSW(0bCbMt#9Wi;jR2=@l zmqSx~C2AAe&o!C|sz5`SgV2SNx4#74zL@E*Ox-A{x~0QB;VxKl&LJpsX_O9i zRL)4RI*rPA03z2pOqLf>@olgz+jb>OCcx~t=LT8xy@p6Wv|l=f1G`~x&a-ff zw4MTk#7WpePmw~oNKp+v^i5!jZGY${Tqw-fKyr@%zz5YbEQskwH%jVsb;Eh^#*t6f zKTUHvUwECXgyL#IB1gr;Q|Z)U@#6+*XJk=>M{r2*7DaoN{Hqy02zpkXd zn{gaD-QB@wh~ZXoyXwk#TRhU+;u$Z+>5KS0bS%_>_^iD+=}eh74oM{l)TE?O#-IyC z#AlS$Hq$UYI5DJMYAvWe8YADGt`H$ZF@h z@xam~M~Aw-dbw?`P}=RwkATIxHP&34nWOm5r2Aqr9rmrDshMd@fW+?|YjjRL-YnJ` z$RBhRFgll_e>^M`^e(q~F`MFE<(8?`zo8w(b2yx2_SlN*1*8{D?36bth4pA^>>M2n z4S|2D0qvlY3)(7UQfU#BsYa>bjc{tXu%l(%y|M^g2&b)6Bv*6&`~ag|GTb;&Z>z{t zBUZ%HwyaLE_09r^dl0o))3?mF|M+X4X{QQTjc5#!@LPdjA2-)uwVbuagCngFI4;f* zMOqOqx8fqdy=mocFr8Aqo;1kv1uitW@0#V#XY_9V$>}XOXAQFd@PO9rHn$wccqETc zHJ_)^r$%TgUJ{JWFG~e?Zg7;$mWS1 zTvqA|YNI#H1qTKS~3 zRGOr435;=RSBx6nTS5S8YU$$9+Y3gG{7I6fqQ>}dEgBX4)5MNIwX>)wVIs7%(46tK zEG;t3-dI3@@f7$cTad(~x(ii7*@y~zzn1$E01s1Mmxy|AmR(6)b6YV8@zePPHV7W; zShvk!%S%iEV?ADbk=~(b3+RL&mW_TsH@`IxizF)^7EejkJS^fK{;>F`Zr37D9v0w^ z44J{jWpC$u_-KbGW~^@E|J`BZ;zYC4Bg%r5nBVU6mByUcUdk$Esl4n526!<-}kxzjBdz|U8v`T4h!o9@mLPoMT%wkcdy5CHtr`CK~$FD^Bi+{^y$iw0so3DqZ+073N zjR|M>*|~Ng{oL`3$%n*iQSxB{fux-$#!ZYfgi2VxFH^GW3{f3YrjelN$F&)-&j{_{Ijl_t+0k{oospgC3%f-3_Qzf zX0d1rrS+$)8$piB$16!=nfawOISA<9`NvA1pv)mNEin-R))&YXQsY9wsRO*FRQg}z zJHx#!kxE%!J zGM$OK|3)NgUF#Cd2KjeUd=SZYAC!b9p%TgLH{(e*W zmNuqA3Yxwj#I=e*(zQF7A!(c+k_p3aGw28@(UufZR7D8@<9Z01N=Yucikghm0k0)8gQiC)%-e&1_LE!U5@?!Nya$A&aj&u$!H9{@P?^#nxC z;o3u;EY?n}5AO!Z08qy!(0sPZ<8%;z&?rbC*SGkjh}C)!H^xc*BP=kBGLlxLkJEnv zjKlK+S^%u$_jTQdW-l^xNYziD zqBWBQ4m1O$1(ug9Ch2v7I0VwVDxgYasLv&Ts3H4Y-tEB;QTqW7m(N4Y!$XvZ7oJeR zdj7UqJgu9X)$L-rys`-BRV;G=b>lzbzdO=UwXCVu#Py-`x( zp%9!suby_3)pm+<6pgit?hgz3SAsl=3z3E(t|Yx~E}ctzfI9%4YrN7sQPx~%ix{0? z!jl46R#Y;y)zOe;qc*qq8&aSJJH$s@}f9 zPejLDrWV)gCf%9}MoaDKTopclfg>TNunVhP$zMLN)T2$@<=col;y@Rh z&wnuNf(mu7gRQ`FO?lYM<4!z8++E1t7IN^9rMy0!GKVb8FV=EIwM9E9_$<;jE`x!w zsS9^uVNG3B#c%>1Yd1a7M`C+o16qNx@` zoW0J3@M#0D=9c9|#|L6$VMy2Lh}*B#ov z1RX0<*Bf4^tB6Uw;M`N0PumiAV*!D64OeS@{c<0Spu*W~7Y~JNrHr1-RzL@#pD*$A z{>;JL%ux68R_mVdR5QMX54kfbhn1Ntrn6i zUoGxH+78`lEcU+CkQ|*DjV^?~S~}Fh0GjE@sE{&nyXBlo8ITr%r`3o~t2hkj7_e9$ z1yFFWaVTr0;84daR(u-@=j=%*NzGOm5EYQeSFyB)A2sdGVSuf)dFme672i|2bG)Z8 z8dlwLV&M2)-xp2{`D~0lpZUT>)(UEVGx~jFm65?FHY`Q2C2dQo--GqmbX3xXsH&Jw z=hm(Tx!r_Kb>@!q2$c?R+G1{9pn=q?e}ybl==RAHchk|Riti%KCl*C1d)WE7P9tWn*wy!I00o1w8&Ko_5eGb?M5s*$GK5p$Je*GFx$J(> zun&1ZrvxRXyj}F1pn8O9t?F!)9MIe&CJ2mP%-Ul9I%yaKr;(~kcxjhn;w`JWBGxG8 zPazKs{6zO!v0+E`3Lg12gsgg^U&NXnH=9EZr)oBt%nYF|bx%J%Btv<#>s?)K(N#1G zJLhWMNpDG>|Bwmtj8o(=gD*F6#p!gFzQBf%rqc7O)$j)mAvEKv55kxrpT-Uz(982m z(p(eEn{QFolArZ=R*|@IkZBlRiVk|tM#T5Etp)Cfnp&Y z7v_}*Ai^RjGe-c-@}5k`Vy)xfVUdh)*y`e{FFuVf?BoGU0agQRT0@Os0=GNzw=7jH zxJ^ic?m#>0m{zv~tXZz{0&$D?ug1uMD0e=b_fX@VFJ{;PjHh#`2!mk6N8o5GeO$xj z@<2lspKUV!XqGy;1THQilleXE&FzDNAk+yQ{|i9}qoWc#CC_*|`km@97$PQ}U}>x? zd^{NS5DP@)s2k}|$0$mZMZk#cY@Up98;a)I_Uc^-y9t~c?4F|zeDrI7dTB?~ zRX(oIhRdDJUSP39&K#fhPI6I`dGGVd!+xE!K|V%rqm2m*fQ$jcCg5!J`*07|yWpBK z--l6`^6(`iOj+un?TsQPCWlqges>axwmU3p0o11xYR*`ugQ+qrIU+0Hcd(iU4CQ>- z9kfzc=MxL)u=<1#FNBxdr59jf$+zI{-dhPd!`D|JaqyUr7;NU?5OP-Ph=TaQo6K(*7^kCv(xxaimI-i6EzfSi1o*q z8d^GB=om*+G6NQRe>!h5G1Z1qz=yyE#57GT##(aj74+|DN z4j4z^tnjgN7bYd_Q~Pp)KfPfee*nEjH*ASGun>k52&H9Zo5R-P=V&~iW9g$%kG(Am z9QzrVFhd;jIi`5&V^(yFFBHfQhcP!$xZ(}S^Inhwt>G@@PhNqJwo;$Xh!u%S0#~5B ztt8?>fh1T9Jpg*hJTjO)^aO8Ki{25DxE8HFlf8$=Es76j0O9B%13%;t6si+Q27chR zhq-^e`&;cV93%l z;2DNr!J;ZXZo}BhiouTj592dAjv=YvwR2|uWjuzujk8F!QZv=*DQb)gGp_ zW!kxEbGI<^wM9Kkg5?u zP4kV%EzqxyXP-r?j&?x?KP(Y|iUUm!4UJsXGu>kBa;047R&y|I_EVT9j?aU& zm3j`B3+bwY1?Rod*{qq5AW7)ij2kLq=V-ZBg!TK! zb&ZiW#5i(^Zlk);4aW^{7oW-A8R^w<@}s42i)%h0)*@E12#}^vh(^O*p%gAuT<96h5U?Y>>A^&BCYfRw8@>j1b0+V> zQ(Hq8>o+x703nrDLvBN0R`Prp9Bwfk_=vtInW1~2DJ+vA_O=fb2CGMZ>0(3!l7;H$eF5 z?dFp2qGm8QCn5{8aOOR*SP!Rt@^Z-s3FFPe4!Nh7-dSnfkhFm!3Jx3{|JIrz2edz) z{I;LL#_zsm&oV)`Gl;H?L4)${Ja-F-(vqV$o6!#}prg{kkO%73r`iRG#C!p$_1pad z3u#i^B*U`m#DubNnO@#cWp$^6>|OKpf)?v_$3b=h;XwoPQ>}Efvl1a=x|QiL16p5h z%|Zf;Kt0TRDPJYXC*5q@2HBlr&DKs8N9RlvE)HP!oG;GtOFEh`EQDj&6oL;hJ?k0B zs`(XO5y3=9&dQyF4FVNIBJXm?#Bn&~N45PMb5drIB9SETN!<7HmmMK@TEUq(!oqw*cFm zOhL_6>7qYTMJMn7sEU65hW&!(*=XR+M851#6W{;GqD~D8w4=pNV}~$jtXk}3km9&T z^x>5;1d0E*D(+<>{!|CPWN7u^)k@KWS9nhk9)Rab9R47AtOt*=8LnnaU56mPM)YdY zjxX6>!GbK*sijALXlEsNrb#Up55yH5U0H?8Gjsw{c^pq=c7kRId8&sP!jLri5Qj;w z-mE_w{r%@WtQ^CDDx;FE(H4CvwsVqob|9$5$TZxi)gZ!=T4hgs=<_P8iytUF1e`bu zZO(rGlqowa62v=+H!@Skjt2g_EtX9_`Y3F$FvR^MLun&2OV{{hpe6Xo5WA29I*+-$ z&LWkoNQ1OhbEDfUwXmUa#n(d zd9L~Jf56;~`zI(yt?>i2;o6MxgwH4bj`UoJO0v2$q|~SsSP8lpy49Ollty9(z2`RD zqAzd;!{=p*G>o!XKj_{>==TjaEc_ZZE5Nz7HGr(cd!GD8I6@OP^*HGMjBOmLmpfJL z*HXs)R$`}uXL81CH8+P;b$gZKhPuAC+RZE2UOxJZpt#zF)~i?tj?7c-uur>>e-v47 zv`u#y?zn3)^ssj%vG(ORx+#g_fk?}_58#@%Cv=98&r>DZD#hMe@?fg7A2s0?%qVJ_ z7koD!IPvpuzvq%TMo1GdLg=vgCQa~T8!XO*fF8BbqfX|dk{hzN&+0x~^rpQd);>35 z`P`f})uC1J8ruY@#_)7?n9!C8cXl(J+LWvXqTSP5yz4a?ILg5L0(oB{mzngTBz-7J zA1>vFQG6Xc!sSvBYp(-iyx}$+g8go9)0<93SdGVLLwKlzk*|BbDV}*`Tmw;7Sj@bU zQVe&@B`|AIjPc$%bUTFeNppOLY!7l@H^WBcu7D<@<>>$s9hylDZXgWd29-U>6~f{O z%#?*oytLbJ#XQ16IzqD{iw~yuA@-2=UBKA?5Jq7pEhLD_O7Ob*Dc;%y(DSr_5}N~I z*%1%sz-ZT5&CVqDK-)-*w!k*BdWAiN_vFeoU=x=9v{`*BKq_<9H2s@F`dCyUk}afe zb0meQ#gcZ9mW+1IkSm@P0YmX(U@Y2!(K2Tn8s@0;R6Ff*#P9wx%B&oLe|c3|uf19J zx&uJQo(3veB)u|vh8I%6In--I%Sh|pyrNu_CgJ6SSfwD5PS>S@A)SQt)Po3vyWmes z>7ke!{ip9@SpIU?$@>2#$MXOGx>$&RNC$&|l<@zlu9A)IrSk!oZsP3e?NfcH4h_*| z<&!XUD#7v`R*9P$ut!(d!*3 zejI$)Y{a<&l|IXAd=teYgZ0v~qMGEZ0v+BvWLPR-V_uy$r;?bkaLHN22?x*9j6PZ^ z86!=6MXsQ*>3He^w@^D}c@wCG;t#5IT&?c?w5-lHow6$GU(N}b6@EO77p@tw;u55# zR@sc*k2zod(35lGXYdMj>1MaN{c)iM^=I##N!WbT?P(oJ8L^ddrtJuNw8FZ-CQ^+i zFykh~x~CV(nIyGmjy*nFgrv!DnawwtwzC!hOIz2UVgQ8f$}e1!^65igQ4+&FXJE0T zA2m;JFb1E#Tl^OnTCvdJ*C1#go_%N#i}v40c>iY!Kg)HYc(7OW3igzzg8s&&!~sH& zv;yJ{6)~(zJ`(Eu)vT|l7R7h8iL_N<=k4UwvxKYWPjA;E5^C+?sZ2gN9h4r51Hw9T zwTBf|LoF<;#uZ5WOZzq>L+LUpv0%@Pp099{Cu0LGWjrm^t5IMgdEiVN->EcpgapEf zMDL9x;5y|gOJ+8&Lz-oi_AF_sPVTrC!06sK-}I*67F9kwCH?;JBze${?mK9SF7G!e zXK($yCeWXSB@2-96?A0+kP1Z1kLGf{E9T82ttQ4bHw^NxjgafN128GGDIp{xjU;s zRBb@JlS=}H0{)T*3$r2GL7&3K7!=u;l3#Y1R=Uwl%*=_Ll;A{M5}pw7p1I z3@BCBSLBZ;1x^ol9~;|~8(oCBc7@&^vUn z>gE38XSnp7sqg)7jhmn0)KYHOEfHZ>@3>}^ohwrEWd|vNb>Vt%T?pn%x*yF| zOKPPtkthP(t)6WgWzq;)Bx`7YC<0a|m~Mh%OBBl*3dqii_qfXu8Bd25CIAnA;jKFnfnw48rO<;+CcHYd4z*E0>))z?2h5ptaAu%mH3MO9 z%OI3#=;npzBRx|p2P;~qrQ&R12dVxX+gM-xDb3zuXyXJbA_x6S&ahkkt1;JnnG<6L=z`9sH@B{4=@JaP=iF=VHojICD)id4U$jJvK#HXEWc=o zQk@DjZ|mC#j+&VJEdbhu1shM-%1FOu`{(eUh0Z5~(O1dnjc^ESZx4v1nuqAi2mVP8 zBmlfjS~6w#^r>yTvfaND;g7sP#sv&Is|yP^eK5m|$)e914xkpUCD+CxSG#pP8O7MZ z;I&qWD_@N9)mvzp7TnwIn8f=|oj5<=k_j(P`+bUcBzbBQCReCx&15Zuq-S&W&xN|+Kg4BX1mK>|V4@B|Mx5}W#r8+>hKbE)YR>MWRw^QRo607MOe3{g3hrxfAywJNRKYAizgo>-FM$ zv-mL3|DOIF>5ro$g+4aRrpI#xJWRmP6@_2$K_*EEF4fueSleH3H!SZ50=^t|40(Ek zBfOm7T&)&QU$1xng)OHZ$6~E^U^`wtXmjjnrxW|KkdS7E?6q+YqeA0)&>|K}1Hi6)c~uu!q0`{D0g~iT+ZobqwvG*%|`>aGU(l=0uh_ z`k#lMg0L{%#F$6vbAxoj5KZ0a=Lfz+kA|QvhCMUQZ}EV^hjIr-tByDy98Wd!f+g*9 zWCWWs&xTfg$Wz_uo}!c00T5HD;|U7+x|)4iAnj_wZJK-OXSgfyx zx=|2Fm4c!aPyw7+p<|E`%X=~$775MKJ^$cPjIjT9z(NdytJRIZ^GN*g_!}xu2K?h? z!)iG?$JxtP4Rqxt-JS%gP@Rup}UAGwgg zP($))|AD?Z=HEGlATJ;4@k7l_NIp7|_euc=`Nsr<&uV~(fkp$Fr1bGZA);3%6>4D& zHG|O`gOOq`1rfp^Phi_WJO=iPupCZB$jXr-de{ZmAW3ctGUb7Yz~GH6o9)qy%q0{W zY@nYaXloz?GgnA^3<)8pa0*cxR*n!5A@&g6i<1b>J{XCP;Mlz!dIU`Ipp@C4@_ExU z(u6h(2~)_N{EhB%@6)pJ8Q!2QKS0r|8OYoV*99;Z*hLUM%VyA@r__y~HII5U5d~ZG zRCaFH{`qeRkm91`8aY)pn+fu3Xc^Wz8Avb1F8y4ccI6FY6Lc)hkZW?pR^?a7S_~5Z ziCY2Vd1N5;Od}@Zr{L_E8-=zbaZe)jTDyR%+Gyb_T z;b*1Rq`eANDH~S(7X?7q35{<9$2bW6PaW`zP>7z57X980l&{nhZHHsn^tk`w-QVy_ z6&@er*X*I;cP}lZOgMVh$gm8Ez=NU55TV6Su)uv+QVL8Z5K`yrRD;1hBAU()kDKFL zWLB&D8_i1ITaMRv+=L~{T6J!Qit71O!L??O%WqxhOBOnoyL8eVhxn`g9wFT;3CVQ{ z(j)yrF`d63_18fZCnav;Pa-zRs3aV@Dk2l%?~)yTyx|{%tv^0a6wp@Kt|7E(j%X^1 zNATsl*fI`lKa{}x8v5W+ntCc(S1iLm#p_UliRhC#6BG?N$1Byu4SzyGr@Y-Ms0G-f z7^REQlmG{STuaxuawV_ec&3UUUhbC-PSbRy2ojrMdcQ0I1|UIh#J?>fx z=L5HX4@7?;*z9DU;IN6OK|naXC>^3-{z4NpCv)@$2gkfT#ZwRbdEcCVXiopyoc`RL z{tavOaJT7(OT=S!io#U*w3NQZ6cEbz>i9I1pfhrgUb zVV#&iK?RH+zY_l{mJ7d@E$C15 zesiU_&3>7DF5p=c9@T${uNoKdU)Su_v-dpG)tG5t<>cbj0ovI?fdjsttr>^P#LJgl zQjTzTb>EyFZEk)vXZ@zwwW9ePSJ8LKA>lG|gJ4GdJSgpgo^rkv!!Xt#tNFgYv_f>OLDcQV;MSb*-4@+xwICb$}t;nCjJ^=IrHRjm5h(e9k_> z8M-!dYfU$RIAbLw#XRz)DS(DT{BS8zAm}mA_`~u$pznNYbKWNvVun8$&7`~WbJSy& zI>{Z75i+4<6Q8q6K@0Q@KRV=CBkF&|^MJf`od4RKW0{6&SaLTItMM4lIW@nWaWA-N zy!`ctCrS9bLxKs}1XB~8QcH3ahqEn96pbCMi$rhYNE$0%@Un}%=$1V%phq1jSO-QX z>9_)lXFT{(T(46E8Bcg6z$-+=gmJ-_Y>UvY88=*A8)U{)1!nlR9ryWnqW`4!&yg&5 z)hEXN*#yXJLL$B%IW|#O9iyLT$_rBz4Cx!4qj|k^(DWjqP^cx-i2vtg!+|0Xc$u>H zUyViOa|OWysg`dB9mHe5-%1rLQgV-WBXkm;4(l1GJT)nl$d zMZrYjWO+u=LOKOK3kt~YnT4z8fpt6A`hm35kr(%IvFJKMO)XnokZ1l_8Qw^9vP3M< zOmxA2l80&qRY{+J=dy}kB#Rs25)P~O;vWv;e?5r*d=UTJLHxg(2_HXdChP03(9u7R zet_xCte--`2Ecny zGx^xWov!m(3?}2v^gT-VY+E`%J@Ub~YcO%0b}u`aV@|;u=ujamTeE#6vXR(~Kk}l_ zPfBSC$zcPxv=T5f2wsAHbGEz?A%Nfnp`gR9gCZ08F#$a@m=XH$>6v$+OQ(_WfBjGbG^aXrU zC}KK}fV`#h!-$4oHOToHTa;%OwU}o265Y;xD@m zjZsMQ&>Q6yLk=BZACuqUaPW7MFUC(iE-<64FCzT4&bFeeP=AxYC(L^EU!exsd){#8 zW)nOxR{TBXFIZ8?mH$fF`VaPupeIGa=E6G}oGdLQ4R}Nl^z93;;+VrRN|M}!{;78y z;|-hk88tx9vY*Od^ndYzk~(xV$p-W)A3xZ#Za0q`Njid;5e@ii-Gq`R36K#+o%$1N zMIo(>w3X0wJ4*|LDRLcLk^Q_zeFEh$&X1e=HHOei05F}z%^!{mP*u;ElI|0_TxO~3z(F5&1G{5 zJ#snypx^h+<*>Ot#%VBA!M!etIJV+;6$)2=3o&RX2ZMIf<9wHJE<)Q8!CZSgLprv@ zHMgqStr#i&Eg~es^Di)TooB10t~6UwIjCzsHZVka43^^ue?**jCBOj%25EeG(4U^r zx&r(9DuNi#D-henN|mvdsjj@INkG`{BVY6Bee-Dvqxj1Uzfju`IvYOqn@?Y0-Uj0? zsIX)t@pH#qK|bk!4}F$OC+Zdj;_!L>2}|o)^J(6EKJGQ2A>WE~n$Jn=eipvbA8pgH zCX*gySdsm{e7YS_)zuQTZ~fGrc(~Vr*~}b5yFq#oM)pM_75t8~&C(h%P+Zaxwv`{> z!m8u)7%SVNizW$=gErAh{&h zsksx;=#y1EO81H+4gxAbpy~+tJ>=lOXk$5q0-w1rLHWBQ77bul4A%|*w)hmmRM=P$ z4d#-1+xiiqbcQ!opu&=D!F-fBcYAfvm$zU>_#x~~shY&IhqZ+!c(i}s`z+naRE&&i z*hxtohUw~ic8u|d4AvP&UrYw^D+EFb(G5@#?e&y;8@!7d6bmatkav$#iwR{Lhe%|eya)Bf zc?rup;CFnhOZ2yGZr~0|G{SYo7y5jL#&TRMvu>Wt4G=Or*MzkJlEuf;HuHFiGL;Q3 z^7xIF*jcCv52DFAEzxTp^sEhE!HHi^b{{*w^zE>LH)rC8djgLv;0TUKYL<}1yhH6W!r<*8vUfow+bY$wJbSouKB#oJ3#UUMX3 zM03oz(C+0*O6w4uM3)v{A#E*v-iZb-7@7v^O`T5GusWHnc)*=tFT_`*XNcrNIRNYg zvyU+~Bt`FQR5v!iKcuRvEH160S|_QanN@DPg6^G|EF}# zrf;XzrErdYA`KxNM+1{Ok(o~S8}`V%@hX!mh=mIt83$GF8pqWLP88IurMWBkL}nrUf1N-=$0 z5leA#y{&Ae~^ZEm?;>YVJcYHr!-OL$^IF+hJyGlm=Bc69{_6eYg=xZ197tYn5J z>?%T1)by1~R+9()O%1$6M#2sfTxJyAsA;EiqNi>B#MF)|CTL-U+hwAJs%Tq1d%`dSX=IU!c#Z4hwOK3m?(TafiA^_x?nwer?LsZA0f2=I6p zL(aBwG`3HEZ00jrs&M@PeBwJ0sia@vmGlb6xmvrK4&V zWT%8DHbG6e?!bf7R+CT((VDLDEu&(+vUO|S0wR%gM+jwD1!KZ4En|t8h0kUl1m-bE zPr>2_f`zA{b?t#{V={0fc}{&Mnxsq zDWC5(Bx)Js}Dxg51NVT5s(cx9;@TEvoPBomw*<$d7IRJ+EB_6NujAuF#EY zx_D`(%}RgwWAzSKZ}O}oW=$XIb1C*yu6#tS?H=>4Nj>KoDLaQg8Pe_H~}H z*|R%1e7bBIiL-;K` z16M<+=T_noT5TH=0D^PAF@c0YVGWD&9;H={kYAe04xPg$@>yC3$z8EXur;7~BQeU; zfO;M{_v$5hM)YI=p>P+t#u!q_&bAb7o#-PUOb{&Vwn5lS6q2r}Mux`YJ>1~*S2pfD#pzed)=nh;9Ta;qD%jvs3sI-Yz-KYVRejL9bb z=m#6U-uan^&`vHFOs>1CHFVGN5}*!wa!aCRTX8{fXMluXIN@E(W1~bK#d=K7O^EXA97TT&(dFRDCE{WcZ$q z_MEb#J9!u&vt(5}UW!F&z_}v6wvX&Cpi?%o*nqf5Pv{oxZ@;HSZ7iuyOlfH63Yb6b z0|KNCmEu3DSA-HbLPka0HigLYYigL>`rU0oVt7(uY%(ay<^JwQzyzLuWwsIpnCK6- z00(~Lo^M7$ESHa|ZD(x}kBA6|xHzJ z$07;;=v_GZQBBEuwO(q30qLwYW2sJD?dEkyIN!A4nEtD< zySD65t@vV@eo;DimFH!=?*sCNYsIn)1O#r66XFIwPU4<|k(y1kz{DUPYGEsC8Pvmc`k|g?7J@M( zolgweG${^r5+Y|KMXzrtoT7t5pGLQuBe`YZR-|1k6Yv->QuE!-C<$p0b-Vv`Du*Fb zbNbq{`-9{=(9%4*{CtH+w?~+$Ki)AiJ{O%7_2vA~=*Wl;a%A}g+7LP!J+`fn?`2!n@701>?@%)pTjIHH39;ov=*~aB{neXh+8KE-Pa^j3!|Bg$UuwprfXbPYvU)iYpc zZ)uIRcXLR}_Ljs4cZ-wrpiwtM5#J>okuRwt-5|e7CM^x|Y~T!!F|Q$FIQnkk1HE!eaxFx>y^jf_h~5c!oRhf} zK^h{--RB+G9GO9gaWu!MfwT!Sq@+ay?@ zi1;bzfO4s`U?>A?INCZYcBMmfKessYc|e`yE+r%qlhVnW2m3Sh-=wG2?!C-2d|YDa z!BF9;)wY0c6nqltYiZ=Q)uo*j%r2Gc6zJskEbX`7?IX!B2rbBI>kZ`Jb~Ad_-Z3+$ z*7sO%3X2ueK^}nn4`>n{fU^`DF&yW1c(LED@?mvp>*|$-V(r(cS9y(T?%GHyXS4;u z!!eM?mQh(UW~Js#sAuODFlOlxOk&~-dhm8hnL;#!8oih{Ab zv3|FZ!n-LLLbR0|n@~GGyI>(j(tIQHQ<>zqbun+(VfTCE+Uy{QEtHqqtEE-hGRF8@ z>w}dc?b*PymPjH6G^P%QbiXL7&P0LW2=33lix7F}*b_RGOtguoD0?ty7tQ=EVTyNDZK>Y(ah*w_W-xjTEF84ns*KAm90ld^_t{Wx527`|;aK{wsAyk6W|Jbu+@im&M+Rz^ zY~`?18Fz)Wcl4tJCE93o`?xH7H7+J41K3wEO8VAh&-tpUal&olMReR*}iX zwN#6dE2Fl7afM?aGYP7T{VNH!N)#w;frM+WZB<|`Ks5JV1$*NRC0O_;ypFwlMiIf+ zJ8CG=)x*>C4;JGRZx<$ZbuLs)C|&&;!7OPnk$9VahnGD`Ry=l!^g#_;oOY4L>1BKR zni`^)8pRhQ7kRcw%KTiY0(Fm1$T?IfLyJ2?Rt^>(YDord0j(K7t*&a_A#Vh&W`BZw zwlCxobobcF3}Po(N2PvOtz++dI^R;eT>4i5?poW^Hhr(}rQe5rE*;}kND*)cQaXe9 zhSs7Uz_FbdcD?1Y8qp!*~Gtx1w9kZPfA9LIAvoB z#_p7N+LRC(rFe~pGhqH=lM&$_FGNM?RBsYx5Q$@i2BVXA85~Zhyq9-$d3?-671lA< zUfZJFe}mcvh+q3bh=RFEw5yJCXmWaNbwg{O(8@vfBvgZ~&#pP)kX+$P1-7}p6kpDd z|E|~ZS|lMz!>bb7+Wbrh$NJ$BH~#W2RG%lIcmO5BdJsj4BVH;{V1h?d{eE@^|NYF@ z3c_>5ehewniJiNI;77ld92euc>qguz5I?R%zY8WVKt0^tpAOG^HOgxjJf?;?t)$O? z;|aJDIUWu5VpW*CwzXXL+Az1ejd47`gOk)SoAI( zo{zAbQ!6-d<>!~lFn{0ws(4>-&EX^WlOlFE$m=BiUngqH>By>iu4KUgrh&`*Evb>*N0VdFN4l*?0P#ybXf!%}J82`vX6hYa z*nkJdF68@g7J2;MjZ@^gdZ4!-A|V}JXSQ%|?Rw>4|^cI;syXO zw!Bi{*8`k^_%Gw5NaK03(J^meuv0M#nO+jaY+N2d6|f85#ThTSptntih%30Tr`^w0 zn$IdGA9KPwUZ@oe1$eU?`x11%3F?W92=n};!o0sV#}5Xh%mWJ-pa{7-+M;!eA)Rq% z#B~+yM8jZ;fh(!L!bfBl_8!-gQDi#Eh1*o8Jp3)``DZd_&B4vKg0Q-dV`1KOn4j|K zP>E?{mN0vx1bat%Lb1g78q_NM-33%;rslB)L#GD4__0?)uN&z}IEkoBE!Y^Bpj%uF zadq$ph}SuI3*~c7Ig+Y<+uP&OlsPm?Db;ya_aKpJ_8X6twiP+s0W31=fx5lQ1Sqda zXLky1KE?2oyp4WEtS8xFvC;wTFpuaX?k5z~T40I%I4pbscV6x@p zfuX4Izsd8T+Nc%5)=C#I|FONXj*GW)41+M*`*IOawuJD#hX_`g_cC?b8UXe9@>D=K z290*M0W#w*0SAqFlW-5kat6a8=sR>}SJ<{aFTWeWam^ek_$zlNKX!lbGMsu)C?e^| z;=7)~mzQ|Po()grTAzh?33f6l{FF7{xwOG*@x@AK>8?N`QUy5L%t2lIPM|Vy8v`N< z3&b~SmE~JP6;JKJ=6b*=6sLaYXYNl8fjsnaKHu{ce?ZpMf#}Az*SU;4w}@&5tl7ai z&#PH0NCC)w{}Ml^ZfnCt0LTCekqr7FcFMw3)W6EVe#Ft2AQ=LN?uI7^bW+SoBt1e8oq1z zo*O#F~Hce%)UFy1m&~{K{Zh$u+HUc+C?);;MO}8zF2g zMMI}yi4oqc*{MXy1Vp~=FkOY!{RdAJz_RwvDK9_NmNA;d>r!2UyO*n)M5l_yQWrMe zFF`L4Ai>^=jp`4?g>=3*ft>n9UQM_#iw~1`=fra6J<+IEZ!qaShIpW3s8Z+N#mG}R zI*_Ee5Ux>=UmTovV55;O$i(ZmekuK~rF5Gu$2Xsfi#qEKwdrK;R!X$3big%{@5zrq zu%KvLG~(UT0qIn*;kB}Y>Ay?4VgRJpFsz_XB`vVl6Cl3VXiRE@z6jITeE>O1Ysog$ zAQh01(p}u*3g`@ZY&T0-0q#MxKRcCl_hhL>OPT9aCl{ZiZAu4g9$dk!WTV}JN`E>a z_ooxj{&XSgPbXgc(}87wx+^=_ZbrCd?8MY*6fUHz>}%BErE=@IEM8*T84oo?q&)f% zF}>Py0!0=vDT8KkFup`LwM6tLWMt9v-Fn0=qk8z2aWl$!>UN?tlF2XB*L8CYz_^`7 zPQ;sX_y_9FjF2L67Oh$HPI_V)Iw1O2B#$Wp=%5;qkgaQ6SuzN&8*wW7cfmQ&;+Ko5 z6!lB;qp_q@GS!WJE1kb|tz6+Wo|jiXm=lB?E$Cl`bih^sCK#-3q3_=Pn#IP$MEVuL zUuh(y^%nuKl>A(jh)+?WDXSVjzC2Z&6=M1tEfpZjPG+1Xiz<2)v_CoOrpzo|Du;`= z8mn0j;~|Bt$yw77)l)(a8!*rX9%h-|9 ziNP@QSOSeeWbTY><%&>`i=bsrxAy{fXWoiiso>e=NM9Z^X;<^IC)WV6P@qP zMn~fKui`0Jc;`4l@-Y{0Awx(6Oyw$Qj~exCU*ilS!;X`b27j1 zz?Bph?eW%Z=1#vt8MdWBSgSSCUKA=wUKqMrjK(4#_$QY`C1-Ty!o;S02yVv{QE34M zl*$V5v6Mq-O`K zre#&sE$=yLI92G#XL&BMes*xGMWF4(vR2@ZfH&FBK`EprKbw`p8|ctM885C*L>b}X z7EX;51h#gSt8;m*BWhU`VADm4%hlX<2SaD16@Wz-LD5u5+L<#xc?wn*LvH{VssHP6 zzBu&*rX>bX4i?(!t^wqz}(0 z^WR9;!*gzm_lKv8_RjI0&G-_&SJGHE{A+Ra6(=rMr%4pZjTI->@WQ#E)Di6M=y4nE zb1S1ptWgKYWpi7sWigr7T`lQ7fBoFa_(qbLFSOVBm$`C-#oJZq9Qo}>sbW>kIuBXfhE(AkueD-cg4!yk&=6%v~kat0X(J9s!@ zh>}$h(d3NtBK7AkAh@R`Km4$>VPE10SCO^t!4jpIO`%Lh2IKH_Jm#%&q%iuR+3Le^Tx-mMkNwhoh4LZRtfMtgJ_ff4%VIWD#QecQD7V=4N0QE2 zz=hkUTm(A*%d;qPiwC)1wbh}9B|FM^k|DzJu%W@RY{pZb-$r880rk#cSY7B!yANDg zSj3w3eCRu4kL44A_5{h{T;L_1$<<_|W@sU8mcsOK&4t7i2^Gk!4BRhz9=ymjde>mZzupzuyyo+y& z=fOw6hg~5{E1eEX;)Od@_4EKj9?>znJ3w*KC;{uLZ(F~efd6+oO>m?`;9Gjnl*^ZyAxHhoD(GAyF^6kh(%XP%gYxz)f&=~ z63Ws-3&;qSJ40+PH0U0dVU@-xdM4O*RKIG49Wjjv_fhxX7!?9u*I!vNg{Zy&mGS&wZ?_Ml0dn-y9lV^yPBR zY6@-m-QRd?q8o+(<|s1>vtafsCL%$dfbK=Shyk5+%!Y-?$srXZ(K+iY$q{maPRw|{hS9-@}`4q z`v`nQAqdi4jlTsZx6iidVlukO`x>a6_S4|`VG5LM53rwuS=rA4fE?o3=46oEiILXA zGX$(qnq)3Ppye_Gv;=`_!d}g;ahN7rBpO8Ec8MmIWkX$(I&B!_j@X{xc?LqtH3&JR zmOPc;N9kK4g^>+_x-GwrA^LD-4q{88hZc(yGAF=3fe>`?LasD*ywsV&{SlOWb6YP- z%`F7ZJ7dxo##fRH4Qgqiw(GVVX=S{2nOZaLnu2#Fw6v!OJ5aXdC;XdFq;r1FB2$d#~>bFQCC8$3laJ}>EVf#KsiDKRR zu*}iQRJZYhBgYXVv}_S^{J~MI?Y^U&2>i&$6t*G;^oPS*P(OEY!9>g3H5tELo`Ma= z0xHqiXB|K%SY^e>(+N-U{qYViZg2PaE(J>iFxnCS=|Fsx%@l!_n7*KW1zQ>(K{Mde zTwOCmXt4ncDJHZd)%t~R?^s*QCGF^?+B}aDCgI8w6 z5ix6m=_0i;hjQH1gx#H*V;w8 z8WpKh4kUW~*&5jUr=QH^aK@Y_eV4}D$SQK?R|V9HE;|#rTne9^bCq%eF4R`r4w+y5 znx-IxvcoYj?Cw6}M-Oi*vTlp;E%|^pmQ>~S*&FR5!*>H zcBS#f=}jUT@PGE$aH6dbC?aB#{Tn19=E#Zp*CrYn<{}$bVKEtVL`9yFN~v+7^!|kSpYk;r^6E@zs+{{wD)z&J#gzWNDiv~ zy;1^1&djBHb+BeTdsP~)?~A-n>jXF7ZkuoGXFu9_{e?3bLO8yEOp5;NDbYVZOnz0X z5qYn>k)x^?oy=|yf!+qR#SV(>Wp|{_`m_wESfUtjcnGPv`BH=H=LHk7MLi2o5dw(qD1YMumF5df8?9uBz>@9h>-umHbdq}8>n|Llg~L@+zz>>d z)?|S^G2EN$&As*m<5l6ydt5GZ4_)zT+Ck*kC@x+&fwk1#Uqi0Rrm?y%6C-Rw3xOPR z2TFxBFa-VXELYzqQQLqelQ@1aS&Of|W7b$H&?Y%lz_^~Gt6DBaD><58ClveA+fAGv zUhoB6B9T<;K$W88e&o@I^__Rh^-@2(CRfZU-{;K@S}a*kAUb=Iiy(umadXB_rY~%B zEm(~rh(%GFcU9WyN>aB}4P#nBk|G)Rh5g+F`$)NF%$fsSaJ%VB(_VA>L7L#=8uLOm z;s7}Cdok2rl-3{O0kWoLEYl|MemY2>j3;BU=XpKNr5&Flo^$Ni;ctt}33e94w#GeJ zCm&WjW2|I;+t1m7X5Q)W;~4uMY&6=Od$7!h_skjQNc&&Yg_V50MW;)>i;K=M)L5s1 z3Inw)+6aa;qbU|V8o~=~cZ&TH>ROBQ*-0cQQU@oD-r6yjEl#asGCn(Nzb|8x=1M** zSz)4E;Defjt{JZZ&=Qhj?D+Eci1*o9SNtR{iYp9p_Aw4)^_KCCu@=8xc{hTHAWJ^$ zC!W^1i?@>X${lRfaJp@pEuWTC1?+UxCn!5XgFrR$&eH}zgAOOQB1yhJ*Z zk5b{(6-^4&g>GE$5AcK~dY6w&OIc*P3hMpwe@J`NrbcpRf4e@>36@@_Um9a*&ail} zdzSl+!`KFVz?iY2m;2K{*Do`rQk9FI`NtE{jipp7rP7*8Wv1{Km;gHNxTiFZ>zccb zjL;q}IND*9A;Lset-Q=Jcj4a1G2Xm#TRO*ACkk8KbV{}bHRRBG#3t<DV;K2-`F z?CtGU&tIR`s0ry4Sd^oclH&sFG%e`}3?$H}mmT$2Tk^qH;HCJc<3RA{FoGVG$4$F) zghgdBc~nc<+A>rHY_;)9D44s;5@J+YWt6Hdy{=H5Rdva^Q>1M0V4aHo%IIE6MNd*i zPm`w;udCJ5`8Or3DMfWXXz zo5kcWC1;$NtykoIsfcqdaZypYtVg*nwLfkI>3j`6-))Ptnw@&oo^|cDs4$yShCnvN z0*O25hh?;Y@3Xl6X2C(0=D*?x5czLuS3N|d+k7&rTGU=eI-YzANO)le`JMCqXw9BX=jWXEEQyJ#&*T$yv0{(YvT))rK>Xr$e=Y(@K zkz#`~r8ZKy!Ne!|Et@m7n#$FWaBcN-j%n4A9OE<)7AvQIgI+4zrEwjuTyxrlr z633qaZIo|ZU!c?Et1v6g4a?z@g^-1t^UdZlu~4eWA1z$Lw0vfK0s3{GHhu%Qz?(7r zQaKbk;6nXJiW|uyJ&I`Hs4WscWE*L8t&eU9$C_#^35t@M+z-8L&t0JbuRXB`83@4dR|C) zOGlYi=$3LAu<^G^Ynx(tE1Aw%_Q83%$3?xpQeMY7IZdCpW~h{$R2U5%F;qwS=BmSL z{ItUOKMLnh9SspHmPLSTD}n5G`|=~t3)7;?)y{+{u`?~!YwsF8PN~J$Hf%dlaFqavnFGfXT;u1Z+$M-Unscfea7+1! z=8-Xlm6M6c3%nXDY}+s?i|n@6|M=dK6JLW3lV&+VWq zw>D-W>{e6p#m#*GkY@*fBJs1f9ggw4rBxC@Gz*}qcZkfq1M`9rA7b&vGrBC|s{9Dg z?k1;=i}~w=Ykm)Q(`^h0=L8xg+7UfbO4J79RV>zJSjlt#HkJqycKN_={C%&T# zKVaF9xZ2N-9`?~G(v?$9+APMKv3f7L~%8lsX!D$WRJ2504Ie7tC1J5v1AD|w~KlsCt+2ELouYrB01 zCp#6#GKea(7jM+l?%E_!WbVkAH;HB@T!vavIl1T?34Vg9X6w1dou>6Y53Vn#HhZj7 zrAA|CY*md1XBwFVlV{O9XzXFRy#83{W&-WZIb)rvnIY;P-GKtQczHE_J$zJwx?xDO zudN~VVpqe2C5F)ZM7&!_3-hu6syx}cny14{LqSl*eyaa zpc7OSsGo2&Q35wXDBn9~v-IOZe16^FDS@vwAis`jeF@j81tTOXCCI8#q=~StEd`7k z!`th&q%l3C%3Vp+y9>N*_FtmyHv{OkiAFLMJH~U`8M>LOfyPJ?Fsai?4ve0f)oT=9om zt97MidtDqbKHw8e+oOcGlgNAn`6WDe(uC+VdE4~SpCsOBs-Xf)z+4AJKoxL7oheEmg%4AZ^o zV`E#_$}BA9%nPj|5-&Ee6Xs6BDKeopa;_=6AC=j1c=Yd}st)pF>x2s@WVh*-i%*vN zk#DHJq}U#cf{C`+xSwHGUTp82fW|Iy+@?LR**WdP$1-Y^DnZ|ZB-a#Mt|L9A8C(=p zf4ef$>_k<*UfFYs=!lK9)GgD8U6)~AeU%A_B*3x6#H}$)N_+-BWKx=f9N=8kThG#< zq!L(both%CVHNEl0!2!SBJfHyH6?;xrsa(%|SlRtCgt$pwY2-by^ZVF*UDzR*w1Rw**+x?kP?&NO5k(;;LJC@b zBNlj3wSt&(3cVl_1nSJBdB&&pOFT$Ss1_w6-VCscA?8UnID_~mKWa(EBEA}tvR#C` z*5%Z1lrahvJh1#?MAOvwS{2iC5$~yuZwF5w@0c)1ro6U>Wgvx+pE#JW1uI8+ur>Km z)|}^5_R1C#f(z6JE_(fHdWY~|0WDcu*b`}lYe8|*g~)4NN-0sWbB^_J@?{|BDs%8< zT-VSbRLjuvoE4xEu}iz_p1k_WTM@Shy7n8LonFccgX=^G=w1=qjI$x`?Bs^4hW1Xc z2Ll7Co*|i{0&FGsYg`Av-be-9(_{_9{l7OXt}lz2x>}2NmDNMUgjI$r zb3I(a(hd5#K75DXeGYwI@yQlD_+|gy$3D7hcoP%A;*(!Kv0ox`kWJd8C)Lg=DpDw4 z2C2R8pYW5DBiv>c#7UD3zfLZ2_jeL^^5%j&OU6OG92_>yY!keU8RcmC*l^Z;c?v6jx<$LI6PhZg_P-oQ8@B7uW zPXYV;oBi#{f0cLhEcJa4jw*)1Q6028JS^OYwZ<=#%JiqxFXP*%TJA#lS|o`%8fz4X zgA9s!(8?#MbBql{R!h~Z%+I`OeNV13IS0!LzZnda(( zNpkcUn#}CIDWmFGlaXdI*8*IDo1&q!V|B8SIf-pvU8lZO`3>-F%H0yD1>_ynE?&Vh zw$##Xd7>Z{z?i?gim#PGtVhAe1W`SY+-+JAw8A9N6s}5D6vI8Tg>AYK=$4hPJjn z$$EZs0q3GZ<)&<8zUyEqmEBNYpFIM8mfU)Mk4)YyF_PJZ0hh= z|2sMU9DzaKgZ-WW{a&P4U~-5N^rU~(*Z)Ya@h1xIpx_i2I#_64kBZRb>;B|D599VQ zInK#75OwJSJVfQFjhbLJCZ*>REZoRbL!^%GMHaAJUJa8LEV#c7z_a79rIuhZBw{ zk)G1*)0B6%d1T$W*KfP|A|=(fZa-*Q9uBvzc10gI8#{zyCNj06Ks9DFwi``*yBsWq zy4JMRDY#pUDwAzV_#oYyL5T}%1g9D`oj8EL4fy@Q8qOt+b5tgdr4TcJok@n4+pSF2 zhcmVlig&jJmY%yvQja7NrjMKkK*>epI`*?QVeYTGbrT3P2}AL6J)B2qojHxs4Dmr0 zS0{N0TQUYO`xY=dIfnwAy8Jw(fiHlyx+$X8N%t9U1n4U|$z<9^JO*J5yo6w1Rm0Ld zu2#Q-P7zNii8K{z6h*vQR6;0=5>gzY66u6i#1qQ1lnCVnhc?g%vw#h80huCJruxqU z!U`Bc^^<|y}wJfl_g0VPZo!mUpW)1V#LyO?U}HqDvb-frCCQT4Gb7Sap8$S*Dh-9w+oI1w8T91!D>AkXEv9ih(w;emfVb;KD}1_AD&8a)g=nU zhiPhCQm&_Sd`yoye{J?Z9dOFo@a&)5ao8`GYJ=HL5tAsOPP@8oq!qDG2W7rb2W7rb z2W39e>U=OsMLuFhK4L{arID|{nd$%G0o>{I0iU+hhesrN5@1{$zqj8JE20JLBQ12j zd|q4}?|;~{DWlm zTE}aATL({9cBcIq`i(-@f(_DF~U5dA;O^+fgMD1WLRiqwfe57uJ+ zn$wK-xVjBzyTVhx3_>e1`$Y6Jd>0k^vysbsDDDAGK>c>s66hh-Sg zK^Q-A!m~l)xVWgv*u62|jW;fhUgWoB0^NvRl&=B>es8{7nt#m_7w7rhfNg%b!Fqx1 zp8AZ))vft%h{2euayFZ7o5lVLhp<3&f=*092yO~hNf#kEz(5yY7iYJKN!H(*pQ)B2 z8)`?sp_s{Ek(q&~YaH%0#p!5AFA|mK>9w{~Y_t{F8pFMpqvb}Pgi=@VWC%VX z{Yq(=_4k{1Z)%jwNhdzrPIC7(QE5<9DFY(;5xD89l6dkaT->YFiPS5s6J>@wJ5 z48nqji=wimt4J|Bs8m^%+^BrB@m=c6+oEEfWs_?#99g=8m;s^>35>iI!d16i|22iun z7{eUd?_d64AZ}SxZ!yAMaH;#$?iUU_t)BYAb#@CBW*$2|LuPBdW>>$3EyB%ON_VRT zvCJ))G6vgMy&yQay5(w>hdf_iZ5DA4w3!dl3Xz0?!OpVb&Fbu*g>^n?WmC<^MOMW% z_LV(#c}KP~OG3A%-cFysJ>t@hb(w}&OvZK|xmjTTH^oxid*MdLciac#@ZWkR;yLz} zy>A7P02wxhI|Z#uy~P4TDOSOt8# z>_AV=z9y{}M!W$s`s!>H6xwC5Ot;GDK;nFncOa*e<0BHxMx_loLoJc*M>k^gOYY1O zk@Z)wV2in&r|A`FT%_=#y&M#6*S9=*pF$dANiX8}{Q};j$xR12qvCM-s~eQswvgc* zMfJj+t?&qRYIsTdpoUX9JC$f7)Ca=q#r;Gby2KX?I?jtK1!SacGdvRdy;@*X!mqIw zV+fWYEa}ZguY`@ZNWXChv|%l%3sGZ8o}`tN&*y!YyQdt`oihV&@?r8R!>*o2@I3$f z3hLaM25Xv>H+xAT)4pkx8Oh_Pn`4Z>W8fuZ#3%GGc8z`m$D~5Voh8kF5;rZu))Kb> z0}iPOx6>~))K{6;98l36?VFu%EIW?q}>2u8I2jI>8H~pAKrG zASI$yTSsuqKseWwa3(5vswv@`D3MH5NJjK{JJp51dZH@Aw004rvFe!GwvKu8_E|n~ z$Ty`Wy-pdBzB~a`u&vJtK$fUthI~3`LDFZu+`w>g=sWS_+16(3YG?`KC1~{Z(teL0;wucesQ;hsM zC3Q~*T&}qhpFQMw+!OTidWqwmL_!M{Zy=lU44329&u#+0HcbI8X;W|6hgds1g1#$x zgR7BtA@CFCK8E`X+~lBF=}|wHiAbt?Ae10SY9WkXF@4`|Z(nh%IepY|bCC`A(IBup zbn}OS{qhgo5F=#C^$VfGT=w#Q9+tuSX7RVSUWWD4-6Pha_JMgb^Kk~$a8x~x%AI%# z=dJWpbAw@HFg1fo_RCK;K>A{fz!rV={S^*qs~yHO`)y1jRJZI-#p|n^vY_-?-W^4z zRQOhVm33W_2oDz5R5o<$XqfGK2@RYkDDZ0T$>>PbE$=SEO@lH0Zqo>?g^Ia99n^}> z=u3`X`iU_mT{^dI#u?Q2mk4@y7ayn6)yW~tRqk zk6nDnJ?jHPv?g5TomLmerpI=@of3Sn9nOOZiaJLqA;GR}wWsoA~^02K#g zJobxS4>!07t`ph8Pm%n%JjB=|jevU4s5?DYhc-By>rbsKIJH6`9*JQy3OKl6Wp`eX5u5{7 zM(wv#-^I$^w3Vx?yJ)1n1?P-w^`|oC7w7i%X;vXj!7$UR&t8Z6O<35RC{eYjKgwL( zE_giQ4kr1Ys|90cKKJ(J6OWSwXI>Xn$WyH;)C~im-v&?s2pDT&pUToan%;UnE+Rof zGy`(t@sqPR_E{Y*g2RW|cAsr-wmr1J@9pnL`-?Xb8c=I1R~qcW7C4*p{tbU#mg4ey z8hX!v8#-+9(e%!-jO@Yi2JH@($L+Lk4u<rc-5a+FN@TvDQK%Ft)$p#GfICfq3%7uiT?gJhR zlWpk%YFeQOP@5V2MR0~zjLGY;3QZefDn1|mh75sk>y7wF%TU%KU28^?t?vk9ie;f# zy_$8PS!Wycq9n{Ov+JxM3u1!XQMW&Rb}|3bPI!L&jf^MUUp&np{W3dX+TkA=Q!I6I zlb2>?HJWdiMg8axR#5*t*v@tHKpX+nAr(x=ECbN_b$s}3v&3f4u}7Yd$?_|&i@``C z{!@F#0*q(%r#08mwmJ2lco|to_wsu}#G%u35803Di={Tu+M(si7BEFHYPPjiVFWs6 zupcEQ+@=Qs%S4IW!f^nwm*wC^Z^#xJYb|KhgjzL_vj$rW9-3?|Xx3fhcj7{#Tc+~O`&P@&r)Qu72REm^H8 zjW#3Mg>DS30JRjgYDC;S8LO#L-hgUeB#0@^KvmUFur#Anzh!u495_p$H5Wu{mbZk8 zpm8M~cujai4=jBYx#JjVLnoEBqZKCncB6E(h?=$49IY>BSEPMq=4U&k!bsyeL&`*$ zebN?$Uq<&lUd1XCFsi5|q=-oHEyV5~&9}8ozL&5*v-BgGfCszozpG;FaPYgmF6bde zj9H`}@!xHXWFybPPg7GtX`A}yTLe{zvMk{_jD5tJgb)_0tUv|JmSc=J(|fKFS!_&j z+sISmi!KWn*$`L2%*0BgooolTnr)h2i?9Wr--s6HsSm^oY1 z6JOS*p&A8lO~XFFjk$w0Y-8{s?CT@v{%12a$X9!6RF39le6_&B0%z<9VHa^VxSvA@ zT#+qqac65HR;yqTwt6**8>m%4z*I;M9ym*z3sXX>k*D>GLh<2cU*aOzgqOOvjH?iO zU0;&G`#E&L^;`WVc=-;`(Q-?N_kvC6s5{Fn&O9zBqO-;&mMUDJDB4Ib(i-rasZ7oI z>*pZ?1rH!9%t;U;~Z=pPuyqQEVzaNhq5b2M)oy z-fzaojrga7X8NatMmmS5Mmq6k`uh*f^!Fbc>4yi+@`*Roe`(TZa5vI9i8bgGC;eiX zUMa#|eY{s>FkL<)G6rU$ZI~ae{7IvgL+`YtMvB5gz=M`ArDoCwy=TM4qc?)-DGtXO zkJ^E20Ia02$-v+%BqK*okBPjxxU^x}G=Ngqskue#gN(o1x@f4j`m~bA-zy&Na z!f)q#47Qivw_Mv}Rh%=>GdH!QkYFJ9<0boyHYakld_u!cN2v_vl<4+my={6wU59fwAuX{UKhb!#d*<<%dMe^ znQ?h;HJIWeS)~A}QJTq(r0Hy#<%)gVJA3|fkNa{iGo~C%;?!XN9ncM0Vjt}rEc%uR zg=6pj>>2D6yE7@t+b?I-I@&?Lg*C551GZIrGZg{@a}CbDf71J(=}_dJHx%4*O=EsE zV2g{RomJH@W?oFy*o(EHm$m3ry9O80NQoQ`SOa?|qurN#*eZAmWHpQ=j&xft-Bmhj zJguZ%2-jq)gY8>emmK4lV;M7~NIW&SoAUT9d>1Ao8tDY5*J*SN<(e%Qzy;U0DWV;& zTDS#rnJBxS!Bk<6l?G3+6o0m_7 z(B>;Wv=g3Pen^LpU1Yg>UD;wc3k^(a(rQQgOulTfpt(LT2J%jOo0!o1(($J}ILV9N z#{SYa4wMF_s|#^X^oIz)aw$WD6|}AYT*j!Lw9u<|9#&{x0%lX9BZsdW%t>USNUMEO zs?Xua$B5XO-^#kao&9ofV8LTlhA6rE^WENF5-Pe4F%KYG%3!{Pfe(Wf78DIV?hx&S zT1;|RIz!KH&N}r1M`sSUMFEb}4W^%`jGnXM*LON#%EsPf9x~VrTZA(W)s)D4M$>J0 zjl(#d-r-KcddcRs6u2w^D`1Ig@90M4`y{4>1QfYqoKjaUX7P1v>l+_&wnmGb$TV{Z zI;e9g>b+Xz+|5Z{>W>cJP5rm`nE^;YSv0u1uVA>j8pT+ z!yRI5=`|geXE`8T+JgOsB9+lsQ49KrQ$hf1z+`tCOx8R(t7*kjyOqB{HPV(RfC_MW zD#C!HwMMuPd)_5;8+bii(#VoIekW&*TTrYEYNw>K1jn8{(GR^1y^otJ+fl^M?6n3_ zrn1&0k-8!!G*zNmffA!tc)N(R`LDwYS#^@=GqG*@!WR8Hq+8e))S(u2)wDW_I;M^h zT0ezt_ZY~T!%e-x&YoeA2b|Y`Y|%2ULF3n_r^RC(*VkE0tdB`z6IC%|WhtW-i$kBi z?0kL4CdU+5VxJ}qJ29bZ$gRTjD;^(BI>q;}9xQPMi-reVv{=Fh0EGHBiin1RsOeJA zeiOD@jyoc!Wf3+atgmG*viLrGl%$eO3j|W=+~H;?{FK63ITp%y=pHjqG-3vkBLjEQ zc2L%vyXn*Nzy4uQCNK4URYq0z;%V`C-c^Zw$}6`G{6=b42h_K_|I%`OiOn%R*k_Y| zEn`$iQk-S2T+8eWeQ|2!vMIEa)1bvlm^~ra^vg;`d5szdn^H514#-A?-sZu#)L1In zfd%Fc)(*zm{5Og3e|zaa>%j0EzG|&;V1T6<^~>L8`tS(-HWOC` z@8(uW0W}*P+a+quYb4X`V=#z*+9*hjwzD~JNj~3CZx%@5*gpLX=&m`~RU6SrRrSxd zbuMe}R+Je+Vd#{Xcm+;M|C>W0Q&)=w{ZYo!IjT&zhRgWEC{ydOQWzoDT>O>B+;9ES z4>qyyXk=MR19pIymlcmPEN6wMHkq2RlhvB|X^Yhjsnw(-&nZ*>C)q|FS%zqTbKwks z_cZ6)la-BDPwT3;H5YJmdvrF2c8=z8W6Oh(Gekp6VmCQC$+_!b#1hM2(j!uD^TP{Xs16kG7? z@sx`n=5!T33LHu~&1f}BN*0}~O$}FEJOR7jE4u1zhI0mEvZAsVA5-K`e)e3+O?4=# zr~-mDDG54!oFZ@tqbvd7mqle4yMY;4T+*TE!*JCP{DKF}cv6t7%bb*1d9e<<&vVqP zOJ+QWnCYXsHRmTY8TBRW%5L2ki4x~ z3=<$w^u4?mtDu=3Tefj$+)&FT4;K{dyyMXDflxy9?jUb$P-|Fzx%gisuba7I#`AB( zO=T%_2|;U|n)PWcDM3szI4UD(DXsj?ymh{mCu{(+sD|lftaJz#Bq9jqOeS+>@;yeadoMv|JnNC&9j6+17*R@I_EUEMjJV>5sClc_KfomY>SvH+!tKxF&=y7t;&Xcd0H8S zhR%R$fQdW}KxRpDGCU!2ph_*ilh&O;UoGeEey^Eh!CNuu@_^Br6K#~8Z3nAoYJc^m z>R-Z6aa6(#YFyRZ=-Cs)Mt*Bzbkl#R;G3wr=IM`K(a1(x*k|;k&Vc(Zb6{c}ef*Sp z^JKn?H9tiE%aU`Cv){03y}CuGOH#OOd*n!anDR)!I2K8`SVLI7ibHS=NB%VHK-LMuCCR z7@odNURr(O&?oKl`NGw%G#h%hoIHA5kHU$F{npQbKRvXi5jQlhZkV}^xGfcS1ZSzj zC|6zhzxJ1pT+WM>c3hZ$RnK?2GQhq)csSX&TJ~X)V89ZfyeStPkF5Z--ph}k1@)F9 zT!n>&Di%pc4~3~_LQ@o>dPYrX1xlH$x3s76iFiq*N$0K|Av#E$bm(J0M$m<(bD7>s z<4O>f3j7{D&I`^?M4X)e^>m*Tmsa!7_SaI41+l1UwwxW;07*VQR$w-;YK&7|Ua&CR zP9+JGDq#={aY9k5T@tW_&@sCZNXVcl^l3EO z&J>>;C8{>L4&3_J$mkZ8F$@PAaZ?qjv8Baarb~&rPfep64~9?JyB>bsuTVPkAn7T1 z7{QMpz6oIr!l!-1ji7KNDBLSYcvlDC6NK);-YNtslf%dCH*aef#5ZBg!dmn zIGfYD{x_>({Q2qohr=+8IR5OmKGMw|z08T~05retNbaw189wVx8|UKhuLX@pb{mP` zKTY1D{QlVoNbG3T;oX53!ebquwJIY+IsR;KX7P&($O(4Ugg*bc;Yl9MYa%344of&x z05$r*`C8j8n09`Rg#L&ejf(}ZxY|E@y{&FuZ-*~>WCRVL&Fq9_(q41+M>DsWU71FJ z!)K3+>Bi0i@K{Df9)ZFs)8iDLi<<{{dR$ugBMz)`+eIP;8=3RNc@$_&@ZM&%-Z-Xd zAT&W+U8c>??w&AbvR$r-|8b9D$yQsR0ku-p4R=k15{6s@hmAFL+km6kJ0?AZr8})HxBl&fgQun5B^!ultYO^SSR^&*01L@!H(sR*#tb$8xCU6t0}_f8d~k7*c!%O!ZWJZ2|qyVzY)h zxSww#qnCam@D4{v1&y{6gpiH1q<|@Kc+5Om})> z7tMil6yvD|RuKVZe-}f%B5gDn>422qQ=75K7$tbFh+N&?(y-oIXs~!)0)OZ= zIzg57-Z28=krA$yA}9Nb!_H~kgsMjMu%Cst)y?b~`zx5>S+)4OELL zT7c}43xdcyVd~yLv&2f{4Tx0a_|bO9k9Ii$*&JOBqCy>+;S)E(3|lmcjqfwP9fs}o z^jrNDSUa8js|N-dMM~SlCJ7u}{aHk47mhHxSh5yL5(~o>q|r11AWs%;7-9p72nZT3 zSFE|27oh88NQqnxc1OplvfbK=(7*@w!nUGRB}50(##fSunCB0Q6upw^pf+cpes0Eh zE((;6;g00f5g#cKBS^yi>7!{; z=a^|%=NNjx1?IboGlCyB9~F$U6;xZJGi;`#39PMr&=-gF+1aFokVqUaQF37m!gCQ0 zw91ljf!&BUYc*+BYXTQm)N=B^8AZLbO#Rr5{?d$oYDV|b_$)K)=?b$mjRB&LX5=mz z|3CcfW2c-PQ@Ur6BnU}}E_}AqHHa)ic$Fw`s6W54&HK7zJkij`y9NDa1Pf9jj@nNz zMQQ>$R$mTpYHj!+6=Yf@8qwca6B}8fd$gRgO)jN{h%zFgCFYg=ATV>&P#}2sMCEYx zV|4JV`2fwA-i*M-C2w~mV~yR^V>t!K_E}fh)soWX>I@~QxHT66+xghVZB20mP4fC` z-H=*QyR}DFvtTk!?@-^sZ^IV#6^pTaVHWXvL+i)p$hABX+h?~24}Rspcq$$p25P;* z;I7vuRb!OUBjSCQ&Kp6VF_2dVE2KdtlVAaKVXUoKtR(d^dJ8{ra-w*Bh^}3GTlH?E-qo2ip|{3yH1nR#Ke) z7!g(t$T?W_o{bgR>a~VO#zthh)xIA4=UExCe)AUl{*32JE7TTAzJWdXjv*=|DSlF_ z-PnX$C&FD=8Lrv#0-QS!@nlNW$9KWg!IzIMk|}LuiXfhUfmo)Xl~dzZTaHU|bC2z2 zr8+`+d@+6UQ?d&N;klXC8$PYLj0XLB!;(>HrZ3af1E78eWuFg-W><}}i^r%I+J z7gx5yjyRVwDNW+bN*3&bxC*)o!7%HAMy5B7=*zDqZ(6|2V#`A~hKd6uiD>{>USAW$ z#+ftFBE%D=uWUAA-jXn$yAhjaCPX_m%@B(l70s4}U;v*n9c0SUVDDGJ*q}RP8~GWY zesmWkZ9&x{4+#8MV`EMkC&lz`r5aAi3M-3XaO#gQDW_AG@Scy7Kf?yBP#!ipxM66F z*1Dv`Y@nOo>0FZuy#yu`{#>a2K2^f4}ae@=SmL!R&$ zo&9G}DcZj}9!$;$m(}%`%bxxmRhN^~>#HxrQFZEQmy@&c;Cf`}qro||{EFD+my5|2 z?|zLwUtNtSM}Lh6qiTaVg57xBy9RQ8)&F%ucs!`CdzZ(Pp`eu9f*kEY`T@DTn(St5 z08`!uFlFyg=U+{2IiI!sm_E2VfwO&BT<~PQozt<9z;c)qB(CSYq4xLo9z1qEeVWI> zyV>e!S>0^UVF@a9mS^~qWe8SJXZFKmZR0hU2RLwh)!*?Ny)yQdN1i9b%S{Sgyxdm! zG=6;i1+@R(vZ7iieXZbl(Oz9Xu~cwUlo5rVvfSDaC;Vz=E>&M7cT8dA2D+w({97rGp;Pk9w{tjPZzRR3k^ z#iuKq-B^MlD?~>UH0uz3BDXKgfa_6p{q=JcawaCLl$RJUvDGZS+X5^BaZ= zb?*UuEZ3fFrJB6VtHJo9H~O{epK}>vAs)|TdR`d6cIbv_u4a%6PTJ3pU{KVJ_< zpR41+aQrz%ej8me5t)XXo>~TmXerKUBu$Dw-|Kr7V~{OwtMliL=0)}Q>lC$o8u`D^ z%$2SEwc-18F+QnQFSlHt{$1gb;^D*un-)A4hbx$S&$icES)_O_^`&~UCf%xeVu~>) zVxu%s)ip!J%8o&t2lQ0_u(ewm1zXxA1B+&3tKn;UoRIJA!Wenk_7DCKU-NJw=);A8 z4;MK};5%#&#bB%+18NaQh0^1kF_PHl_w6@?Sv(wmSp6ozA!`a+Z%WD^WVJ(Z;WvRx z>I;wiv~{V3YANmh)0Z!-BWRq-$+whd(r4t}*T8i3fVzS>>Ab(M=Bx^G`ENtork!RW z`shr0CI^#yjvuh zKl^x|{)_6?BWapkrYInG1@2YcF74Qc+|e>Vf~-WDacUGsWWwb`h=HpK-lqLXmp_qF zuI09t6U07A9C`(;Eq42aU&1h1{X2k5734 z^;G!=_0$M48WozbsXg(Wwf*%vaDY?!UE|C}I&o+zPk}Kp8!4eS(A8!6;M1b9lV5Uw zRc6v%8!q_k4psLx2t>zq!P29VEH-u#K0&4rt4(oV)e;P1-vYT)UG&X5K&K%et=lBK zY!J57l5hLHjNw_~Got;qjM^5)TjrdpmfEsPLMaORc_1a$>aM>uumh^`89L>C4gk>fBA z=^0pi3D~2dn!=H<5$F1$jO$=m4?^jy)G-q_XFrH`D_^97D`##sdX$fNx5$%$6ZIpu zRP-Kyw`wn%6eBEIMOr6nqS$=%b(*zGYpr#wRz`M@vgIum!ctx2aMFth=Sv7TnFvq1iPiaO%W`9DXCRGG-6o>!9b~)Y- zS1f{yr}4N#3@qTf2|Igr6} zy^0CSJdeTSV#FkC(@b6#SGUVM&QcDl=slhf4q|9Pny-Rj1gO45JzcQzq6xO4pyf7A z!(zC{eAEEczG=Tc0@*$z0Nl14^)LP97ch>wywlca2O*B(KI1eoY?51pdQkaIg>#F} z77S^*V$`}o$+L?Daiva>*2wN0&DE^TV|qo!x>T2aZ(uPX98L1`45`ICB@nQQXu3=FOz zua`DO99XQ0K2HLiCJVz+D7<&PwrAiKXI)LvljV2n!g%oN(TOk@H5a>%?KGxQjK*W> zCTslaZZ2SVELmZ)e1R1`YO~@(0!J%B<%p%6nlg*(d!6jll#nV}eCrWv-%cQV-7}Y)ad^_L2T08QRA9DJvPp)QA9?kenM_1YLs@t zWA@hN`Y1QW1ZXrrtSqFffHo|MKNP3ex*ejS1;{5%n|26UWhMo&;dCjHtLt7Q@3qRX z(=!Q}+bB_jt!+^A;dEthNJ|YmYh~z5^BG&@8XqG-i*}PP4d{THAfOykt9BLoHVy@f zFS2dg>SYh|P$`b<-#GLXx&c;p?y=mMF>&kG&9$-iYkW=<(nREX;{T9-Q*<$5nz?hV z&Pk=Pr8cJ`^~}fv$Og54h(76J`odf9I<;#D`Oz#2P7e=pg?-1YKL7ZI5z1)lWC>XO zeD#n5&+a9O8*TY4B;7M`|DsQ-Yug4;Q99s`q1WrKlt$niNy-K1-mJ6mJAuV&Zsis6 zs4F>lfGWc*Im)kA8J$31r;U6p*~z<;=;T(5S>;WrNSMqBsn)KjYt;qg(i-VyD71=3 zj22<>m&q2C60Wf}M8p_aGO71A+oyf*ebZ@BtW`ijk4gSiy)r1h|vpPb0FwH6qR2&TH2}MmwLrYR*`RJ146;+1Kwn?sd#`)TycB4WM(`X+?zt_dVA+$@uD= z!5NbsIu7O-=9VP&H)?dE+uQ~A0_fwk0cXI@&Kq^1kJ^-si)Iz&62ni^H4lZN_}cX} zVM{i;b>gba0YNWPA?p$-=I`qc8Jh`oiLTMmx=4bZosKZpzNnrg79C%nKT=C zuvGiG%w{0KsSiG3%O?xg6Mf=Zh)#etR z+`Uhej~{-SJU3!@Wh{sH_YU3_h!(9e6&32?`vYERdw=-BQR}Fo=4eJe*n1bnTbe~{ zR7ErD;oirCf~rMpR7Eo?-&Iz?qBW|b8TG^dyZ9`tfJJLmMKkIzh`oynShPk}RH*L` z_dcjmb2v54`~CfQ6Kgg!e4S1cWVD)~zeS}1$^dIB4!Q-U45}$*P)#X;T9n>Li&<%o z1E@_2P@59+K~?e-O&*_ZVf$>}G^mbPNRx7r-j7^`(f3J=})=4(>5%P5414_Q9l${p;YbNxygA|8l-V;;@s%VL{?q z3ia$L+#<7KD8@AA6AR$?{pveznEAQ0-$Q=iISXLQ4!(+C%hZI6s2`8(DN0}BpIvE{ zJ*APxp0z+-BYFC69B0F|B1`v9hiHnZZ@6vl2bLD{W`|4J2^dl7mmqO zqa#G=lrGrC-0y(llX<741DBWX5+I=y$W@%w{Z)TXv`$c2&MNJKXWAqOrC1fG zolzY;{a%E7b@)#T&-evgMKmi_qe%iuM|7YVN6U~nWsK(VGzWi@qtV##NRk5Oe9c4* zzA-*X9@M=tiZ@Xb`~QEm zKf>7tL_HLwQa8BXp_VK>ZNA?0stcU*9^_tfUt~;Pv;3A(c&@K29#pHdGy)D&t=sGnvY%xHW zr;RVEBUC&`Yz<4_pZJShsf?C*gj{4j3Q%iGvlxd0DfnmZsm8!abHR^ot-->;%lR3B zIjl*cnLM1zmfn>-UkZqcj2Pygo(r}Cv1D>GaIl|dYP#}+ZO=^o8lYL>HL@ivY8+NF z1~R={i4$a~Y-cgQ8ivW2^x}S%#N4%B6BY6~8NyZFCbKihS+S;E8HU4d&@ulEE?<_V zCYEk|RA@w?(T5)eRFJsLu7xbf?lCM}nAg2f#PVVzvPJvu=?+cWSJ-`X?trq<96Qll zIXsZa*;3UIakrgePFb3PxnvjAJm76ig8NpabvMZikov zYA08w?8GmU>zs4+eSdphydo6V2*Y&=UM?MQyZnx0KfB^eJ&DJ|#eZ4#4X^hVTU5!D z+2uw##mreN6!>hQnT4rT42&d;whbTEWQ4tsl;v->>&=Z9=wUdDxkUWmmdNO6mPC1o zvH@C^7G2mWD;iE6BquEEm4%uuH$-y&>Z&)&v5%Zlb0=dv-t0yExtcRj0$N>Ie@BDfq%pT&4?i?dKUSZA zslI7Y_U8YG>6|{)hNiTc{tv5%6&8%ID_*Nu-mRvD^@FjaUGfF#@5;W?pT7RY?9UcM zBaFUAvU%J;g2FGc~_k%qL`wJzpp-js{Y!q{yM1sI;{SB zS6Q3y`44^$7%QK*_PPCg`*bydeFu;H#K+FdFaNwxxd;3|XxsKq(VngITzwf2zl^ImkhvE7D5+&~O~tJIeTtA%ryrS>U|h)a zyZ3m`F}s1a-$Ap&+jL-f>f%qW#!}$fpErx(e)6`%IT2lzUFhr065*8ItQ9reetZS6 zbyQ1qy~$`68$sAIyEGFZ7y9L9nbo%mE3M+!l$*T!0^K$PFQh8Us>`ubwMJI{h38j) zNX4I49Pdv~&MtfB9=JxE`Q_VU`XDaU+7he!UpWbSh6zZ8@6i|ijf|R3|Pv2}&`UCF}d(}`Qey=(`)&KqKg@5SEA675# z;v%=Y(G;~syLZ(0z!Cmdd>QW@v4s4Pp9WqPVw){DpD>}Yv-_yJ9vpu$YpTic>inK|4-oYlu&#)qo^v6`U7J2*T2Jm!c!dthFe zjSQNO+Xu-=uPNPDZyA?Ap?Xa#g*IjSrL_nqbH@qm^?fz@wc?S}$&KxSldTQs*cY*$ z=E#~@4R!F?iquLTP$b5%%r7$5DKDkbeBSuWlJf0cF9T1e)L_=>=1$WT94aozvBWg@ zG!`^=35Efl#i05+$qnh_GkL41M;;I#ku_+vWNaa==C|73Pv`4zTY8&HvN!SRHcg%} zP|(6p_Ak0we3T3zv94xY3%&sq1;{KMmxTT8@xdB-&9K(-0c3Ew3w9x%93F<|H&mKm#ON~&&? zUY9M`ddhL%zEm7U@tIc}SAOrRf8u~8Wtcu%t6V+EjAYBw(*&^}t`4fzVYOl-dQT5k z8^%vHW-FnGWb?md%Q`t(y*~aKKlt!@`Z~Fo@$3!PB(}Syt%1k`_0+c&!7ookCWm)U z9SUAJ_n)qsJ;Eb1|ok%TB6w?D&3(z_8PoEPaKxC3o_`1?yG@XsD;u8oSP7e2A54bU9@f69eIx$q zV7-wFl0I%}AE%1$SC3SR54~9z9i7c*9?*HRaWj#$G`D*hEU z>!P72MN3aM5-e`3evd-|W5HC7J;o^am}WdC7i-p4ZZP;W=KEPNSTV;?AFsFq!D#2n zwq4t{Z7De(obL3li@$~g-W4xa(lvY!Gp1$HW*XrU&B;WsO3+ATm z!e)Q$@qeHHF6hIQeCF@)#D*7FEmVdbEk)9lp!I_e#`4=~Up5|wXl6PI+ag@W^WQ3oLv3YS}_KepS*S2F6XUr_T}t|E`=z>H#wo zJL-)F&T);`RBEneFq=wWyvnctVrtdE$QxxUnyv!Of~s%Q5C7SE1fNQoMn)4la6hLT z3JFJ8#Tl1*gw1bN0p>dV>^Vk>RNKzUHW~a1mHl=*Mbx-r2Yz&qhm2J^<>vKHC7V@6 zm@UeL-Ok{q<<7|sk1MD_sX5t~89K^ig)eclhLGe5A!+#6w~17rxmhIn67CFH&{*Cr zjHjYno-t_)MGVikfym^MyXi`E)lQbrOI$W!M9E-(B;bp+cbA&m4q4kxa8~ z`?B3*TId&1?Cawm3#R?BVJY^>e{TE-+bsd@MOT`h#$dy>efAo^VS2>t$4EOX&hB39 z(IB2q`QBeYzOJ4$I_k(#e`15~Mtq`iM^Tp%rm0>VLNl#6$kXuI`g9lef=7xb;qI3w zOHEOFPb1K{I_W|x5+{w3Hc;)LBUTpnP#WZ$Mkc%(+V!)O+@2tQULzAx;`FNJ_?Oe1Vtu-Mx#IB=DfmD1f%Whc>kmRzSp0qtm^9PgpC(P zHQcw=O9i7dwlg4;=ux5_{uA6lpIc73oY=8Bb5(-6tot%{K?X_@p)E&~(-TLMD+;L= zAmTt`c8M5m^2cY~PF&(&T0*rHv$bV1`f>#Fs2Vd_&U0)ecN2qQ#qZIV3;SijomY1k z$7~V$%iTABVVL=gVgcLPuVMu5jLCZV@U9P^b;5U0F)(xhkWLgaWE9o;$%uzX^_PA0 z`{Ymk`K!8^u*~y^Eq1~J&mV-L3uKRb{&ScZufqe|4E0B+2VeBQ`R)V{tA%>c{=(D4 z_@ib3%fG?lraPUv=$)R)v&Hq<?=J^7`C3fLyilio#q?BU44w$ zOlas~Z#2>Z9?N=3nr#QshaCX>KW5x_vFOr9$wcI0dIx)j2!R7?c`B9QMQ*p?!(a&) zIMh?7%nOD-c4p7h-B%5;yR~Y@t`0+oj3Z?ort3CShry2y-gYBM`I2KHOT>GJoW*5; zv3fE#*hxubW!__Xwrn$U&=CpjG6^N>sPAZ1Qy_#S`@d)ze3e4k89b#p; zd6X;9qm|)rSBSHs|L;<-X$YAU7EF)L;svqd+~mrEY0ClM=Bz#P0P|tyv1lqgD8&5m z)x(Y}hP%)NiL}*l8aBWyn>H(VRR_;)XK#iQvcUE5(`B0)dPsz*K} znr64cRn!U>{&IXL!WOvD%a5s4I}g;ygBY4Rq;C-y4El(~Cj)7heHn}jfXH*FTR?16 ziR?Zw84%oSvfA@(BF=JMXldPCU7T1aB6QTO^n|fHVr}!O*Tqyu7c(6j}EF>>1YjUkZzsvd!QMgGB!EHg!>XgNRBj+@q?9hEdw&or23iNVZABf9aEu6J%o$`b)whDFH&tLR8LgW2tk{keL5`Q1-=*2R=Y^LQ6bXt1^f}O;psscz??)Pu1B_ z&oJoE+^%k|a(0-D8w%7t=gv{AbI5#&b7i7l*F6mblkcgoSYl)_zl0=b+AI+VDS)7w zlly+PLE1w2U4?*SiwF==@Vn{(xu|=}vbKpEz5BiDe!s%8)%{_0&!xiagMQzDK+RL;BRqb`j<%{Xu+_gla6yVJ~6uAS1ysmz8O3B{~_ zq5vtC(WM><$+jb;O+bbyaP$5VKkx;Nt@QqR-iG2Wmh-EtU%w2ch1xUCtE~sWz^)>UGL-ACOJZbR6OLmK_qf@Q5An?Z)gv-O~ z@|}5Hjc!pZNAP^o!z(opR6JDYlVi3gJjea|vgXYf+EQ0^3)#;347uoWRA7?8QF-f# zj)yW#y_3O;Qw5u|^m2d?1L%YUnrYyJ(=$wh2VI#|(g$j3IOMmtR^bG%>n-DaEJ_G(Kpt8lww z_Duv29W_GUMuPh#10XE__!0ujCfT>`TzA?d62Y9f3lSdco%m-fmaw;E2^12OF6{xW zSxxEQpb3RL6_%x#ZSvAOnwVJ6&d-WT#HKqo?y|Oj5Y{4w=_DmH4%*1 zg(IJ#(??H<$UzN1RFKN#4Q-m*S?#57pdDQ2QGiAyIJFB_3m){2s(+P#hJA)8RF zKaAXk{k3zAVOfehKj{Rr!!#ZucGE>_J%AHK^t`>7_i=}+x)$zz{eOB2=*7gu>1y{-fFM&HavV`Dv+$8>iZu5&Z`-M4^`CuBAd2| z!~P^$|CbM_J3cI*SqM2ERU-?UjJzP4_~!E1@p48^5iGKl*)_Yu(oh@*AC-oy9aXG@ ze%J6wN2=eA8e=x07D3;HQi6SkZ9oa)i(QIA6wg2KZlP9AZ(?0i9Oiim426@;ROi32 z?DbD+BdR2OKc$5%UZz2@3n_{mK1{)N_#_G*j}M| zb~dt0g;Jky_~qyvp<7E8P-03Rw7MMQ`+9I);$L1}_6Hi)T-w>|sa$XRJz6J-mMHW9 zS#y2m)`$4ThyTTq2n=nUQL9zgv2j%%19^V!f7JW4AAR`d$Ho8jb<4T_pRNTJ`_JDJzmzl=J+m+h!($vMu>iSOQhvnum){ z5)Q2Z3`#pU>AqWYU67>Iiop1I(3{}ebuc(art)XiV>qS=nfltd;)hfUa!yP8^5}`98^U^?=+`+=TQ!v zSKe>%>wYW102uDWM~^yZ20Zv!TF+$N9ooEehe5KD3;??2Yo zI7_RCWO2;#H4n8<4Ju7O#A^x$JG)_M(BV4&@cmW;fUvN|3ks-xk2^DLW<6HU>Gv@p|2miM9T!Z)`dbASGZzCo`=rw%nP zj96T6JtP5wgqg|kisvOJ28}9lBMd7*_7tLxk6)QIO{jXn zl;-fSk7RDKN$*jncz*t~1wf<~IbiHiy!70Jr5#wuq7;6tRKTya>_Jx;7}w}m)xvuFwYp5>1Fj{69AKCQq7>o_-cgvLSPY&+rr*hBoN zSL^&3eeMds5^?AKMN?ZG0_p*#GkAhF-l7^`_s-7c$@^jm|8X|Xfa|m2;Nn=XUL}-( zsEs1i=yQ)osw37)b<~=vj#@iKlndAE^WoJMO87N`{KFMdvU3Bx3!*E0Rf$H&xqkKH zJ*D8~I^eYwW9BYIe3{gpr};PYD7wFQw`l>ryY)L8nzoV)_-e88J?U8Msh7%yqXsrZ ze4}TdYztd2*|DFi2Rq9Io{;QV?_HAi;o79$$;GtCeCzdb{$;y3T_KxXemDOX*xzyz zii9VT!af|Qs1|t!musW`GCCC*^q2(D8fwaSRrr_nDx`1 zFp5m&P*bu^16_GXpBWnSbQ6!xZ^0>|67-t(DjQ$T#7q9&u469Oi%*}pQ(i&b`2{B= zKG|82;(MY$6D?56&mn4)rdky=S>>7|3_vp*fSpQl%dC^$kPXQgGwOnb*W@yunZ+%e z-#6yd+X*aLXcy>N0&C-7!=iVZQ_kWQaM+s9VVn7}2zn`LT=+Va*JqC+H`hI6 zv4CzPb4qDX@oic`oqDlupSH)40d|*<65OV)mDT&Qi}23c3U=93iq_qr!|$taF*PuC zb)zG8=z8P$7Zwy%yPbtxhO;|un1SsNte4+<>){+?6ANor-Bbs?YQ<^tglEDeCw8_k zr|^!`ljh7^C%g zdHXOyN&#QU%?$-T^QF&NWFwHeod#$8ZMv}&@-4m&FeYTraVhP~kI#H?EWZs9%g%5c zK7mblQBjWXsi2jjv&Zd3HYu1g;wKGjs5x^^z(WQ2kDmOL5Y?Mtn% z#yZx`OSf{B1b2x2lu&_P5?T4aWj5#%c>370YT1Tn38!43&bH4vS&k0Tj-2@;>rD#I z+e8jeO1t118r7fg_dZou&x@zw=@2RPw|bdec_nzlGnPSXU{$3BVu4_Ow16)DK{83v zyh*Cb{QbqB>GGruWH@T|@XgDPGj>|%ShMh*(Nog860t}_gDKV{E@Cp_L0sb3EiNEvRY9@L9|VGh~WWB1VfiDw)jeI6B#K2CByAqsiqL zUP}Apj2F@@q(26~9`y>a-UbnN6NEcl?p&-!l6Kt>H?%*15^~xbB@F)6M>y6SUr{8!S;)Wn4xBZnQMUUbMdBNUOr}q8E@fl21|F?p zD0xtpgD)kP`8&I_Cb3(|f_`Tu^Y`hFN~L#Hmyr9zKC7m1w(H!!*i-$7_sK&|{{ z1saK+6=+~|RbU69T>(8wI^j>^5sX1iEznMEw@^F1vyN?~u4;DSbk%h5)2cR7yXx9N zc(bw%thZ{rgVa^sn)TXkR`pEhShu#wsc+V@ru|kGn;2d7D`x6W1&g%qru^qB79`%P zV+*CLlEs3tQ^_K&tCB_X+wEAho^MsMAS0A^CG-4kRIf4h8!3%B*+>~(Cr)dQbhIz+ z*PLn{bQ>#I&Y)hqB)pLOm1Z;&f@|JE!msdbEj z&bl>5%3C#SAiY(u2GSdpI@_!@zTa%Kjy`BNMn`vcHLYsbL9L_BI_tLcr5&`I6XUIV z{S&3Ut)^A7Ri8$B$5&~THgGyBvV+q>NA>(CIy*QWbarU8#$+R1`CIcOr?)q@orE+O zcD}A-`0n6zl-$5+m)@C}YOvET9W~g&>8L>ir(J{2#8d;HQMrk_xWr{E|gpZ+=b}@*7`OE^7ZoxD)pcy2bMLPh`7L-=JDAbpJ%O1G9ssMzV2q z4RWn@Zx>0FTFbCzT>Zn~~NBbGIZp03X`heY{J3hA?v4dhawYNBm{1KwmVk^2oLUn6V>qhLL*!>Y5 z6uS{SDEiR)SKs^=Y6n#npU=-$z5a!5ag7Aa7zt{EMq-(3C$Y@gm1}}YMS0zyxdzU> zp0qrJuhx^*8=Fie_&#h)~p9O_Wy8 zbkpfTdW%xgV?U$SLffU5TcVpvBY8J>qwTvX?4ayY@y~7)r5VxHfDNQX2?K}O_A82p zxK$EY^kcO~2YXg4;7#;e4sUa!v4|-ZzJ%|dsyTY377)bVlA6r#4VoiL$(|KRHS@>o z-+5$InY&Z4MtoWP_rKH0e6_9d7c=Y?+rz~T(vXT{XEu9issxYl zQ__IoJ`RIU!;~o`1}_f~5sGx-%+T5*tBd)2OgdzBINClQW{WIb`RnyljJsvYffA?G zVk@j=F#^nr!4=A-_|G)cu*n=<(&`>lz?U1;pj8SQYo4s}tv|V)+VSlPR8%0^%Ufj^PD{vKeHzAPfvxt~#g)ZDSWf!4 z9)B(kQ37@^HyqhI#erH{I-y<2*V}1{%K(-)Q8U4WTDCF*4%s(Spn;;PG||yHZ{;Gb zliIO7dNG1gJeN(}n`Qu(#M}%;>eJ2o^_dLu+uJNW)70er z>;f~~l~x5QEXQh5TOFA5N-ZiF6Ju?nRmDJQjq&9qS2x+{u+-pah*it{>+jf;WE-1m zAT@Th)PI7K6le>zW!9N6%4*w7CL^nCDOgx&VzT&zrZU>wTGp~XY9>|`;1(QpN^LWV zrY}{N$EIwfmrQ!6xM%BsUhY?fKj*ZFgZ%F+L$C6*?Nh& zvvC_{`$_}gW?E4d3~Vuazz`U>B&lwg9WGi17Z^0GzZJa|jp`;bk#p?Ut`Eg(Fl}1S z7Q8_aC?KgjqS1<(k81q$b=lC|cSklf+YL%6ePI+R5 z#IxJX7iiFImN)nhPIh5cLYCD4+ySO!?v%-+vQzbt2rEqx{az3Ha?t&G&^yKvKs*;B zFA*o#z0(Vv4<*>RKb%~Q&dGq4{^f{eYBD~%7~nO%f+d(fVV|IKG5C;c4pY7V%^nHG zEEP*BIE)jwdm?c?t=8q?)z@E6kQL^m&OM9>TTIQV|o)2pYupX8hV}Vnq7Z z5siHIbR)lBCy(ZfL%yKCE@9wXVEh|4&{Qvt-c@)prCOMrW9<>uSKN7NTQT!^Rx>y2 zL4nUx++4^Tk>0=+d7a)cU!iCn}}qRta*j?Xz-i)!mj=}yFA7>wp{jCt8=hOxeuR6#q+Zbm_LzZavikX_~Y^u~DXmuD9~tT{}vi!Egi4+AoYU-!fxA?Z1cJ7E3TT*ON^_STSzXRo*sYCE`JCPeAe;zY~ zFvG%$OaQ;@fVX^^U_pp^BKxXGF*Cw5u}l`RN3_Q(u?CK9p9PnNZ~5n^4+oPP78JTt zoR}^~u@8*V!B*VK^!cEh?$y@8|8_-0op=2%IO_Xb|hE1c=>)E_fFX_KYhrb z(g1pQBgF{L+lNUVck6!HxXO0|d=YMC@s4R+W8@w5hWY(KjAdWo5$%z}r#noFnd9o$gFpv!9Vtj!=Oirf2daGVp5sF2AErkOk^`K*P80`B|( zCCLHDwV97=T;|Ru&w8{@FQ)PBu&$eD^LlG`;I-?vtsczq1}McMFo-n4bFwxrt%bK{ zObJJ8RiEIVd#lrB)57m4YOc_hjHRWE2tTQ2QNS2t=>lZH|`1fq2sned4G)WrH~8$&AQO=22PC2{Y8sH@Y+Ro1{r4tQL+g)EWr zf(!}gQ2IfkfgdR@@FS;FE1oHICn6TGKRko+<8h5Bl7P8A4s*^i?xJEK%g>^{8(53a zaxJnz4M^DGKEvlS>^S()O)qPv7tFK)aS5=KfULTbyxI-mD#>5D%TB)~SeQw4ov{Pi z?yGB{qngye21?cn0oU;hFalbmsbAGXzY@au?XLwxE6u?o?DF3fGIAB#x%zLl(QisM z%^5Yn`xsmy%rxJEKyZc6`Y<7+j!I$mRtl@jp4ZC?^4 z625k0n@Pblq-C@t-S+*Ie!W1bg!jDO2!2S$^tHdJ>Yle6+&{v~5A)~C+l3ZJ^HvZr zJGgcvi)D3i)sY%09MLqpaPD|^>?Zti>@Oq~BGg7}$F&DOZ*JMr?xr&y28w75!Xm11 zI2xUQ<*nuO(N{*E{ovJ3v4iS3p#IBiUX-mDJYWZHn^yc{`hV-}oy-8Rat#3f2`SPt zb1Aar0Rgh%=@Zx~QcK)TBEW*%qp(EyYzuh$c(oZ!H%N&RxvN_|CWv=00Fe0NzBp3m zvWAV?;XkXcS=7pFMn$dMCkWJU2W%$Fcu-@FxNTlVS2X zNvdFnjv_w(YdDx(ot%sYcpR+Je#b$wUR&Hvaj`^7up=vXAbMg(?+dIr@YrvhymhQ( zowhDAVM7C8XRZb$Wpe?L=ZLT)7YQ7>F4vVyEh6laSe>ffuSqE%z%85C1*f)4lR~(} zsP=$>1feS<;8ZP_k|McAfP;4|PQorca7vn}=|qf7AU&;PV7p+#4PZV!f;dfr19!F1 zdy*(dE^O>mPc!;*p`@@X8sjaChchYmQjo+EJM6!#reR33C|MHXWyfc;;Bm8E&v;;b|!|%NM1~ zEzQ$`e}jV2-tuB-Q#f@kJyU;^5E0rp0p7yJhYD+_sJ_XA;~RxBGM2Ids6X5aDn8ya z6b%p+@2uobeHwLX)T2>{5an9KmutnSDAzm;+IiPP)>7I;40MWAhFwH)d9c%^ro!2q zNDYcHm2;LUEa3c z7%fE0nk(9_bh^3G?L2quQ@<7Wt0`+g8wS2o_zjvmfES|UISRiy8(t4i&gzFCtBC9Q zNTFZf&ff36e;1c$acve7CicA$>)=U>e!ye0-KE*+)!Tq~%n>mNxUJ%2S|5<^E;rBR z2KF|3*^_B&TeZJtbUhwUkTaOj;bQXnqGygy_9wsa646^}9M>$(RIqomw7?15Q zB$-9fM=Fs79THsVQ7n61(_Y5%(DO8TP=`sDaDisz*kcZyr#27Oa$1fWkt##V%AT@lH~WdD=p*CbMiwKln7zLY=A?5YRfvl4ANkh2RA3a z(~Cl(QFtg69tw>Da#28|7nu;LDWXwb4Qh&DhNdejrn}MOB#9}C~L&x2B4 zJ^OI{jd4By(8wG;?5laDR@@xPU3}5h)~1ARl)ham@vD#0+5XA*Qs$HXd4KQ`lbfO$ zgX4kmZrA<8PjGcbLOJj8`BB#6xm#Z4KECPPoaaxxW}5(8Q0j7oXG^aJ*B9rf-;GH; zzPh^LT}QstV_Y7k$PPv?!jhnBGG$2!C$1;NowsrO22@t)Tg}uV>ZPQaJvOg(s#|pm zjZn#j*nschv_H<-dJ0HT1NiJm z*?%LshE6e`E8n6f;a+mF(k#>p6)wkxIkm)0{IlSpA&Qn+ZVMaMKzn|?CPplRqz3{K1= z#ob$&p|pCPf(=SSKMhH&%g>|*_ZksTT2sSDyx+O5IW+heRxC(5*jTCyh$qfqC(RjQ zU>}_Mwhbae%1f>R!42ROL;9GO=ueM+GvTd zsvJS$0uH~vcx~ZYy-|A@$sv@{*=Qq@ful~|#e>K1Kuj4;2^GWE3`0Vuf z%kk+A?9Xusp7eMKesCRPgELIPi)fIHWIy|h>?i9gyPZGj8R_TgTrXehDS}Y%9c1=I ziMEyGm6+15^^hgCq_Ao$3dCglRg!HyW+MmFS2biaHoxTAFLsYvXk#|YHuaPQ$yveja=dESN^0dCw$V_Q1C#SovfEUSV& zHCk|d0yCDSgRowZtU|Kh+TO9i2payZsH<@7dGxVn#~e)t%og=>I9uzOq?eDT>6tyB zxDst>X7Qesvy@gqS}Gcb(u#0dTN!Z1I~-;^RM?yf6wcK?D_FPc2$iv|JWnMU!ISpH zwh+^t9tVx7CFd1v6P2|*<%b@M9E^U-@m4M6Jngl3Bmc{aYT4TBihJWPJ66fmUXNA> z19fq$zy9on?T%@b!L?En5{=&CHFcp#K^hehvF2JGM65!Dh!+dJ$z$_CdP{XcVBhqCBIsHSFY55Z!DPO zL(tt8OhonL!(v0xtYB>xpS|~Jr8f>rYt;Fo!xS$`{=$qoH&oOa;+0AREY;#mjAoeJ zfCi>14=MhbRD-&s;OwNOdIc2jXc_l|u zSmqcj89v4v92Mv<*jc_81Ra$UPM*}iD%kk4z=~B;k~&;c@*;(W)Y2rNzi1MvjV1wW zYm&h2O=3yDHI7R97WfyvR&t}y0=D(o;O+fY(i^=+ zySnBrxqQ5qNfAesJ2+Q6*_VM6vktYRp1mRL@r&o_`kt!wx^Pe=sWgits00$l1jHs$ z$QZ$CH#qgHGkS58J7aEa!h?pf{14uU3C?X(JON7U%Hd9!wC0m@rY|O4tP}%3)n^ zsUu=ED2K2Jmh!_HZiRfxP|!uCiUmzKRWfo_vdx23F_w2Y=j53{YMs}qPy(lbc-J*A zg2(qc!23!(P`7dxTmCJ8G$y{CQ%wPZ0i#DH6*S-d%o9nNNLIexfcf_uV6~wEL@dpk_At#_}e6rrk8jLJ$esw>5)uoSZ@kqLdlAj|ATzF-++?3RBb1^GY< zQ+*0kc?wf?3RAI#FIGcLFGGSyVpSe1SN5*AUJrw@f_>lOl2lqOkm&S{&R#8l0K@{F zy-)(ehuSGN#ba;RU!=cPpP`A(Wem}$7^Fin$l$~vW1}EK{EIaz?t97R1&EC*!l9}( zIJKBTS33B!ol)P!X1seleSvPN|ejv)Rtca9gY92EbnP)+n-o z)4OLpF|*dYWKui2U>3W>pjnm)Vo>KQxXttK=g5@d$=n$LOeK>v<`v|N+Y5}J)fU^( zqD>8G5H2^TZYfiVm8igjI5sZoey+RZH=BE~gx=WPK2*4*$^b#JYg=Om=ObQ8m5MD zBdj0kNddgeRylPCnzTaIN2e=+iW-ncAE%bBjzwg3Nh|(xa?rcTCUI6UzKD`-f zga_E+QvD9DK9}Dw=e+9sm>QUa^x48x1o|6x-N)aam}6vSBez=ST=c-XBlfB{G7)D2 zSnRsg?cQR-PH7lHrT#>{0*sHSNbw;dM-lT-_S+LzFnH;#YpfI^zQ@&YJw0EZvK$yk zjVU;Hq^HZK;fD|M=ovVw2k3*9ZfaikVLSl{0**AirWA2DtqQv0Da6H@-qc%PV{u~5 z7YoR0dYUo>FBvN|Llp!|&$bOH1=ZS86nrbvKryzCUB7y5+1b;4Gh5AHy`lm3 z%N!xC(n`vCKNk%{cOYX{-3_e{j#^uV6a#+BN%x$|i<9z|=e=@z6DGu?gZtSn_;tJC zGyMhkc`-IJ*p;rbHX)^K!V*zupNPqF@H9G@-JNnf_j;MGhkWLWmhK11l>$OMTX{h) z0*Tc63nvGw1(R)$#uy{$hDNCQ>54|Q9Ouyea57ti6+#VV2>JYdcj8>hT?ww=aLdPKI+}YA_t(Pw|U0loSQ+$uy}!0(WMBmR*?r*dw7hH+9*kKsLXp^#m6xnAAR@<*bNzHFmCr3uwd z{-sd@`$3zj%>2WNup)Ga5xkwtR9NGG^^X~Zm?D=v1gRf@gvfF!OtSzW3qx%S#M6Y>*hq^()i{|D$hQ#> zi3X=u8G&L4VT)t?3!Bm<#uX73wkA=wDyW%Bn^B;{_Kl(){`r1xV2qj%FU~IU==Zgo z9h>Xq-v>h1pb`4rW=ZRGYzyJ>=7lh(g*QJ1{9!&}9FZiRe&q6!Q6lHk2E)xPzvZ3M zgc}7!)#<8`kv=|%f3o2iKR=FT9<$-HiEki_hOftSyNl^JOS7yF_+9`?X#vWYyie+6 z?42rOahGxvD^)`*X0`Q{7O9w~QpCxG?M$H`s=1GPt+a*<4XX5}+O@v<&v)-`SeFdv z_v_gl{!Mnjxi&V(M9sA`#+Kkp^~U&A#-=hZp_ie1MYoGCm%3YOd!^kaC2(k^xYnmR zr2VhM@p3d9F4)_+hBuvj)krsiwyNr2ScdT>X5&|#`XayFwnfeQW~ph3`;vu?)QSWU zRwN-`lqf~H;`pgik$qurtK|zjR`+{v139{lHcc8G_ub}4 zRF->`EoJw-EY&jvWNDV=AZx5?c6G*GSTr)PMpCTG-MyA78L7^$*SEXZtWO`}8eucx zk{)Q7yWSY?bGGG?P*BV`xul4R^vWWkp=Ueqv$Ng(kWp>14kYR;3B)v({$c2k+YZiP z+`n?dp!M^qV=15f7UQRFs;lH7oiyM~j~zPlS!Q0QjOJy7<@|a2+tUymD7dvma(rce zT4MjW`N=YlAx7o8)7ftJ*b7`Ie-}uUQy6wk1UHs#V9wdrc6P?~_N$AF>zm)hc`p(HdX;B03qS8)?dhg+YUb#~G{t0$#> z26Z40vrogxP|TGbytvwGZqp<pZVry%toWG^A4d}fo;&)}<pm=k<;l>#6iW%;T~w| zI7xGbqaKM;Adk_~uiTOjX~aTEpD88SSG|Nw<1I~M7>^p(J8vQUw-9F_`;8xDw5Drr zu{=+;(WdZCx3gL9l|j3epxqGuH#d!67kIaahQ}x}O?XH0${kdG`SgJwz zm}isom_+4qWas{L`T#Y4m=H+Vd+8?<+-?+M!Cg!wsE1;{cqBUKEogm){n@K0d@_3p zCzOyI&VGr|&rZ|$F3axb&y_Rm5SJ}F;rNkcBKm;P%VX5%0?KORkuTEzjU77G&hRCH zOI=V@seU+Bf`>}EBTX&&PrEt%d@UaZ;iz};ohtw#%mR(U%L|J{%bLKTxTwkPj04D< zUrx|=5X+U}<(wF{-hQ2$cRmdPvGQO-zKjM+&m9I9R(JLYW^2{@!7`%5*M@Y=AsRUz~@sXkkejEReia`=o|#wG;O#}HCv2joNeM3br+Z_rks zP7JkAdwJs#s!i2p*D*>fWH|Xf*u}T46F@@aU%gYrp3z^t3Oz+Ara~eg3+1e)p?vp9HYBj5+ps7h}S(QbSfO3tYNL%)LRz9w~z5XMg)geeyf>k>m>*C3?0Hf{+nT^ z{Imsa3;R=w4>r@4)*Yw2nW&uvO>XhOz1=gh-1fFh(vGw-Imm(zEr1FEbFm5yhW*h$ zN*w>;D;q5YRJXqn45F~z5$A1e-_zW7J%Co91b)h)$k7jdcA_uap0E8}duKTkI0GeF zJ%LY1**;5?L6Zu2;w4DPm15gx__&0G`e!3xzb>UfO+$lYIF1JyEEwVO*D9l(Pa}m4 zN;i;XB00N|*(934)hplj5#tD7gfx7!+gnDCkct+9N&qGlv|r5@0fx$|wo%Rf|L#r3l1KLg?e=@@X{HkpZV^56cyn%efr4RuK)36>(Hcm6jUH z(zDw+DVy#s24`#{X)+|R1+ED84xxGdJb!X8BZ8~J4#7CW0B3U0Jb!?aQxI&p_?QxLi!sTTGc=@RNd_QkDuF1BPCW+57J8ZLM2pm~%(9kyXk6%`i7W$bTeQNpmC;)1;=yKdteQ;b{h4S?_eGrTXYGxVfhO!qhG z6~|=)@T%Df>U>Ibkw>GQD$G{u(^!8bqaJ5XdS50!M3K@!FWFO8;tnH#y5mtEUl5Bg2?P zCPn2GMsG~HG6)4A!b0>oWS8fgvz4i7Lt(BhGTn}pob-naUssqy4HsGv`VCp(H6EW9 z!JmWWjSWD_JH({yZ1_5wnhS;ygkNJGX>GP*$->vlP*R74;T$XboU11`!h$E_14E`e z0xVL2IpN}GE|=@o^20P#;a7CmC@P0}etEwZNA`rcKosWDtnD>K_s|p0G4`Es0(^oW zYydUDKB$p|A(z4qgF3FjZSchjTe8hJsPo&I%`(HBFJTtUTH-4Bu?xw8qVCtygyW;Ernf zLa06n7pU!?Vn&)U{Yf@= zG%mnWV=WE`lbT26c5P0dG`#ZA{Oh;l!$c0?l5wG8?g0W6NkWqA`+S^=YmYTr54#7UA7O1aOPH+PXS6pGKgA5Kc=g&)}n+ zPSZYz(417nmLG#t1^56|VPW_n0uhx1B51t%64eI)hlgjhdH~G&*q{*)?%=B`)5@&? zKdh16#vlw!jE2;r&X~V|*wSp|^X1t98^5bV9`{*9@qREqZLmO#R=l-Q$?MgMX|5$O z89dD~o)*IsFemin%XZvnA^;OMUspc&X1g4TrL8}fq#6A{jT>-l9|l2fY2L;+617T> zoI!RDEzF&rkSp^R=d3ngQ}ZNcMBZ0%Y@4md7zs3mb9fq%yfH)(qf_?+Mi=qi>PvW= z5+dGK0gks-nC~q|%Q4P|WQY^DhHLtfps+U!E+Zfxfh)cpB0Q~9i}PhQemKl=Sz3if zL1Pu_%aKON$`X+RQl0fH7!V;xaIRyKt&D#KpQ#huLj_Q&wv^FS`sS@WXDehUgdc<> zARw{>2}cWGp?tm#3VZv;vxHka0;H)5cJkGN)Mv+outnIqh7qQvE8`1bEY>pnPJqBD zP%cokqW)?*qZ5g4r}FqW4?ITTKWvv*KKA(1*3PMd`ruDWC4qeog-g)Leqi3C#s9@G ziZD_^NLd*u?^P6^;h2HcRF|JzbS&^oXC`l3%Q=p&plwbV2-%7ebS8`d6I|V7m703} zi=@h^Q-&t_C3^KbZ74_+N=eNPrcDCqO!=idhQiBm|Dgr6X4nA*KA4i!ruFQ-qU7 zb&9GcRw_2OGK{MGzf7pj31ymd2=J}>C?~beM>)NHKKzS5AB5H7G#|tdIp~Saz1DmY z4_Jq>kIZ>}82wHdiG2*zr-=cxF|R^&POx)g=5Nbke9jTaFD`kQTKt-?9ju9^UODGu zV41aRQuXWdW5t{TXwii;M#!d(?!SWCUe}u6ce>Ujzi-D;m3MVc5$vvqbKhrYD{kV> zu{q{=<7%9b|`;hhUjr9HO;Zd4w%98`3s*sLJf!!TY6f`QRzZFbuq+-QRBh@5vZp+l*Q3fP%LgRY5 z%ZG6;OAZ=Vbc7i&GY7^bYQAj{kERbyQ^aLQ;Zh!RNXoks2Pb;`#U6m+&6oJQt@mQ= z%@}(^rhCJ%>hYGtlLOupILI#eJ;m5Cn5T=~u+H4)@ zRVvs8M`f^7`{-NAaEb^GIJMtg@vMym@8vjcjL~g%CPupDh#tVH7VPSMoLsU{cfkmk zg!!rfTC>JGD^kTTVf?}HF3nLr4x$Ikfl?*|mP;AHaWt$Qjvi@FO`7vCeND@Z^DYtq zQME+YOH)aUwD=*5P|!?&A~vB`ORZ=5aIM>fVske3d7pzTy0V$ca_k5$baVMh`mls0 zN7J>f9xc?T1sonnPTGHL*GRk%(Br>3qsQ{sQX!r0T2M&)Zg9_IFzl@*c!hNXHFPNj zv$I$+o$B%AY(C9h#VK?O08TXSOUmM!fYL%~gQ2bD8V;jZ%I6;hmcDp|Lp)qYg@)K~ zIEW9>^21vPgHEuE`;t%?q-unX?#7mJ^Oaj${-I(J{VSC?G_gFc^vK0;FzA}_vQh|D?=UU;98u*mw6FpmA_5YUrJ;DHftWmm zk+jt`-*6p9=Og76+Tflamd(5%)rQRry^-i8QDE0c)=RY{cnQzm7VIJ36CHPaE%yzdP#ow%tkEoH1is5>AhAGVMLeEi=`X@+wL(a;*m1sIOj z`VQBFQ<=s>;3L0CrSdT1t)pE6DyPoq;6ruhe>r21!xv#-V7I0(!+pk90aIB7#pa5sSpEG1yu8o!_tV%01O4L7K-l`@atvV+n}%oxU^{mvVJW~#5-E% zW0#_aEx!7*Qc`Sz714*)zbpl?_+74l@Ytm-Ubc(0;br3?q}PcqTjmR!_x$q)YmALH zqu%*)e{K;ajCk& zj`4=@=LHi~hWW=0?7@_qPZ_xQMP7pbx0E3O@HViyr#TBbAU5Ji;Y2Hkp-6#U(i{lz=kpTBl^u_~tNiZKE9N%Iv?;#ZU113JR zSL%jyi?FfSCwa??DJq4Ff_S65=Q1FoA>3kPP{KR$Y}R%HFgho)j^vGWxgW8OIgvzC zj66A*nu46sca>PJ_Dc|5??tx&wt0@cw93@uBg%QZI&@KUAkH`oTmt%uQ$ zL4BFcqDV^B!tL#YBawGz|2LAPNoP2wLN$W#I_X1#ZZEO zD1|>d5<^l`06PWWVO04+DQ~YcD+u&@PBbNwv-koLHL$X8^&9(EUwy3PQM)RDzt#bt z>VVTa;H(ZfuLCaXfRBx)eQY%C<2E9(;p%CnG3k}U2c_^yDO?_7O<$aoekrW(rCB@{ z{}a(7z#~h%uN^fUER5!8imYVJ!L80JfQzD4z;+#wP%PGg$NZs*Cdb2q|1tj1IoND5 zc6so^EziU8K_lYJv=@G?5-owlULk>H-3gv(V_=9sG{NP14|`TwxuTX5fEwPEYY^=i z5^$5%MaKS?o#YhO>Ldtqu4Z;O<0R@_sWgo4Wz>x=N?8%uW<8{z*}>_>*ArgtNsnO@ z7+mk#-#rO%CC5EJ{m|nLgY3h_;JW*+!&SkLhmwANJk;y0dI6lbpN>BrUx2d*$6x!` z9|s?Mz4IgT9Q3dCnF@GkD?qOfxeIu>D|ztMAy;1ee?33y9iL`A12*V&c{}SE?-o30 zbjjmM+2B+E67L$HvTHtf{qgFU7sLi1^@eyUrdMN&w9XbT^{&s4xpgwguD@PbN57n1 z9%lM;^J&oA&c3NSy&8UaeZ@bnY4pBjdP3`i#L_(7%?nz`=a(NYC9NLx59ujyYaR8^ zKV(2Ddt<#<5*5p;cU7=s#OAuepKch+6a=9oltSy^x>qJOd`r3&b8Hr@YywD{1QRn%CZCFe+GanMDpLF-dIbsvH7q`n=tDB; zDJP`H)N^QDQ}If!a&eU+Ude28Q|XGGhpS!5M#FqI?te)=Ej~f>ZC~`n*ynNwQeNL? z*V^l>6+v$qbhiC37YOoYe5g~0PX)SZ$q-5($fQ=<6(O+SZYEu^JW&p#pHDNPl=2)M zO&SsoO)9_w^1$wZQ`h4ZqHXXHRcfq{gwZrLol$aQngl5!R7`8=TD=p;3pCDV)fq)% zHW1vQJi?#JP-gAA%_FrE!xGKs6!gZ0S4J`zw~0Ct>?~X4TX&!_zg{sl!Mq!?qZk%^W0K!!mra(+PWVhjRjaqdt`n8dpbR2r^?#C!UFK}2Kd3L5%u z=5+c*E)2|*6Sh)MH3_~7g23O2ii0qn#>NxhF}Gj?#$qOQQOkuLF%t=QBX2nx&W1&bMTwWmm>C2FD1)^Uj8{uYjVCA zC_9y4bkWD1wPb@VgHWr<>HN_g35kLtezt)6W-iax*JcQaxyEN=CEaN@-M#uXdWF;5$bEMJaS%kjP5(4A^Zu9t$H<(72D0*eW;rjYFSefM?(#c(65 z5DAJ#ToV`=WOJhirXHm;DrS5*=^b5!W%7*ao|X~jEplvS-~*;+GtX;k%a7BSSf`f- zEo?i%Q3GZj4`$yXRI$oRu>0wYf71#)Nd$(okaEPZF*$JcblnhmdbF+dj$!3d5LN|l zk;5;WO+F1~QL;+lbq(!KOpV>OVhvnkkY;${zY)Aa?-TqG>a=_wB)iA;9NE z8Bz*|LXQ#`7vQ}rSiSGf?JE5+^BTPI%3pGhbn4w4u7K}0By?i{I6>IK#Sp%4ynCoZdEHX$e;^&x@ zBm5UdN9KDiuQ&o}Wn|}P9K45mFF`k`d*67|_Y616ysVi0Uh-3Oro+766t+BHktE38 z?4*>N$APbq8|V$jY=C%!`%zp$)6y`c*83V_0B76{Rp-Ggk6D6hKfb;>>K|V7*^~Y$ zzE-I0_nVKOPfpHy=QsB7aM2(x{_}YAhwtP#zqmO${fsE(_nS+ei0<>;bBqtZpC6{k z$>-A|#os}u$i*lAP}_0u>_!hFQy=_MYzVI$pI?+R=+R@MdQSSrUO*oO1O2JcNF~ zIq0AFt~3O_!C}A8@8|PR=NDhk@f3D@lXH;Cu8uEnwr@C@9$)@W0Qb=r>U3^8+wZco z{`t*W@A~kg&%1|!onBmg>U})!;l~Ah@TtauwG8nN@RDwU)8q3G*B@h~&vYR?%3>;o zFHyDrjtuLZ2hpt?pdao@Z+x}^W+(M2o2sF}@KWHcB{NM<(3@IyYoHA8}7AL>CHxqs@ zbI-E&VivNJ7Ru%FHXDgb->}yr$>h4|_&&a^t{V9ckN_XXSAf;gasdzP2J6sTU?t~% zd+u@ga${C~v04G=F)Ly3Qa6Dn%~&({AR+- zOWDMKfLfH_vAu{yQ&t+0mA>o&zRj*C?abWf2YtJK4ZUoz0Z^Z&Zn@f6Ir@gtw*_r1 z;E^onbU8BMXlB{@d6s2WHka2UYT2y)SCJbV<81W@v$e2h+|Y~+T^=k0bHRoX@u?0lw1@!$=p$vcf zbE_AJe6j2;n4Tgz+$#5>;lDtCN5GIzY)j0ha5qLcaEPt-maqxMpdCK#!n2wP>s#<< zF^eN!30sAnnW10_XebN`Ip@Xr@0W7fkG|BsVjYN>Wk>ec>B$j<>_{*99zb?x3m+a^ zcO$+Gn{W~V=j99~?0&rbW-+1DPs zkv0?hqTXd%O!_eBEfv8+-cYn`V8=LDFpYD=x{3YurE7mfeqX}&H$dV584jEsUw^z{ z<2~(tpe={jIK|-aw*Bqe-;Vv=lGfqy(;z$i$n95b!S%E4KRjThSSR~|UE^Z;1DQhh z!c)6$=1tx4V+zLlFi0zlRrEdn8QkH~BIo-C-Q{O!!K{VtG5uenU;&eDBRbr7yheWG`p00=9 zpAZ}(Ily98?#H!R_(mekv@IHjPUbEuVJW(5tHPE&&qp9o#-krPYLM_%>aKddF1&u2iB{{-eIszMI%mZ8gifLGZk@!fT z5`qU1*XjJHj$AAVJ9_aO)`o4l0&nz8cS;xgU>gox7ai%zc|2o$COSG^Tc9fn`dLc25oa@9cd`)ZW;eG>e0~Vx0W>OPiH9=(jLABY<+wc^Uk`0< zz{7ZE!+2%CBx$kcWH4y7)jREBdjii*E2YMsjc{VisRJ3*w1^qfl%pxHzwwtchyF5(o;$=r6Q zK|SLQ{5|$ej9STBNgWFPHH??E5e9IIeVZMmXXe_-=r6*|V8+Ro-#*JB}3a}WcgM?EC?w^=OP2l(55)x_0GhxufvsJ z^0L@bY?fy(&RS(sFwiWf ztTD_jWBKM#y)_cCBaQZ=#~VK!E)n*QO%xVHa$y5VyIo&GzCgkx0gxM9_zTnzkLvy_x<=NC26C z(z(eJN)_=@9>GA8A2v{VLW3F5bcP-}Yk>E%QYlZLN$ISOtP~9{)zAcz7EWy+OeyT| z{t-5oz_08j7HhwJ|Qma|39e&^eWd#&!(Np)xlj=6NZOT3#NcS*|57B(uwnU_1Pm zsrMNQ_5%6iDSco2K$sJg{7O_aTRxRr;=Q+fYXOl0WX?UxQdI#;g-xJFW<3uBqXTu1 zS3xtZSN&Fa476Y~SxN})^%P^~`Gk($TeKZ6=SkGK>os(1!rWdKpxhz@#8D>WzI*|cOwrh zvmf_J3j7ER6BJ4F+;m@ach~-IdD`S zM`w5BKp{3(ak(brX25!Z?xF#J+cU>{hcYY=sl1cI76XJhvuh2;z3qa#oScI$@OsJ5 zDTA~mIDy#p@j1fJ1&0_w`Z1bd8Vivx<8{pumxNCE_v`6uF29%YqO>l)a&F2IPf+n4 zBEOf~${fZy#Gr;Fi&;Z1M}G^91)(rM8f)~!=t6~DekdyDA5BwyeGpa+H=AcpI%C0ruCwFCv8jPzzp|5**?2_*Hd`qgY{E+C%l?{J!9 zK_zo|1W3>H3_;g?_==QfBgLT|f+d0*$O#X;tSRfn!07-SBSz?W9_BXZ;I=4B%`7Uh zs{)qoX))tcCQb->)eY6C-cY`s>6tn6n?g(Ra2LLNH-BB-+X=}+v`8m>ckdm(WA&PT zZ-!Y#R9Gp|$%5~ZMcgNsE51Tcka;uKKWHMdsqv6ac!Cc4aW=ODX?FWGw*xGas;*|M8SZr`yY z);0Wvs;@l5X`;T-^2Ks&e-IRT-f(p>pP;IiQ;h5d3WblIF|Jtdrr;k4p;6>m6BgwL zn&Kn-+R0wK+3PkUcm(!6ri~=kp&fuBK=rrg8I>nCjC+1bXY4m9;KDEKOOuZb`xW{& zWbNz+;ccuA(r(3YDYQsrQ;fCtra8}p0rtpyoj+x^}A;9m)!{6}Sf_JZ1}o zfR8k_dLxmSB{JS;hTC9m$5qJf`L&P$pO?66UQBuqU?SK6nPp?MSrtDEh4^oS3zY^C#4xy1 zI-%=Exa`wMMZZezDjCR!HECm-z8vR2_RQ+@akG|WY6kvKAb!^JBFdUWx5Oa8e)=0^ zLP^>73^uQFPnz4zA$2UEz25qC zK2aMbcCRDQ7fp<8P_0WLA3}&ZLP?|kqB$TwtHwJ$TRa#8BzOiXDRrxs73~{nH(h%* zDHyi+BJDWJ4@>!>Em}k9ZbdQkUd|KRT3qZC_UkU?h(B__Nk^Li59RLX@;;HtL>K)e zGgBT3oxq0itznI*jk>LgKo@fisphz7V%V8u@jlRKgULjuF9+uUG-2-KLRv;b;!E%f zAxV`wv#LZ-9FF*JfVRM~r3;D;M%P{gajYB(0TLi6|x zF7oQuj~?qen;@O$VH#aGLrA_v&N<7Thx3m9bhG(AJ0Aa5-~-Exf#}%ZZnj#X{=vc9 zrW1a7s5+mXu+d%_$4JI0xkz52uUl=l`G+x0C5tZbX1r>Q3VX#H42F?67S#ORNQj~u2x2tWU!|9yABmM1KxI4;5n@0_b{d6<0 z7goXQ=c$C{`lnTeOTuX0eucXzXx(4>TFT26cc`BY(=MLOL4Y&88kfu-9L`YxdL3+q z-&vDKRoPaBbrN7={x^M{85CiAgCL| zD%z*5to7rP`Gkw1bs8}Yl0xV<4)>fh-+Y0R7FxzrcxYt_m{}k<%VBdgcU)-|g{H!yIvmYGcL0BQtie)S}p%39Hb!s2qi z#O145w^T-P_LCgLc_a{$++5#dXt#RAgwk9#Ur5u9si9GjoQiR2#)=BGP1;wE2QZM3 zcI+8qK+b+?2(!uQ5A-2PyPKc5^-McWf>Uv;Sn=OlNht?9@Ua;i99q`I4jSc0VW1-& zV-WAgIa$L@+!}y|Dn}SP%mLPhEw(I@W6$pFbjWHn%Q#}V4kk;)U+i9>V4J^*HrqBJ zA_8f#kExSZ`C=lxVeI3VogwrtioWZ-o56y?;4Ifx1%$p7v+vs%OB;aGNa#L?QM~Fz zF8AdS8cubu;rHc;*VUFK}Ih9;G1FX$m~$>HjuXzgC?fW;<^XD{2#NRf+21;25+F=sg5Me)d)+<$hhwC#=xZ3GY?HI`Hi6L^{5ZX0o;}#Wix2Is+%Iu6Z%itdjjMfhJ zYcNVWYiI;Y)!R~%E0f^h$=8+vxJnLhZ_=yfn7d363pqSdVfYcuz?M2siy8LQkF{Kf zi=xh=3eQ60VwFFac+2C!J``_r080hx4q$!6c>XwglBX)>g04k6z>71`NQl#`TFFC&V~X?ghsQLLHH07n|6hQokJfNqJ~&ovB|D% z=?tHE#)2zw(^t7flr$jNN;9%S7H2d$93-?x48!=0X5`awN(?sVp76-fZT-#|_)r5lJaWcI_ zti#dWcnU=!9SQ+r7#H0f+yu7aW>1B72~?WXV&CkE^g8SZ?EXc5Jh6Jxk@ z7S+@roV_$h9x2npIQHXU|T)M3# z;;TJ3isY=5vjzS)ZbhmgU{hz5Ct|LKI_@Dv^PMTJbDa=%k;Jh#EMTSurJgT9Cm`g4 zpa{u)bHEkT52zen6{nCrpsm>OT#(7*aw~l-ftP4kB3C;2)6*gjB8j9(@{)jo@&Q$a+4^C**20>Rf=JeGV-?+GhInI#yn9{d!5W0fmA|9ZwK+fYwiY%(y zzZ)bA1%Q^Zu(^{41x$R4)5Fxp*t%@ZUe3DldFC+f*F2bstEq99ruTAe;9TuXOEN2O zm^KiH>5C__ISv9*WOz>Cf-wHTU&`W+t)5Iv%D=H}`a$@2dt04=MWflm_YQ7vEZ1k) zO0umvWGEKO8k0j$CEYv~OPm{pGq@Tq?qtm-&1R}Y70gyx|0Eh@21kTi6GlTQ7ZTY- z9Cd=9oW-ZVvt|vets$ zaIto)ro`x}h>wO2J=O|kGM%r*G#xh9@?+-YTlqPeJ>9YR!K1)RQPDMfeDf!)3Y> zYh`GR7aXt^Q-LTQXD4aB;Vd`YvkgLV%y_PjP9R!F6n>-Mk*C%t5yXbR(c3f(0w+fg zlty_n+w@eA&*uy$Y)~O>gpye1j5)TjunIbAv9&$2Gw47!VXf)d!=Zs^pBz3}_IOjQ zpvyt8+H>dHOqxCHb1|RTr=UbW`?|ZFAo=Fo*=3M$+klDV2n$Rsy}@kId^q9he^U#c z{q!KX0_QoMe6y7!`s71sx&V{LFJlm~R!|g#m(Gm!kU)x6>mvdc(zAOyIvYkIR`&!= zdAfwlrB|J}?7$Z8V!}pkcOl1N;BNe!B5ZOrA78p(Kq?_TL*FBaDL_8bx za2jkYpOM9~6hv#6T11*7&FjRv1Q|5$Qr9+exl7f8QWI?|DdW2?U$_&E-8_e`r-dwz zkAEy;J4=OM7RvOG3p#PT)M{n}_6FKoAI=4bS;^7R!H^xXcxQ<7#FBY7_?_TrA7dqF zJq(rY6VNjq;VevtC905>jPMR5gqOgVCn7%`;y|-ZeBfE;(U2SHnFtxx= z$rYcDP^;x?nLjZ0tA|His9#zXgq=Nex}-{T+O68KId{_~3O(Y(1DO#Y%vnp%tUev! zvk}bO_+HKfAOL1fE993^%}^|i`4lh~+0pb_ zVmOV5`x}7=2$HRMMCZzc2wyUE;T~BSe5>uI)FP6wTw;`!0wW;#4agSAk4_*Xlm4(= zawcn_iDDXP3Y*jOG}q-$o>Lc<)hv!UAnFW?AOn1P?w-gjNt%O-sj$swjwselzU;Yx z%cnK7iTsV+67?fYvRojuI5R}$l2dReW73CG-rh=tq8n_&IApN$eQdKq7fR5>p6112 zgXQCtoyMH)&Vtz;j(98i(?0IS&@{1VA&pp-$YVTo5efJLIW&79^wPET1WR}dUSeY< z$NO5fd{fdu+a*UaCN-^A!OC6+vnkPXauw0ojR0Sv7>1pk|HKx{0KhLjK!(6DEq!pC zX-QrsiTa9U1a0hesq?e$tz2kU_2w&&(4gT3HOIDw+*i^<1m;k)X-qN%-?VgkHTyEV zg`)__Fk5U<7ko2Xq1j?vt7AMp<*HvuA1x=Z1YHg!f+`w&s>^|+@k+DEvI2WVzboa5s`SOdyQHxb{b(rU134>_#LDA97Tq*+goc9gR4q4wXOOoV7UgTk1A z7e=O04`>)*NcH2wY%LS$xyW~CYwJO`{r+GO?LxZI^wBdKqXB)uElW1x-;6rZ{$gZ< z8T7rZUbO5$nvq$GJ=lXRZpSEG8s34Z61NxQsEemMM8zitp|A!>ugS2^W=JJ)PrxF9 zOg((%a9WgSs)9t$KETcv2rs3eeI*QX((H#ONU^d*s6Nnp8MsW>-oq(Z2sHUu@v~aP zWao?YOM=1U#k1B%2@PAKVa#=&6RL6s*Z0a%5vQ=%5H!P4TBy%Jsu#~{SJ5}XDnB#w zXVNJ!z~)6n-@acENNopv&=m|a4-~<0S^>%Rj*M*!kOm?*J%pF`BE@ITQBC=w6U$~^ zYt+87;~K?_CJ+n91TP^1$P^ldq58NL8z;g5GfQAFL{%GtI$HTosHyg~0G+rIr>-bF zn!^MZ-|`y8DTbF!CP(%SeLyXSa~X2+yRa88`W{*nMjm)*mn?!ySTziW<~E?93Y5_V z=j`|#ot>VD!%V?MM6Zni~ovY()p zHdTXX+OB8H^)H-+Im3&$z3in6lP2Tj>~5I-#P9(<-4C8Zxx*l2xx|SA2Mr!+ zfmz@5(J)t{NNl2)<#H}rNO8^q5sH#lkU_wuojyF8-AO|M|6~Fj+0Y?XvI`id?s*(j;XsW*t@+5_oiNt6+Cj+oEzGs~6H zIy0Q>yD2O?Agvn;&8d-Qv3z22DN}kmH+j)}D3imn9`cq$oL{aqKQ3}N)r+#l3>;5_ zsE2dfS9Xf8nRF;L*el>;)Ckq#GorY*8Tm3zQO_IN{8qs{Nk*)CByr?wTWvPhYba1T`?H>tH`T2|jMXX34s<8BAmL04uHE z9E{f%rSPr7pB`t~C(I#FaZIw6op$h4!aqFQGyXoJd@zRykvUA)v;W#vp&Tuou)}!` zB2S;Fbhgl69Y_pooZEuwE5*C5PJ`&P1E-E{K!Oo?-N! z^)pjp%7=|n8o>JOMYaR-4FRlyLPJsV#Az=HJ7-6Q@EU2q=moRce5Xvkpl z=(Ywhn`w$C51L9}G+Jo~$}p55&m1ejo&|9c?AGMay&0Qdg%B1vpp=0_SYW4~P;o>% zAJEHtfOnqm9Dbz^)x0%$?4MYSs|T?h$QqEI9bqCd>MC^Pn#;@K&N zJ3ez(AfCpw59neR+X)Ui|_oPjv_=Eln)9pXTbWwV$mc4k}PPdG=?Nw55romzs0 zjoHOqaqxrApjXggrw)V6A|WylrrDY$jqF20U+k#(t4kfi(8BnsZ@1~`Z)pzf%N%{g zc^K`Db($Qk=)=#zkNJ7Xzgwhw4+CDBQ?bE_&_*|c!{frLo->85z?FYS3=&(2Y{RGS zrWb^h*D>~N7%pd?!eUxY;h56dA)p4#=-rZ+uuQ^+Xq(GWCNjqwj?*494YEmx%FBED z6+0eMyupJfmSq7eczR49?w7(A?cREN$E`SmAV)_jukjk)t9V1B36T?X5fa~Xt~gq2 zI5D>xW4o{so6Sz8Zuj2e$Klz0*+m2_Ei`8!V#&~>c#<$dtfSDt9^wv77oQk}Kk=N3 zU^SjGAp}l|bW}b;06Wsv0RK?4)9IXU2Y6XhO1v_JWIaxHC7j_&OL2*gf~)-DCUQgA z1G84eNMraIOfnSG zz#oV)Z8sAzOp*nmxS!Y?3nE~11D}XsO@Dq2XPe+=lMeZTJbWsR8C)l}P}&Y})r0~b z0ExZ40fl(}!@MMk`3K|Oc1nVS3SPMmSF(gs=b3Rzhxt@phS5GrAEyY-4-kB4AwA5H zQkuI*q^0f*>~=l0dnh+cXCG4BsRyJAN|Hv%ht0OCh-SgVN80DCg~;;z{~_qzII7v65jB@zww=OtgcywgY_{yE_8tM4p-pDeflqYh;)+oyp=`IZH z5{m$dRkcb6P-k5(fOVpKB99VMeR$*%_t}fcy{^--sT6_&w$MPOaccxomnnpZGP36h zH_aZVh7@_u@mkYE??t@L;cQs5co~39r~E$A%#|IfvdMjkN-~9k&6?a#gO`sO3bh&0 zmn@Z*{$Syx33qbl%1qlk*_KOHo5cCXW+{mZWPvoNt{yrcsX`}za<5bxgM3fBa=j4Y;fPYl-MXfg~xTj7^`H(c70 zM7;;U{)N`wnv6&%g)T=aZh=X6z_r^Ac7X}~v!6D~f|ybOlZWh;>(?yx7e?hZ`}s5b zi5u4}=XTg@_WJ-mxcEHHvfja-e$A#^$i_IbzEG1G1Q`C`G;^wxXW=|>KE1olek_K! z36k=V^qVCC3FYaoF6WUQe2JeN#lRhxWu*?VlZIg#yqQqW^kYX;iKR_Uk!Pt6Z^oMIJ%52EG(Vh9sgyztMkr4nU!*3WD4D}&QG@Ah5G_zS+OwW;v+qIKN&^oaA z;}7|iuCt{R)hP^>X>Iy6fd;pd`zmec=2RL!Ub zD#^j`No{`_XFpZ^r^zH<6hs7(J*k^^Dtoo_Tw-Gm!)$Tn7K}Z&;9OcVMV=P@8*VzZ zjT!zyAkd5Y)-UynAEH*MEH3ZY^fk-74`BnbbE@pSLJA@Vo!qqnI#VtUqfuIJGu^`< zz{{FK9PP)V7RQ@!wI(~)VD09dq*!k4K&!jefVZnnQ=;tzy|sL@n>Hos*chj@DQi1= zT+NOp{V+`j`1>@vVsM}kcrk@GiehC{!_mfW#Ey`Z$$lj9U;c2i+NyQ!iJ%g0l;~sgp)vhh9rKBLfewJ5%FYek~h$TYxRdDi}0)B zsUio?5Odjy#>Os$T~xx5Ncf5-DA+6T<d(($zlfel|HJHRi517Z`r~WsQ z8D0>V9wHebv0clIz%2u=MejV5tXEL4FN{^2+^jOZ%W_fEGBfOGTNwb~-8?9Dkq&}d z=nRxQo}-EGD#(jKl+3Y|o?~3ksct3ZiJXM$?;ip>`{wErZJieKx+m<-;15C-R5NJ7 zB>I7s(hnC%`F4CglZ_tPV3ew1(A$<@MoQgS}M zGc;RnxFv}V6Uy_b9IBuV-AqCl^I3uJacGL!ju>^ZH^I{-BTpNH5!f^y zk9|al639k|Mk^lbh$OaIJZ@uho@xSfSpF6SV z2qC4^iBY3uq2FP*pbo3FrIv0BY`X=v(*oOVfwi@Cza`(h7TEh17*9pCbiLCCZ0ky= ztuLLn&UD&((?0NR{pqxIsMFRXo@H$IsN2?~Zd;GKZ9U>~$Cf(Vdem*3pl(}_c+;_^ z&bA(HxAkbdtw*e)W}Vw@J=$*T(RN#pw%dBN-PR+X8fp1@TaR|ydbHElqn)-M?X>l1 zr>#djZ9Up)>k+T+HXFaw)}x)a9__aEXt%9LyKOz%ZR^o)TaR|zdc@PA&BpJx^=P-P zN4sr3;>puyoqKIP+H334UR#ei@wU|2)}y_)9__XDXs@kDdu=`1Z|l*1TaWhJdbHox zqy4rX?YH%4zpY36Z9Up=>(PE&kKVQQ=v`Zn-nI4UU0aWMxVAY*@7j9wuB}J!+Iqx8 zyDfFL_2_+DkKVWS=zUv{-naGWeOr&-xAo|KTaVti_2_+DkKVWSsKXtFX6Jd;x)s{i zsm@kguR2?8-Rf+$^{cbh*0Ii3ThBUMZC&eZwf5}|_Mx?JJmlIk4(%L7I}6dyL$osy zoz}j!vk|R)M5mRJ=(KVYomN(&)5=S9TA7JXw{_lH*@;doKcPpWTgJEBI&ZKP9OrdB zL@alc-mW|J(^D26xs#J;PQP}F#A&0LbT4Hum%{a~Gcz)~7$F`+wN@NG`~+lY3mAWo zNpyXU`a0KH-~<`j8JFV`Pf{`a0s>+)L$oqH%YO6j-R^rmuKkL@`SCCASWzuO@idd< z36Un>KYl@};evWsPMYjfohkzn1LGdv1bs*s-4EXphk7-sR;&uPg7$=z;K-+Rm@I%Q zUk2*BMm^(qnX#cPL&)x>Ym@YDhoeRvwhK(1;1uSP0mdESiwzDMM%r?0H(@7X7Axg< z_PX1Y*VL#0Lee)+K6QqXqK)PlthRQ6sfpec`P+-rR|3h0rt}smNwVw|;oxi+&k85I zf^b5sdfxRfxNDgx=<7|EJQMBya#1TwE@BUVO0I&KNUkeT)>{mzk^>Hv7Gg(SUw4jaXi^pg(CUUF(h;?wO{TQVH zBw-rGA^MO>Jmu95Nr@ytaAPV#kyj#r&3d9Zs0szj`sM8MbIsLTB=P2W!Jq6^hBVzO zLW`r#h&AL;j*jiKOppruGIsHYEtt}pXQ&pIaywJqCD1hYCag@$wD!n&#_DF19VNaE{JsI?u+fp+~y!cDF(U> z%E}R686u+*>I73|W158Wd^~1w2bng5O6g0tuRzp^~x?s_8*Vz^=FaAm} zWPLV@h`U(SX%7Vfo5N|v07@&uZY@wY0V|HOp#&@n12)hFkdOJ0iwPJ z2*hO)U?0_g4M|uIiWQ!(xpTezacnMdwZVj}w)hE$bzN#Eei>0dlN1LyIzI%|!(fB! z+`_C-k5g{Pb3ocsQEw$I2?QK;RQp&q!SNUK5@4#xr-)f5tjcX>Y~Vjm@pu|4=|>Mi z$_0LF!0*66UoV#Zl#7!#&b~+>&$bj}W1G-R^bP2{!*^{^4o^o1A zurFog7#mqDD|UD z_kD-StxWh<_kFhoCN9YOaV_rEv^z#Q@NaB(jNVko53GR}wpc!2pS)8)uFP}{uS<|? zr#I@V_BrgQrY{&vUEErpLXM4%*hTR5UI4GxT-6L$hXXUqEdK%js*BFj2sn3YdxUL? zP|oBP;q7s09H+KTXnn_em%lArscdWJ@in})^L6-a`u^}~anTakLz?k}H1~-z`tCvJ zpsjU5?ZvuH()sereY=(QL)6aR_FjFa3}#0Q6hZC}usBJ0{1Y0r>@$nM1tTdk06^=?MJAZ%UH2jQ!xweOP3!2M_w5REQ)zhfavoW{dh+#N_Ics9 zOu~AIeLUPfaZ35i&-rXrYx4WfElu_%D|H|brQb36dW=vfRFvRm4y8ZPUSGcHENcfr zJSd5DQitoDL|Jh- zXL+9vxv9rn>icBo?gyL8H+005z!>jx@(cJFslPECtVFtwTd>3d_;U9W(7MWTTQ~W? zVqF?jE2B)qTH$5nFK84W9xQKRl`wE&0omrNRi7fRe#}0bD^yEP#4+HL9Ov5M%Qs+O zZEz9zW1%fAMGRzL?B}c(POBIXDo8~bS0#>+p)P#xMaq#~b9^>tX6;yRhXD_Y>94Kf z>E2kou#jHU?x8FD1eOj4=kz<9!F@P6IsN^sgRPYw{LGGPE@(*5tXsl6*Lo~HRU;z1* zyEJ%#$3&d{6q|D^UKU$m8^ehP4h;!ehRh4wJNugBpHc8YrZ|X1hScxPXjao~XFc=3 z>dz2-do=DbWEUf8-kb*E+KMex9SNs+BgOKQ@w$U@4xVgZ%V6{86`aL2#EcuI=1^K0 zBh#vIqbEuqT~yCzFC`&rqfk0E%;V+=0DC=fgJDt=ji5UE0fcm z|H1RrgMa^z|9pSL2G6m5l5FSwzM95R=j^?#S{gyr-&1VAR_yQm+Phk@?Z5A9?`y?) zi2LtpDb-72+gt>?p>OhP$Zn@$6g@0TgU0 zXE=i$yk_6Tlni0_5xB-;!L-t6#t`}gCqBPdXs2L1)%Zf-IR#iW_F+#qGNESlxh%6T zwzY87P9)gn4(qTwgq-OTpA)&(q)@7!HO1d%RvYTc+g%+ibOL|}zJ8$x*1|km7Kx}WU~*~;9U<7U((neJG+W?! z&w>M3KM1Ol{Su+a&*H6HC0y%K?oz5wmUj!O4`5TZ(Su6#vY30PhDAysuy9U79$7<* z%do1=pe;Mv$djpvu+FZcK~;#2WIA2WWxuSaHWxyi~>WOvH)FfePVQpG8w17qDigvaI zbK|MHYGYP8ChzR+N-pw&r%r`mYeu*w#%wP0^T3m6f!cG_QgMP|CttOq4daNb)hG!YN+ z_PVX9M?1H>I%B?_zq?g8?AY0x=&12_k-CLNcWYP2skaMsw(5GmoxSPSFN^HfkMxv~ zs6*q#{gTV6{0mSWN+Lu$mgxV6~%ey8~j!Rp`G@5oHCGNzOrk?-w^tQ(VAfqFw#Nv)xluQs^Av@q)p z7`HTZ{O9I3#x36%|GDiOwF$KE)+W$kwFxv>eFCk`HIoeQNV6xRRrTIYT6#CB_pZCQ z^Xv6gw=kjw)0U4-TRt|ef6U4@r)$bkzKsVB3b=2=mU?e*TN-fN(tz8Rxx8)8Wmgoi z_6=YS#O^jkygU2A+VVBaMEn;!WZht;yEkn}@eNk{oWW{e zGgxip3|5pSsy0Q@r1T+s%z|ziYZF1Pgv9y0|qy6NQsqbEz*5 zQH8e=ZMS89T4+YMg=PS&jTc=I&1kluHeQymHeLp6E|ck>o0vwAnwSQ#iD>|9Q*WPX z!8CHVb*64GMscxu^&YkmIr7yo%*r-D(n936TZq@TF#GL!Z19Tv+pMU1LxDBM16ZS> zz#0t&)|gsgd%tMZPTj(AXMeka06w#S6MI^@K z?zp=M3Z-~RBm3=X`ZU!&U_2E(*qM7UN1#>hTX)j=HEUltrtaQ0)nMJ*oyO>HZ*?0i z-ZmFq>bSAJHB`R05v(@%+uc!v#WYw0zjj)9AYiq8+uNN68*8w(d^LQvd^LPESiQ~q zMvW9%z8WbqSdA1JtVW6qRwqRctgSo;wsoaWiacjqXX>QLbGCJ-Zn^3?+d5RYTeX~Z zjjIFOdQ>-B^_*?9QzuWJvu%Fr2CJU4ZHDT*zvpb5qxzA>a@Oy^IIwMw>i1tfXWJar z@4tA?wmGWbfAO4cbF|&oqx$jNir4SIIIyip_4_ZLv#m$<`!Aletw;6yFP^ilNA>$J zma~5U#er>DSHJ(_Ioq(Ve*eXDwqadeIN>?lu&#dp#dEgJQJrL4&iefq2e!>o{r-#R zY@4IKwjOc+rN@qS|A?QjaY;%l?QV7N!R({49qjDyw(P6GYAq#S3$J35ts0L6tXX!a z$$IQ+kAHg`sv|xPct^!=7pPMSD^NogV2w@EV9gefT3RgjrQX{X9(uQhhu&)$Jz$OR z0&BE+uW4w#53E_XWt8_MT&T5qufaU*>;wDN;MT9qS%GFl8=M09T7*%+nq^zK2PD9a zPVTp$E3meFjc@F?p#Of$0PQynkG9{9S_W;u4JNdfth0XEWM_ZBWq5!!o8B@!NNSk!3P(Did^NHaZ* zdMmTx7x_PtYAV?Fp&Y}>3P+6?C~^ezo~XFN;w0++2JXnhjxV$wQNJlv-T07Gj!}K3 z`qOaL&g`2#2p_X4Aemk8yqe3+;R0Y^CyVN8YI4)=qZ(z+C*@K?GJtKAIpXTi~kNv=! zJ>RPzqpWO=T{76ajQ{`y?Rvd3&hd`)bb-SSs?c)ymvI7#9k?pCQO`~Uv8fh9~U8T~^;%l4-X|A%B( zAsh`6ni0;)KeSJJ`ZF;cE@hMK|0ab;U&!-X$}=JjJu6T(N$INnPg#e5YW$xlty)!Y zk-9|_|CHeakiS2u0Z)#EcpHHd>VEvvqlK3IRj0kHN-b*NBBlY-(D^XUvS&hbTFEf{ zUmC`u56Y|dslDD$#VzN*YGQcEha4)avB8Qv8)Vr(`v|CABhsGu&HmNLaF+dH@gMYC zBS^|W3=KBSvVVyoo(88@)kyk3^7sU0)aL9T)hmr3;s0GD$pd#@KPfmwqQ?(e_HP~* zI%xFqEk+veoLYOce@Jcmml%4cCLtBiUD-Eydcu$``{$8;S^eWhZvLClJ6?yfe&b~_ z_U>QwCAArEoz(I~yJ#Uf?M8Bc_fUOzNoQGb^f=QDqNeZU;i{F zeMV1s^oqV@|1hRq8rST+J(=$^LuHr#aKSXoWDB{}$o;mSdRe4mJHtky_waR)K2lw2HESig{I07DaIA|h8IG4t_B`u9WO2cs(wF%F7X5D8twV{GJjH3F7-!}4z zXlZmD!aHb;YWgmH(mbYbQJ?0)sY1&2&v^Z}evJ4#bKL&#jw>3qIT({x5pdB0t!s>g zEM#-`Pq(yNV<{|RUjCWKZU~8jjjY7~&)&QIczLb)e(R@Fol?2Fgir|~gb0Oc8q?mz z9b@e6?Nby5l)0KJjBOer44Cchjm-x3w%vU?!8tlQI?CV-&M0d2pw-IY=;$PaD9Rv; zqKq=gAc``IGE0UTHD|2 zWL4vA@Cjvl9HXxdr_5EY?JUnf2o|&ye4VyaKhmsotn^nY+epzdvl$j)Y5|zQ3jEwq zdOp%SbEs8-}Dnnsy=i<|A{RPV36>k{fOF zOF=rO%8rs&$#Ru*EOCcQ&OQcP_^Ilw;Hv6$Dsoxs9OG_hNo&c3@DR}Vq(Xfz-&(x6 zr~VQWL;G1Tw7w!sP*$er+X8g2G|)au!X=pX zL2I$2&M}5-SDk8`*=U#67hPJYem+r;1nHi-Ob>s1+g}4u;}-X~zv~3O+(#_`STnWi z7|=4U@3p3B;+Fkm;Uw6SKG)zRP?6KN;DtTWEl#@EV~ z=3Dw}XZ==5@WSnMG%ckws&z@6awq*o8cqFWN>lq&`s}74D+3n~JUpHV&mA51JdYD} zt5lXv`|Wt$x(O}Ch=3stnnU*v;iJ#`>s!J`JRM3uQ|{R`pWaA+r2aYmUQ(W2<++n< z-->!05J5YJOjtg9_G)SBKuSlhHeY~IXHJSX){rrvLg%4HzRo>KZ+H8BT^#ylT1`4D zQ<&8aJgNb0PW^AEbS%E(uVdXyjz#4kDD~sseq{iIB13^&*gQ~znHJOabS|COFK!lj zP|mOoDGY+z%`()wrtb`SJZ7X8-Vk+$W?zXAFsFx7C{H@XkPXJAWrP|hgED;Afvif= zWsOSVx0~m;xkjA4#ms;V89^6Aim!w|zSwzX7>3YOGeX8-4dID$LhGHgN^_1^ulGm; z-^ALd{#Xv(m2^Zc91*$v4~OnO;nf3BiP%BZ7yn?)E6qRf?UZr39r2BP z|J`&_B6e5<*kkELI;FQWdOMN7rkN|E>nXAOdb*gFC4$G&Xj?oV*OzzF=*PbdWO!5t z>boR?;Maw+HknMykPFc9t&oky#RnNCW4DfODOy76>L5iInnl5ZcZ{^r4rAT|v)e`& z0yC0zL*G3jrEmD8up)XA&!3!TH^$otgJH5wix>uQt*ahds_VO+d)>~vU$rw*?qck% zbV#KUE!tr`BUapXOBjP)v60U*i6G4JG}`mM-q3*e^$Tu!#+TvmXntMHKkcBUBb)i|2}1lez`#jy1t)#Q?5;5L_42 z=;1ZB_A8}3q}0{a?w!YJ&SBbE+`kq^h}RhN{4ENCR*hM;eTAM;-nx;|JSqUZ0q-~I zpuR1tyvI6=vNT82Un<2T6{|9Ea!)-Oy?tKgtl${gz(EKeu%eQHL5ChIRV~k7)-UNm zmliJS&AtGzpmfq_uL>J=(>G{T}H{tD@bbB_+| zYv~)U9M*sL3K|?%Kj;Cyvv>PQX&47fM~!cB(yqYSdZvo*W>(weN%I?`1(ecmoRn^Y zSx3vP3&6Aa1t{U-Cu-qv#&E#`DZvScRRZZmQe1PI`Xr@~14pR4IyZ`tULe~+#lvco zIs+rn{l570YyCz&XlEao4G zLf(*C+=8$rjD_t(g(G4a!OMiZ(~tkx5rG4kYncFu_Yo;v)Y|>5s7Zl6QLSca3`PWl z5qRCrsIPCR)5nz;wy}Duzm?};+&9#YTSfufVSk*V0R3;IpX(p6U?gb4qJ*k?OBNUw zq0U3fc|<-G{tn0qM7$-k5+e5ZbwP}PK(s)_qSV?O=@E~yskzQ^0l1ue)9L(k9<%w^wcj$viCbqSv2Dzo1>re#Qy51pz=a zqP?xPPc*6EHBJ8pj7)#b-;aR-sSE=gfZe5-Rnz81*3NK&dMSXk~}- z9Sm>Hou;G}kE~@#P!iUOU;_PySB6&Ctz2WFS4|hFX-M-1uI)iky5NU?G0Rr!PN#=4 zUvkvtEBxNFFAdh73a(G3ylStH)bO|0GM-Td-CW=W+M#IekEA=p@Dx>ZL^(|#s2A2Q z{CMhtyCy)rygAfici`cAPVcN_;9J2eyicFaT>C|q=Cd+5D?H7dm3$%JXQd$`)A-^bjzGf{kb55<1y4uvbI<9rKsRcEkX1;XY(-(5tM>$Qq zYxCPL^WU#>8itYDf0h4!l>c7Jf4|Ouf1Uqkh^DXPXAEJ=Q8@p)?i(q$r1V^`W`LZ^N$(j#{a$YJ8%rCav&-kU^!-np z_wSnDKj(M)clqzF{MTP@H5mGVG;U(ca)|snj$_S$whFm{Vb(_{Ws^*$0~4DEP~?wofOi# zgzJ|A__2)BBfWpBuTP{*bk~mF@1;-VJJE@YD#`a-8V-N0YwtgmaPWRXrGKsGRhhku zs$*5|Hn;mtZtn|q=|+A(t{95e zL8*L!_uKTD>iImkdr{?%>HTq5X7}YK0ng{^;aNfYh1ls6A!1E%p3yUt@MmR?BK-r*1E06yu@8MOzV!IS9`{rY8why3t^`RamVX9P+Ly)aJq_0D(N`%J$d ztM&)_yOHUu8(Y=h8BKPB@2i4{$xO^TeEMtAK}R>~9r{3b+CL#rywum_Z@$CVb@~mp zMt{z$?)xfzG+h!bg)Y&5-GrI(3chr@pZF0mzooap2X!L{zstYi{bK}GO{=M>juJTv5Ade4JKl}=wP@r9YGanvR zef0Zi?icjET*;Z`=+jJZ%AXZH(Cs6^Z}~ix>-|LSxV~?4KXo{p>itx?XxDto?`ozK z^rf9!%A<=$^=mu;$8ps|d(ii>_!)eDBkr*Nd7>{t_ZqL@6S=tA=#{>_n!o8MbcV0! z4{!t5M{1eh&$E7l&P89Ef1SV9cb{iI1s@yIg75=;0KejF{7(HlF6hCp@c{qaP?^j6 zkKXx0dgP4S2cGYh^S)#hK3vy3auIqIxZqRsljZ!XFasZ1Pul^eq&fcvo z=e8%vJ?%Zu`L8zpM$Rs)>`=cyQva-vFNiw8OMiZf{@ZRDpI5AQ>ooEYf~0lA#(t7l}?} zQNdKUQ3GmW30N*n!V{In1fl9d-7%GaES7=QtU3`PM6XuCHhFE5v`HAGY^y5TB_F1V zUralqiYY3rjL9WHgGs=I0@pv8{(mz4@WDTse#?bT{Xd-P$5lj*!=#2QZMi-#6A=0& zT(gH+KiRd#WvX2P^q%wMViZ>h6M=p^s<@g>(oyAO67?OKgde0YU*@V(9v8>1I71Nd zhpYFQOc^H0Lx37?yCPQMn3Upr*Y)69e7CeJRE{5Wf=EeTJLsylY#RdX#nx_tC^z(|;7ypr=Kg+g2l*4YGgazrb zz@E+-Fy|XWez4t_s)g`vnfM{#SZW$7TTh|Y@DLjPLS1m+Stxt)w=o?ApVLT*0s*9_ zAefEnQX`_h?pDk~(9J^ZrXI_Emc@MWs8=i4yH6J*FIxHpfMC0`gm1#v9Gh65! zI4@ncC37CcQu9U}I~2*2m@ zuVd_K>XGtZP%dU%(lin8G^O>cQ+hfEznAl0t=dqEY@Ys7c0<(U@45UJsEK*=_g8}DpjJ)3ncqIr zn`)clmoHcJUnQqhVoLlwrS_(Nr~g`qJf+nt>XjsVN;YdstxT!asq;!@9wAMg)zjJh z^o5?7w@6dp=>ILf5l2ri{@oB+9|)a=E!t*yQS1$4UyzwJ<3i42D-P=o;}uI0(Dl^T zUV7A@%n3#l@jr`&Jr1SAVvAyg_EJ(Wx@DpgA%GcSp-_evjR>S83VFkpSmQe6oq`DG z^qi(H>S;}klcq%48JI&z(**7nIbULsnPs_z-{;?V6$MB$mo;bvedopEFq4R_sh$;y zf6^q+N@fivXVP!LLU%e0#z(Eg%AO))QZ4$6Dx{yN>w98<#DnkDhw=mu{Ef{5ykUz` zfx=s!`0KZbx$x8o%X&PQUj&PN>}= z`p5L2Re$ggsjjK?ifMUh%F>>sWoYfG3V3l%r9#0>!k^ttbLmT4dB3f+VueYK>jD)J zLm}W0gc3G!(UD=wBO|4C<1*82Wf=dsia3|S9hQdZD^4v^hD)xN_rN!O+Ovv8Utj#c zFu`Qv6gwZ)+kSc_cwbl%H;@Wx5Io4@)nWjQ!Z5>;&j`W--~ofI^4)BK{er1LPJg69 z9cKL?<7T3-&aPZlo51A}3AN+V6@|o{7^LueOuQUCIRwt@Bvr@!iXdUx5CP%zVe#~_ zG1qe+td?M7uM-F_l3p0zTgrPL^lSenwjPg+9gJRAg6)@e*lux(IYi~y4jkX>D?wBi zAf?^xcLdM0=_ksvI**+TWsy?aSz1EG+;`c!IfR>jsy6L->C~aM^0D^R^^I4ZZi8=F zRMkMctNp$tWeKexq|x5)7gi6TsJuIMbg=t9N_O(?xhN>xt1qio_78pS)bRFDo(Zk( z+!*6!Ns-CG!bqdSz*p7!t~zxs^NiP5j`r1GqODer-M1A{Sd@Rbs2BmicNBR*k-VYN zroWNi{$J(US97&Ft5!+kvOQ~g!5M09V8_6Lei`Sq>{|ZP4klQ5MtWjQp6*OmLA8&~ zGPc){wL_C2i_vb`D{)n(8P70#YTK@eUJqm%tv!->6JLloml%K1`US5FNcSi81k(!a z(Pq&W?jb8x%P2535t0hGBJ+;Chz_59mSv_MJ;itvja1jyHiE2R$8lkl7TSv*Ss$q8 z7ymY@M)f+tV(*H90@<(uF8YOlKr}&!I~U|^cf(fYe$Gp7yb_}ig@f`&l}_-dUi|Gw z*6NrdVmkB(4R15v1_7O8y%^Xl97Y4}Ro(yuz|i`Mo^3kIYKEv|sNF&p)_RQoGLRPI zP9unsa;&C6SGLg=qf*-Wy-;-NR<6M^MNeoIl)6^lN?$>~BTBVRg~#$v0`1IWw*&ev z#^7lO?xI_El6G}HsNJ>!i}=|Xok-jE{>wiXpdVAQv! zf4KbU8`?(1RNordMo^e=CAPGcday{}cWcu6MCt+q$P9ysHBpdA0&#}A-QX7FAo#-z zhb^G?oq}hJ`z&jJ@VX*Z7gRq|QCh=YEIaemuTvh_kPj_RoEE!Zc%UC0P^qOT1$F{U zlp3@_XSy^%1JtV^8J2)gAbp^rRjijxGi&A&trJ5+(#*A7GQHicvG94f?zGg+8}2ij zX()F)f`pvWcG$)m?({zJ)wU2{EA%0fLG1aQ{uchG#XKlIwLE|YVzOu`W-yU%`sL^v zb!hCr1eGSOCJlaxSWH^!!Z%d^s<@6}Iy#rO;Wl7nu&NgKb?b1%u8ztqYZ!e~|K&4J zosi$msR5jD@SZ%ABZ`sm@33n7O8O0=03WEHRcw;H#VqfNpb4ewp8@4V28kj_p7m~01w@I`nuZxQp0@w-jL6bF5uwY z4_j$|(RtJhZSzXy3&f+^n=GR=J$fQ+Zs-?Yr%y5F@>n8hCYWFRn|Al2Fdb+&+wLwx zY?t1{G`fs|ZG(hqqr-s?nvZU^CxQe@vpD#M@?ci1vf2vT?0}KQ9V(`pVHT;V1qcUo z#ZFMg@Xre{c8x=Gs<+GHC}EjTaZ|;QvOfUU>-^3IR;{D4n%>P9xk?9-EA)38`wlP! z$2E5M^dvF>pZU$5?(p2*N#(+6>yo66LST)+NwKRjuOXsb>er7J0@aY!TwEj=W`OI! zGQ0}!JyH*B*U(PjlP}lwC1$pY6b{RrLLTPU)Yx;uAvJWE7L(>r1G#`QukE92E&G)aW0EyUs0kr+*+6-K#j4^FLV904HS^g zPS`NyZ)GU!9ymr!kjnA0y1(@1kdSiv4}+9;ckIPt-D0#f+y!eizQu_2A1&gxeJ@FP zqW_MjrRBEu_h}%^3qd}tKHJ3D`+)|c6yqYSKRZ83pPZF7FWh?t>?(d!7O zZJqZShfRs`ICue<7<;T+*?IRM?HjiPo zLnJJ`_%And78Ixr7silb5Lzv~uOToQxZo+#ebC zgi*0u?KVM2nTZhAr0QES3~5@@unv>&3uvezbuEQ^PA-! zVc5g%>-R-1t}!f&uw+E$d|(fvh$VX>JegD$)K3NB6S*i>#4&^BNdOOF(*j3N;C_@z z_EA1u5y8Yw6fyGAeS})Ew&h5gH1o6cFVg#(%J7PuaVTp0CDrF+B3YawmuSl>`Fpkc zq*47xQOP??Fc(-DE)haXJ4*UPu_~~NeRP5f>KlsuP*OVv=w-uC9K$2xifRD5eqL?X z4t|K+uq%v81E+AURUkYO#)5Q|%wL5qDN}pcvtk=UW0lG#jTanq{+XKVx?omDq@r74 z3&dq|J;$}9OThi+oN~8^iQfq}%7&J9H<*-0v@9r$=DWfYt>NJ!UWg*>N|YV#0#u;m zKWr!o^D?A!!5>}mC%b{Gm-5Jhgf-f4->|Yg_AoyFEE|Z#aQxRexKP=kE(Ql#b3e zR-87FZ|PcYy^J@n3D&TB3>4@eD>2;ad0}=qpq0Pxh*qx&BlI|@?uv=YSipTl6N)J1 zG_zm73PR*xRv_`NbHVfPnf!Y~IOhF?;NX{yG5*!Wir!B*@2AyQ-jB;p`uBu_6eq;Z zX@MrH@Gj&S=*ex~1^HU}pR(~|fR`hP3@^Hq!3 z=1BI%TN9$ zY-&(BRKF*)v^@7f`To7|dQ_A69H7KfjJx_v2urzkFRPXqW?q-PUDD(QmPPc~+5dJ$ zQjvDG(^*)%uKdE;9LreOh3(_jGn85u7SkU7!b3~|1Ri6GfwHk~jJ0cQ+)BbGQz=?h zs1)o2G~oA|@`@8w#&43?oI``{1=6g?JPc4zmilRE7KRH*H`cenL%-WKGyyC1;%aUO zJ3`yx_yAQrKa)(Cw^A>)Mjj-OFFA0R%l=UHh=V$iFtDa|e(D#&18U{>oJtk_osQvs zvV5T%TEH2ih3E2??VniEM-zh)1VLED<96VDMis~%fmmTykn+nS5rzJgk2gG5J4V1O znkg5axA6*pO`4K~AL3honi1NwlT!E;MUmBCinl!aF)U6)Hq%~?31o``x;HF+k?gdc z@6Z}qhTdscq^#;4s~x&D?T#-ITwPbo^irjE#YTBouf znQ(yH)#_E@juDc|3Ty5UxTPoMt)gWs9ZC8@?Wndrb^eV^GyExJOgxn9wg;#;^;n!L zT0B<{Ab^)d--TF2bVHcHw^Y4T&~61groY09{>qL_Gw?}TWOh%jUl6=`qEUj>&k` z;-d0)D^n4TFP*Bj!sJkkYTZxZ#w&vPE9&TtrP@sJvrLKgydgsy!aS2s!pwv(;&Z`5 z8SI1FX^w9jI-QJhGdUSAU&v2aj49PX)PzACJhQ^OW#x6?4gG{3Lh98ceZ7|Zg*+>_ z#@twXN>-LTFAe;)U}8)x`l~kk1CRB_GkhXwMZc1M!O*CuB@JvoP)@d-xE9J;8?LDi zGyt_=YoBIc7RgXv;hS2ZX0*aHeE1ZI8n{VgSnI7Kbxz<$SAgHBNpDrTvKS_bTO!Yf zYNYJ5Mmx!?X|TZ}DAHX=cMJoC(VQ&B0JF3n%R>?G;MzUja0$t**DPfz5twF_*=72iBi~n<9v2o0r#qvd^@23TP zifdVKGCjg%12PDIWI3ISJgQfC^;W(6`gPw^hMlDxB~5!4TB+lHW^6{@tDX9qUbjj* zP4%fHTuPiF`)taECHLMw_>mEZ5tvW<%Ot30%>JKX-(8qv>=5ulO-|g7jM^;pI!h%FHxXR&O7;%c zZ}=TVUh$of;OFvm8Z9QCFQpP2s*dpdlyhJG_CWf?i;0(WKQjMsFF8EwPTQO^);K!ZypD49qS%5y zbK!!2(P9q-HBy*%IE`^l{) zD*0{*Zz~#Yv)xa=%v|MBt2adJ;ZdMUYCGP?ey(uF^1eo2s$L(N7h`97k~K}Pi>;4n zag88yIl`>j+t}%HelxFTe=z6bDPt+u5pSZGFcga}10JRB>8D;+4U7|{@p2#9JHFWe zq>(%ZSZV~uBF}Q zg~MWFY&XzSYIlyDj9UeLerA**{#YR0&*4whY1EOu)fw_B|Ezx%56QJKYSVtP@v~&B z$)VKdNwVHN>1%H9xug$h9*A=Cu69tTw|A7d9QeGPYxYP(ec0(y2ehmENly7gYQz2k zc=OYE^dvkxg>qOTI14X1djV?SU(c(z%vR>8n_4TWzZHByjMrS!8=Is)MNX&4KkcQR z$o+IaJW$7j81F{utGRVa$QT~3c7t28hd+h~&bCKStjD>ZO8U^gW8TTw$kC;J)Q^{F z{flM1p?cvfk6jG`dBgK2g%%zvKWT+AA^?s43=ay=SgQ&iXKS|7gYDU zqGn#Qu$)JM7n{H0q3Mg#sQf*d|0*`8=XccGiy1mBC;SAJJYwp98S|8maL03F3GtYt z=367k*^By;HMG)s;#oex&Pd1ZuoUx)TvJI0Ka2yKbV28OY8k&NYr-+|ZrkJc%+>0V zXg|YWJobyKUzpMUQ`sRM1I&0dVk5-H)&v3Z7IB=Op=#Dn3*{Nv;=oD{?suF6+c35* z+o++kM!80MDoM=W{G6@JEP20F-_kr!K%Fh+&iZXRr)Nu^mt?EuT+i1T(^<#n(zZB) zJD28-OQpaY{HH#vG-u$^uV=Cyc{H!QwtR5pvRe1zMDU}WJ2*<2c^FHKHCo#Fkub*b zEyvqha#u-54v5XdgS1c0uc>z#nf#!zDrOnXsqgr(b7XD9u=<-q3gCN|LWsprHLY#Kstv@4Vn`4(Ei| z@tAf$Tzg-b4et%_97@rtL4AUlSWp z>ybkA2r1+y9CQ>M2kwPowP&4*-A#f5e9#J>Puj(=*VVv}CH|9iRPiNI{fZz>bMOh{ zRW#%kpqs;nA`!AhYSTwU073R(bf^usPr2vf2^|{GlH$Er^%dwHo2RFa<1l{J_dW6b znOXx=Xh$q(mZAl|e%JQp)Mn!`Y>%h0>`q@5r>D1>(f&rBYv0;O&aphG1@c1IxVXkg z^)NzSdACrq;mCa9#xzjGv9OJ;p$5us4d|AgT&c%~Tszzxp z)tJYtdb=uFye1sQj3dyAv-CwJr-7W-n(_ROcv_ZJ(h}IEyuUB2eRP(%sY!zy#`)Ch zbu?uD6(#kH4^3N7GnDuj^nqpM@!asr-a4iOM&TuNY6HWG&A+eZnjIbdGGj=s2zTgx z$BEDuV;j4B(idS+6rxn)MQpp4eX*yKm>g-}*?!+pfAak66xifQth5a-#XD+xHXB@< zcE;1HYGeK$i4lHwL^_g6JRw`%R_fS>Y2mFc=2!;iB1Nz-@JzeUW&azWZ=SSogi(6r z9OfjvoUDy<4k(%Vwt1^z!`FA6_WIL)>g=Y5JbO)B%Kgsc?u-$~dUBLG%i_1(vbh9_ zMyuCpV=E;(MH$)-E1LF{^r-)a>OkiUQ@hGn{7!UlBMG@DZWsO6__e|st>C9-?VI26 zO4e1Ir30PwMB^alDHuV^O69sp1+uYD6dl$x9lQb&x}xi@6VsckW&n5ajFdtTLnQp^ zESuxmj#j-$N3<;`X(J9V4lw2A(6qb;^`1y;8U=~xDxFOtY95p zw(oOaJV4Hl-p!DMWy(7TeRTXj#}9H2*0p9&BEwNu9EYr^f4TPdaad_LEdmn}CNLL1 zzcI_0@P?qom%oy~<5#6Q(weBQ@Q>u-FQQZ7QOTAtjP0Q}mCX`{8HpOh{5Bky)46EQ z^?WBDdZKS|&W&s(^SDd77~9Ij6-<7@LuM?Xoa(pJ;3(pRW6QOdFW)qtq{k91uUKt} zJ9Tv(bO6uz1?yOn9n6+{HNB~qxkov{2YN`j$ZwRRuUtFP@Q}*JY)n=5&`@gOhu|47 zK?J=T5$$Zhk9D@v;Rbsyl~iwcdX=Js!N670MiG9A>A{yo9GbC-%J>W1n&DT+L(0%tFjbIflIRQFX-cWP52u}0T$K;6Y&!? zcN1R|Zs9S(p&EFC2f;X!B%aEtP}-29A#A_M)9{np;|VL$TB#w}BfVOUr2ZmFwzNsu zv0SksBm3{_kK2WlJ?mn80|TWDD_=8&YCWFmExuUxKLq($76eH=hljz4nN+V&^)uXu zP9>e^iy0GGF-e~&oxNN2n_tbFIf7{4q9*SccxzF%6BS1ld#KMQ;h`Hb-O&+_r%TGm~cxPNz>Aofxqe`?5G$L2L zAI~z3r7)L7-ZkM z#fv)JPh6rtF#5{6S}Ec#r4UC!BXIzrEJHqUD13{tK2km?lrgcLm|@Eo*CCU(dd=8e zH8FbLKraa6Xt5-m@v9beg(K@=nwXltXYLjz`9;4mPmq0-oOV+fz*aS}8r6Cu*NRt> z_7-Xpv%qp<(TFUOf9Sh;zVXV%2SIH#g=HZhYmobd)x-amKF|S8n->}Fr58 zqGBAvd&3!`p`VUsy8D?~!$?0NymD6`_`RvWdY|>XFg?xfH~dB0Kod)jIYlzMleA~T z5A-8$i5(IJbS)WjiZ6nU5Sy2Mty0k<*j(A!+$tkoS|{>yQ#HE3Tt)XxwJeJkXi}s* zW$86~Y1174L;FGxqypakoP=`U$drSakkKN`+#dBi$s)%nbw z;*aUPN>+b`rEQ+kc-C-9+U|FBKty=DDc5uT;6hP=*-tX|h%Uv`vY^N#iv5uSJirF` z@QfC3Y9E&xk2w~i6kLin5{`C}28`Bl!9l`;%43PGwX)35JEiePXSg>>bkfnC;*4## zloR=fCchP4d8Y-(h*+mXPEleDx4Q+bzj zpGtetZ*29aI<-^V_UAq;X|taTtMGc7I-(;J!YgDWQhRGH`r9LpXNjFc>{mdeJ=E_v z$_?L>0?qT03A4-(F)NsEdVY{iPNp9@kvKfH(4p)HLRSv$AN2OyIu3p~rSGUURy(st zgO*KWPSW6rl4lJ(K*uI9_AX+993=uTF2mRF)F*3bB0_OIM0aJ2+TBUt`!%E&e`7Ga z)zZy2cx({b44A`NnKGRor_}rAY)Ha>Gfp)0woDHgP;n5!=&p$a!Dt&8BwVN4C^HL3 zSKDv{A8eDi*ZyU@9Cd-B2x!M%w&DK&vJpa1{5rTu58JJqWZ;lp%WV>yEsF~kkYOTG zmKX>yMX1^|827P*8`zTVJ#1Ah+gZ9>fy53Uy4Mc%+JS6i((s~&ychU;{i5IqHL3iI z|F7M>@5GaEA~Mo$+UujxV2~IB1VU*7jc79a2n6`td(*7(ILw7+9bXI2ugGw_E#?wH zt+!5=V^)C48XferkIpD3l4>0VK-o; z#?Bw>i<-?#e$e>em=5NC0&O6Lph_2w4!Y8fde|hrxw)}P5c%~jAOVkcY^QK*GbuOy(4Vlz8G6q1`MR zNWRYmH=)_6x~_g99k78ne5D^hW9%KH>^?%np0HyMOHg)Hkryu@I39$63@$6M^}@$S z6@p5~-a#cqTvWPahH!F_I2Zu`5Cs8e64&zO-$+}DcX&tr7lIrDnZ+oBwZS%8Liq+; zTD>?@IDB?Yz*pL2BuRPX6>V=u>md zQSa8ixgkW*-Vj`Ebr;G7rG@6V3|o5?HRGcu{^pu85tFelqygeb=9%Z?j=M|lI)1#- zabI0SFBfmhwFGat^KfH9G~YmAsikm_xdtIZ|63r_iMDD9Gf&4*OQD=vCjLKz-1MP9 z;A@kuF8lCV6UVv)$kw$Pk1`R%>kcCOj0#*C>O^TWTa!+=`43wKQ7j{c+uMCmxv#ry zTA0ep$B`z_*n`s}V|f^Y*-)Glw)+}`YlT8#rCXsrVARLz$M00e7Oy}MY}NIp!6{Tg z6j%#OUmI1r)b%)vW<$~obuu&FYGaKaS>c8*!6if|m`~sYL;@XPUNQF-Zf2|oHC_d) zVJcMaOdVm1^evf?6Jd1Gq-EDzw`}nn)Y^;xZdF;)3}_E*b+Ny6x;_R3GL;ZFik5PO zu&37Vv#WC=me-+Cfne$k zH@TKT6RCy?0<K#>wId2EbQvxD+p@Dgefay-BuGvW|G~n5UG$i# z#To$5TA~lY2$5Vcp)$up{-7E7dam+@xA-U+?%%5JjJ2Dal_+ijz6s^LKG^3j&_4DP zYs0WLtcc+Fi;WB!!eUNB3Ya(aX%%3ck~pB?m)X>?BT$<+qE+bDmx(xBT(rE}KG?;3 z!9cAJ(y5Y;J4}qOu4w|kUi_Oj`_vr=Xj}+8h#)K$;`5~*f6{5^Gm2jel&|VgO0;da z07t{F@K4gxiy!F_oxi<>?YOSrCf}3ElFkk8N;<91L&GMs{h5`4ufgx5M8vT{D%ede;fu9 zH=X#H<8+Rwg+uZW0rmvv`^A4h48Fajwjc2igYQXz|F4q(2g?{|7RvqO0U3MXC>X(m zE~iiE45CuI*Z)`;XYLS7@`bu9<=xgKZgJ^?m>1bZ?ixLm(&=K zI4f`=LpWhjftH9f73U3y)y@GFu-g4XIb!3IjN7(1(t-4Ur~fDYZ|VP#nH$XkEuNL) zmD|JYjZ^0TSW~?*qmrioSaUJ_Ws>fXS!S)jnhMt6bNTPGCe^~w6aVB~?C!rbS5mmw z3c7}yOPRjot<((l0Zk{e$Xo9V(Yr<2x2sCmgyF6O4c(#tF~z=oG?w}}1Ljm}21@Gp zM@5EUBUS;FaUy61r2r5sa~v-roQ6dw>Vn^IW#!QXBrl_JqCXG;;^AMk<21uV>?P&% zNE43%^ZiXas5a`{p%x>iSz;>|he&^{79lr+2$S2SZv~2OEX`}ssNGCCdhy>fK@1ug zu$0B#!9Ov}K!NVy&T25c`arbZz=!#;RU@DqIy2x5;>z&Nbrm_R14nIG#O-&iw;lZQ z0GJ+tTnLALBRX4Zi-BEnuwe>3EXZlOFf^*Y>z2`V$G{NXSLFnh9-)%^La(FP<_ko* z&^uyjC3e=0s`X-NA1|we?jp(~RGMv7H?laHd;x0T~As@kFZ5$xIz!@XWo|Or72zH2Gk)_lVv!R*0`;kH`D_UlHK#D z15xtKwd0v+3+9od25`r7ECEGzx+eADe!I_YA$8j?6TL#}VTz>HjfTD4b=n=+Zq|%)ib}{whm@KbSpy!Qun9bX~Q0E zMN4ggDe-9wP+L5MgPD-TvPDz|i1FcCaPF*(X@UL*wO z)eRNzJ!`{VY8{%ojsAi&QxUq^i^HIFsSZVu*pf`hM|JYh?2&p#;xnKNGn-znKH%7aC1RKM1Wjz&yJWi%CqNyY|s za}{q3L+7dcI37eL6@I^#@zOt7-k5g`i13yB+O9v%JP~tgIrq%3q`TwyxZXYCljeRU zm~k_e_TsPCr9g4MJj+r5!uj)H%qle&Fxe}uz1-+DJ_rtf9UN;D!B*XSz9GFuj5To+ zx^gMhJH{*qz&MYL9xy~6VD^dK9a0OegLTz|=~t-h{i|xbOw0oZ1D`Fy(G4UK4kj#$ z@JC>D5<+Nz)pl@eEQ9h?#x0@n%L1|mEVr5 z?Azbi2x5n9oUZNTgoYWZ(5HLNMi*B91u5WeUh#jK{^Ox<0z>dD^GrjO+Z4 z4S2L4qj$TJ%K6!Cl?RV(lf9$*)-*=t5UoZ1TM-KdykOCz`1T4b;}^C(-1?Vvpx30F zu(3zdY(CZ!e;s$zyuUI$cJ03ww9wbSL2=^oB>gX4A=uZ(RC_8bgfVIayN;-Ub!*6o z&ZM=W+O|^ijfV00&BtNCa*rqL-O^k7oMt&ePbmj>!7qTuzl60EhLR`ujD#DUsmn zM`k?;YB65KT%A`9z`{)%wC%r~tbI(U{~XE2*qpZ7{Fs9 z+?(oGJN0;#+O3CvP$q5f*3(X*jC4 zPd;SH763NNqu+Tm17(HF+reOq>Uq#8BH6HQ-NPZhWf4G!)pOXtpza+*HPY+QdhHVF z#}D4no&@}#7`nky$d98n_E;FG*SmOl4=Id+Rx#OEGT{jiqsVJ!26f0RzD!>_+d0Fa zejw7mTuz5uU(zWAhjlLgVae^1Wb{Y}np$F`9|YwQFM94}ux)~Xbc>D#pErkBdele` zLlgw41$~HEBH}bGm-e(Hp?3w59i?Z|dFiwE0M9FVzEL@78Ru{qa!L`#ggqT{v6`9C z`is9tSw4{Gz!4+M8c?mv>v7syy>Eyd7xViwDd$yfP335?RbBOA3&F9IsL>@gcPw8@ z!1FWJRsahLK%LhBhcqAsNONrTDCr~d@^Fc?bGSrW9oA~aZ*hz@obd^0d1cFPjS5o} zYjDWU+OhrD$52{xON;G9`-Q;(CNKC&J0N4M`yShSMH7mxK)i-pbwEGLzQ;_2?ta}$ zx$x}JVvFJkA`rEJOcITB2Jvk#f3o1Tm&kP^3u6A|e#yYb7HfETP51*B-Py42j8$1_ zgHXn76T*jk;Trtp(6Vzik)ekkRp2@H=fPqqWvbI>!W!M4d9^eHC$P)-#I$yL_9s8+ zmZ4YmdZbO~2k(_S`{nxkf(o9yw6lBXXkOt-QR~h|vx)=UK+VFtSoG(@%`bEBy8Un; zy;8j2cjF|T=$7*M1p63Dt=%s{bfjNq)BIX|*zIk-2)j+0(z|vZZ9WV4jKxouP%xddBU6(IMhy1A;z0TWEKA zxZSg29Ms~=+LW@I`!hlOFLcH6HwxJNJ+^~UFy1fPE-L9F6C)j-uQi9|i~Jyf01O-& zJoQj~5CN4T)Vr$=bG+4z_waZ7+{1%cNa~|SiTT+4b|o$11RzWgGk5J26{t{)o5T%*nap~>sIibKyp5s7rjt?ndXkl{*mly6H9KQ3vp zXY9Vvg3USL@gk8lSH7feFf!wOLzQMRAP9S!<7HPQ?KC`{muw=+1ewt;Lc|rnWUgwBJa0mtmzAWjIzR}drb?ktmA|IaD z|7}t9w(#IJS$EQ(D%yBQF>7)j8JFUUM#f|HI)qE=&jaOQjF^@ffY(=QuIbD0?2-CD zO)w$N{z-;OR|M+`$45_df1)@A0VbpUZf<2vQ6lq)Gc8LQ7H#Q>qP-XN>tnFWw}%>CH%IU4C>UsIAELWXFzrJ2 zS-^vA;{>3S@N&|Wdj8Bo9>y{7QK>&A;#5WhDUoVS$yCp~S1(yF>ZF3FdQT^9ZHm4TT zzgO_boJJVvs8Zj_6bULWRtF6-&izC+a1<=DZyjK%>yEgBi%1O7nhW}Eax`U| zw_KJhB6ZMD)t@G15NfWu8-|StP{0;-a?#%vL9!%_9Ywq70-I|};~ktw{?mrPf$p@v z>d4Rx-Mcwj!~yU0xp^CZ(QIw?MQ`ITDqnr6YZL9NFM8AES*GQ=j0xI!SG;Cg19g5M z*Q{?k_}&y4f7Mp474_U;k0;sqyzcaGrzr?F@8pvKc;^p z9nHP26!LJa7`ecrVAJtkyb%`89H~Qrpd5U9E2q1ZWuIr^Lmb0JNEelpt1&*xHE>nh zy2{k0xp3zr;eyuBGxgC@tT%)e42R;sNo3L zJensp@{Gj4)XI8fVztoYs=i+k-bY#1c{(nbj=@d2M|H}(;6^h%=r}Nn4D`P1x{Rt0 zU6)bb;CRXR_Bt)ssf#+!s{he)lW&Gp%@gaQ2-A-vw&{KLx zYadhpp$ivE)Mc5NJ0pI%BUR0j_DCD2+U#L+C#2(8ocGU zN1J>V39j?1QP?f#?;Ppj>Qf9MaFR&sd+To_MFqO|Lf-4NZOb9@`=qQLd>TW}(} zC~b#d_+RI}F1U`0I>NOsFboSfPSe#HXuk3LSQC&6OV;juB3Ygf5}+R?MN18z<4z1lA|wV$qQ@8n|M z#4xmSb$VzyG}f_2Zc{qD1%uu@wVxM9xb}B9fdMTrkgEfOY9GLZv}Owiy?0>X!V_vo z#wTN0VGJb!*&E07D4N>-nei=ovPL3yh4trsf+f?lXJ(7-T^+@lX z9@$=^bB}G(BbD158X4{Ijx@nLf{KG8P0f+Uo#SU6%y?DMhu@m!V@!%)KGHFY9-;Ea z)heU6+xhXgHuR5ge7PbV+>lmWl_gKRC+FqR$GTmvZ8)z|=|DT(I%3^|C@GCJp``qoPD!OyC8b9ulsv9{XnbrTy1gzX$)8Z_ zL@S?UctSq;UlkOR#tHdOwem@>Cgc;{tNJ7%6Y`yIT*bHAS;JA%M?2lS6)pPUuuiTl%8;~r z-u=2+&*3aqGmdt2vAL#xrl!4uV^X`A?tYEBZmK6>%bqP4^FYHowW)1y-P`{6kuDCR zB*z+4TWWl2(w4@lqkVy!RY7QqTEhU}b@W7*MCZbsRHG)F`Qoh6RVBWy-R(u}g$Tv;&w z*2E2(uk!Kgk&)_Y##zNCi^lt$3k+ka533EXMtCp7c1C|W)OlSTZ*RS{)}|q22VtQ_ zSJE^N$`IQVuVVsDN(1xoceH=o^MM;#zDlko~r9t4Nd)doZG5DrwrDQ*HUW9`Np-#(spXW_6|XbHL7b-zHu$G!<|~N%tN){ z2iLVIUsKB&Nlm+!oT5v3>nIM_6fTJ#@VBC#V|wPioa*R=!%Pi6_O zYiUw+?Q;!}IRqPuf8>#ClQToZv$RMBO_MSKI|Z61-?)}GrT99DQrPGk7UesFmTN;^ zdkL%364t4VWaUp(2<~{~JTBSNd5&1M_23!t1u-dm`B)#e~oHY4q{9z(wuNTsXC_{+J*v9UTXIvS@s+D7Y&*)_IdC?^&a8|t{~ zs2Sh2Q*AHcfsrok1dou=$~yqfi7_;?{lXVsO~IGm2BDGG7`I!XkvBC?4QQZg zq}I?V?*KF>JJ6_|3L3o~7~;0B?4mO|V~H_b1zP!Z{mJ^Q+u3$a~exloAG6z#_tA=cU;s}D4wGvm**x&+ZdM|lPogk7iDv#h8}tx%otEU zbuTrqROqF$&ZwUk|GJ;b2)CaK1^TIsditqQs-McLfPQM0&TJvtF=eciqu=7Y{q1k) zbN`R{xMZo92T4Av|5cvxiZA6qh@@}U=JcI2G)HRrIpuQzm zP1VO^#ahQjS;UiC`@{NiRwv~hf7d?AtEzmm20L+LLA-bVv093iRIIIwWzw8c8B6zl z5K)xTYOC=%Z`2=WJ+Yo?sHQfQb*-$lWuZP%7|)2X(KPe=c*ca3GGoG9o-r98kIsrd zuLxtkl{bFHvCs8tR`1b_6|3yW^ls8v5lH{lvs0{YW$mQv^|J4Bo$FtySxyETaG7Wzkba|pQC zal;X;6ozh=z)kV@@#{e|xxKXBt&y{}y#Bpxk$pudKkL@HHJrV?<@A~o!>V>9I_b_1 zKP~D87;p~#i+m&3TyqFNl%wPi%90<CQX%5QT989Ag4!73dfU1TiI67V1 zIu5;37pk#t-8&e(?TGc%XnU=aoxd-Q=h<4t1@^6G4am_h?QJx%Pdh1cgpPEM_93{G z8Rt7y=i`6^b{4Xhw&bY~VPW1!@qR_Q+O@NF>?>Ii_P3QU^`>lA^)J8kfe`S^NMfp3o#6RxoT6h^snHt?6&Dcvz7Yn+obKr|9gVl5|wWKGGp(F zc!a-IvQwIl}^fv#yh49ylB& zj`ue2)s###?C;@}t#y_CjUocW(vq$%3u>gmS88^p5BY7|nedc`HD%f!yg0_%&2CT8 zwVc6W&zGaQUZa1%WzU;yp){5+^vtW`s^7y&xJWlGlkA{_lD1pFcVF&wG1S)wg%{YZ z;XQuv+ZH&XXXX3&f8{#06R#@?Z8>;3Ttzd`k4nS-=2fuKhvUYp%gVieEi2+Cpt9Br z%kna*o7g8^NY%yHHBk}E%_!it>wWRBujcN;5QtN32McwMn(rI2+DQy??TetzXdkpb zbIQQH_*b?6(=XJu5BG(S*uz#1_rp?p6gU|y%F)%x5ta-~I(qRh|1e+_V~VkE1JK3X zXs+Z=_t*$W4k+_T&px|`4)pODDy+D7FK3OkD-h9zBOPfQMsB-$DwZxr@qwViG_oB_ z+XCnA3bAbJd!EwcP=Vnv*Um64{YJILCa|KMEXqShB(T=+wYWzcXzkLWh|)s5Rd_sY z#Y^ymL7ut2pRe0;I<{FkTI-dVzOC(!>Ej!@4L0xL2G}|3x*nz9TYzM)IA>`(_fw{_ zZK~3Yo#QlLGj&=hC0BFrwMlqyKka(vvt~FlM_}zyb~n=kbbe2iMWXFarkzd;Cu%$_ z*-0O9rDA(AXeUUc?E8%FJQw}s) z+Ea4ixU-G@2Z?v{b?1|YTRJd~)7vnnarzZO@u1YRC+hJv5ud1KALGaXJMAi8akaR# z0ls@GXSyv{xQmAc8yhnjh;#TKhxi;(U%Ua|GIkUhdppwJ6K$Xc?%)L1Y-h>4taf=L z443wlbj%djK@c+Kyghy0GIR`9fUkmXU!=XKU^H&-Qw8|xOAHXw%2xXxHI>@g1p7TW zSlwzJT)u-rV3RLTPij~X^}m8sw!R<;bi(vsHbD4eJUUEpsC0Rzgn@cK#>=4}H@T<}m0VX7li|$K%=|VtPf-GPL$mp$o;dU*h zbOr6Tbzuf?56#L6Q!m4n>G5282Fu6M1vOlw<;#My4je9K&kGtx3k)E-Lv-5qX+dei zS%~&M;<(asF$KeFY&u%6SncpyHyRkzW9!&D0MUhkK{CDsI+zg$TyCDIgHt8^*5z<~ zBads+9Ms{^PHFR5j$0tCl|C54bxoBxDxnO^Y?ZPVT^TCf=ju)CSpEI$*q73Y<+0>B zxI(CsL$AugECfj$MuZV?#Mg3Q!-LZZ z3oT`ChLWR0jA(`qonKWto=?<~wsBAd-Z}?q$TQJ976uDTs4eX&KGS=-&iBUOr1clr z8o{zF4u0@K3Tbvx$60a+ZJM6tVB48R*1x zq7YfX7Sa!@HpV+`sT-}t*p8_e^D0f>k1w_3uYUek`CIw?LlF0lRSxNr1~qdwExBzf&>Kx{7I zXFN(TQLt6~^PKXnCXFW56!bur;4aS4)-?Z}^SSTlU9=$*$&pOv>~8x$t=smiZH*(+ zP;*II>+5@A<{(N_D~HjxeTLd%jBanW4#G$96wvf1+6E=} zLf@(un6{3^H1}I+1(v70mlrgm7tw4!iI{*?S_!{mve)CW?iDR5WBf8u$NJWNPvo-r z{^#Tc+GoReVzdoE-NUDD#`EWLt7z@cD00iAkFM8TMxu>k zfXPXYF2&bvyTV6WYs>TSgr&Cot3Fzi!HT^q;c5oy z635Lr{+Oo0PIp4o_bzY`0{CV$iZl-5hc*;~Q%c0vXI)6IV#vt$=&Uo)e;JoRVjpTxVuDlJ$plmPk zBY7(L!rl@mGnNW=IJD7u)0D>Yr?(0f6T($3)4ZFO(liTh<%W1KG^Q6FJW(&Mq}Pw< zAemsn^`F&JJ&byL%8fjtwKgP zcpeETw22bP2VCky2-AF|^PW1^ENa8fpy|7{VIf^x?OiKsv%|IClVvDTn{=s7_pR67 z9c`+GChB}ocBnnU-;Jbgy_L|vs(pLFA2F$}9BjSTGKx~YS{MP{?kU2BZ;reK1;DZ1 zwmAo5sQiBAb#aDmh2Nl$t&jL-ceHQ)P=5ush>AuOdFwGv>^YW^NN7ZTlV)-vHO&GK zk(hL>-6JAE=;_0fI()LoSJk6~VqKPP2x-Rg@idFoVF`0O)2?x{mh%M&td8;snX5_&C-_;nHaY|QiCsXkYd3?Zv2Lg}R*o?HTb1ps|T!cpSL8X!t z%{V$W(z=>)GIY_;z&#p}+OW^6xv^Sx>ehQsL=V3f%tOb=*?YQ9y^;pEymgvBYw#G5 zrWvP%xmCU2aIJ9{#9&@B6dT~Wx zq>HyaBWfOIq`-I#+EO`r2OtXzB)KS!VADy$;r1SrsfXpArYWo7xe-0`pMlTBZUK7Vc!kw!kTm6xqEYOK6?_0@XOrwv-B;OKQQgAQSi zp{{2LGou~EhkU;Xx`Y)dlCnoXSA42H8|Ek<`sf?YX}y{L{_oz*lzx!kZlzQ5QjY3B zQJIIT^D7-V_UD;T*7eIlX!rH|wf>%whIv!<|00cU|6HxSr~i!1kpg6JF~57u0^ewD zvza&GD78FS8~J$t={MCvdhsuRE+81}m7N?k7uEY41={8;2A03?s(gSL={Ch99Vi5v zZlEJX)9t>#>>WZs!eC^l8KWM!d0;7Z zz^JtYmC-C%a&3=lMetbUXF@nI-z_;b$x)`>x@&LC&|Q*1&r=S4jiy#k-Z)XH>Ne?16LSqLF2 z56ZQ0K3{W6nk#UWg|K<@XuJ>>gWfFW)O~}NK2#D;SQ9`iV1EFjiK^7u@j#e8?$!0Z#jM#%;Rys&y4N7gz&_o3x-sGks_a zH?-lM0ObiB6E8%8{+3RH$e6;|Qv=5Bih$?zc9$1SH{{ykXPbDY4OtYDj(&J_wEbu4 zkN*C_-(UZ`kN<9T^pF18o$1ls`)T*zZ~WcvfA*hFjTYWa{~`SkY0uIB@h^>HeNo?M(-C%w*D_ulL3F%1|oS;I!PwsVU~G$I9TM%LrvSS zy3A-713yxFQ=L~fR7-b{YP3~b4|s+FjwfAlRl88K+O~FFaSY={_wdXMTXWib=zh#{z$IWi>d{=qds>1oc`E>Wa^*2rdtIxw<)uev^``w&}b2ZcMkAy{* zU2y6WH>O-x-PC+lCCfKXP+3-}G?oLdw@Q|;%NjPR&Am+fa&Fb#^s(v(D(;F|)3+OH zE$yQPzPP`f1k8!qcXMgZC`x;Q3F?B2yG;$8NW@8$3gzynwm2)wXH#Cu|F&w&cxEm!!RFSVao^Pas5E8Lq$d3t>V;Pf-rW~R{XFSRQfjIl&gi~gM*wVn%`Ebfm;q|e5+y7jfnEirs+YNB_HR@ zj%N6)NQ3SlWSho0?ig&ImwBGj{}+E7>KbRFj*uBicHW+fDRG1lf$zkUPZdvrg~OpQ z&SFHwf~XO_A)e`hB!(V0(2fo@#E^)x7#dc*v6c(xnbl26L%7(N7VgNR7!o*u=u{># zyP1|WPkmp%9Pl>U-gfrnc*qFbG?jsDZCJ_>V895kMA^P^-*O!av4~8&hTSICgTIM; zG;XOtc~zJIZ(D9jm%D&(5A8OCxOTak<6#bMwJcYtmsSe21FTdvEegKN2NJB2uZ9(v+(xAIBx z)ozI|F7#p+plTNjT?Kb(+0 zJf}?W>I>)Z`pi{47IJVGBj-+YrX+be_y1Tf!Ku^S1LFG|mzC}^=aq6k=Rcto?g3a; zDcU;m4-djoK;R5o`g1~X(uY&IkMHWqr*R$?+$VG2mW3z2F6;lv+=rtXf84o4k5*J- zIpc^kAD8t%&V8gDHUxZeDk%T?OPfCdApriIny2zrHOsYS=R~Nps-9!JAv1)gBOHKD z&U5!U@AP z4LJCmEWxCwa1v|@j!x?fIlu>ec1o!L2rt23tIB;|x!@&kNVo?P6laKEQ7q^?(7^oSg z@E0@1hzL`Nk{aDZ<&-fAqaBn4<9(DC#?9k0OrgiusJ8+W%i2Bep8(|tKw0;U(;9E6 z1}5TvrMi5t^@P$VP^m-!*&Y-i@2)c-H!gn$>8{fm!|4Yds?5lILd))1tRw}wY}@R4dE0%YE(YpHQG(wxG} zs0n2zZ@%mHz2JZJKKSxdp*X{ms7A@bxQPjDQ?us2bjhQd7^8y~_|AuhU=F+73cd=4 z9T8}v)GM!7SIyID_NqK>ZlX#bGK~p7!7yWm8gBC(H#H*xYl4Yd-~gQ7Om!{50w<}p z;m~scFWEMa{`MV3WRizF}|JNuV;>E7Co%~KH3?4=a|@wAG;*7 zp&`6LhhmisLe(a2FoiinUKdvF*z9hR}=3X?^d-@|&sa30Ce7*yUA6#s$zVc`A{ z?PB)pmND|V<>eS97ACL(@d^WscnD79d?;fIGuxmc@?m}o z1gJspgv7LELD^4#a7*lpa1j?hvb$o}CQXx)X6@3Hr{m)^x!}#$lN#NSk9XIS=Hf}I z%`?tf+J_7B>#UlYKyd$BJz9b-hjuy}3t-eMJy7Q99=x-;lkj^Q{13xDDI7}6vEt~Z zN@do2tnO*2N_V{OvN&3BJ4OU|if1_9N>N9jYZX6+`fOG{8arNNLY+WM)J!PGaR|7} zqi9yB9n^DOl6+klbVZD5QfpAcvdHjR_4cXSjIi8Tx#pEwZDU|=ZRP2$wazoea7sv9lzt-QJOl+;g#(F#GzY})`d$MouA};$%5fRR z7^)>}q{Ffr4A{_13`!aGq5)6qKNl;KE{(S3-1$O=o*pH#$A46SkmN^2BK!lWcT>= zO|?~rKx%y*gE$5Wah}8v_Rt;<+hnX6kf7hgGBX*@g&6NDrM9)M-Cjh%%CLZih747n zr}R~=i_xi`Oefdx#u^lD>)zMHu&y5gWaw+3TW1Z9XGTcmseGRQuAYGQm zf20Yr;d`&%gRwZIPcQ5$XCGv4emnBn z<~vlv7~x>1UKw9t8XX ze)&tUkQxGi<#}O`IQJ$NNxY z&swWTz2p0h!DZpk(m7NfDFQbI+6;l#>AyTgw3YfRjK4yi8-f`5d#0f7rhUG%lJj}# zKzGDik9gUm0rzJBElg~XHUFg#I)$7REJl&Owuhh3m5LFmk8g9=ytZi5{ z$ATj5Ktd3Zb7;TLA&Tevc6RNEHCXCtnSPyyYN+P4+~uHGDV^auFbr4w%)NA`o2zXF z;c+Y|xzL7B^`BFY6vF@Tf}WpOHksw1<cdLGGA7Qtlp zcCmlv6Vx3=lOKmg?NsyceI?P%TENMuLDV^ zx_t@TYb|qKG+^Y+LU$%p%&lqVreLW3$H*6$G>pO9oYa9I1UAOezNKy*;VZ4FZY*sp z_uej?#t^<=#zgKpo;wq=jG#T`Npv>t2@k#djcD!Uuh~z}KG9IUOuORUZ&rS@+VeHg z6QP!$HSwFqSeroQSi)vGjG33^*t`|$5R99^K@M@;COJF-{jwYyVZEe|%^~$jce6Uk zv3V=x*t`|ScB1wskb3l&Ui`IZ>raa@Sq1QJ{ym}43=G9;0ahIllqML&8Uxl75JV#H zJNi#aRsxX9U4l$SkmsbHSpEQ$b1%DpS;fEt307-3)I?cU9?%{~E4`y9R)u7S{HejC zF*?VB1i%Ia!1aO~mLbpyI>QnMKm>Gy$T9|B2+YD75b}>2dm0|iKtcV!_^UuZS1Hps zF9PvG4_~QE-%cnq_;4P`#Ig_~fG0EFOnMUs9C99(abi5O3WiIljU^V5P=^=j7~8N5 zYy%uHm-zg}U%?IV9Tl?bhYx&qQP#4s8pSK9;6=a=&;Sn(}UjY(n`~@)UFaWgfqJ&9I zopi)yC{MF4L)W7;tr%cL4eVJV|AfGS19uCIp?UzustRm?89EO(VFf4zrHl?R%rjW@ z*MG#AlMS|9~DMp!w(p`ps4sF0i38w?H6^Kk29rFlE8vi}S<$un4)~D7eu= zL<^L$a)*^5CO8A1a6ilhk*PPGx*%s4*Zzj;FbnxkZYZ#%!Bq@m?S_A=G~HtT8l}#@ zR;iUVdjHiS{8U3m!k({RR?zkWnHLm@A($L^B5XjghoO~+rg<>1c1x)Q@4VEZOw6-d ziAn1AE_i~n9`1All<7bJ+ROm4ctQgry=EfbVIxQlQDGv843k(-z&6+wi;xYkH76;s z53E9I-px$>MSW2ltV1cZgIUp{Y0m(GMt`7n?J}Cd(ORGgri6u2ueJf$KbX(|&`Kx- zYH^ROVzGMU;~NYMGgAU}OP{HQoZy81k`oAM&2|TfNpT&B5II6XtXSwbt)gzIjk@To z{hh$JaRLm`z(xX<55~YdqUSzRyYY<9#Z&~P5KGd)AfBjkXx+;ju$dY*FqA~^(~HpF zC?w)&8kqFO+-KV-y%M0AaTp#r-q zuHNK)eDKNtid2!cQegdhllC

    uP$-I`Xo+n&21!veBTE!T$|}X>7~~_RQuJG- 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%0A?J_ZRgi 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 literal 0 HcmV?d00001 diff --git a/src/packages/itext7.7.0.1/lib/net40/itext.kernel.xml b/src/packages/itext7.7.0.1/lib/net40/itext.kernel.xml new file mode 100644 index 00000000000..55771730f09 --- /dev/null +++ b/src/packages/itext7.7.0.1/lib/net40/itext.kernel.xml @@ -0,0 +1,50386 @@ + + + + itext.kernel + + + +

    Represents a color + + + Predefined black DeviceRgb color + + + Predefined blue DeviceRgb color + + + Predefined cyan DeviceRgb color + + + Predefined dark gray DeviceRgb color + + + Predefined gray DeviceRgb color + + + Predefined green DeviceRgb color + + + Predefined light gray DeviceRgb color + + + Predefined magenta DeviceRgb color + + + Predefined orange DeviceRgb color + + + Predefined pink DeviceRgb color + + + Predefined red DeviceRgb color + + + Predefined white DeviceRgb color + + + Predefined yellow DeviceRgb color + + + The color space of the color + + + The color value of the color + + + Creates a Color of certain color space and color value. + + Creates a Color of certain color space and color value. + If color value is set in null, all value components will be initialised with zeroes. + + the color space to which the created Color object relates + the color value of the created Color object + + + Makes a Color of certain color space. + + Makes a Color of certain color space. + All color value components will be initialised with zeroes. + + the color space to which the returned Color object relates + + + Makes a Color of certain color space and color value. + + Makes a Color of certain color space and color value. + If color value is set in null, all value components will be initialised with zeroes. + + the color space to which the returned Color object relates + the color value of the returned Color object + + + + Converts + DeviceCmyk + color to + DeviceRgb + color + + the DeviceCmyk color which will be converted to DeviceRgb color + converted color + + + + Converts + DeviceRgb + color to + DeviceCmyk + color + + the DeviceRgb color which will be converted to DeviceCmyk color + converted color + + + Returns the number of color value components + the number of color value components + + + + Returns the + color space + to which the color is related. + + the color space of the color + + + Returns the color value of the color + the color value + + + Sets the color value of the color + new color value + + + Indicates whether the color is equal to the given color. + + Indicates whether the color is equal to the given color. + The + color space + and + color value + are considered during the comparison. + + + + + + + Color space to specify colors according to CMYK color model. + + + Predefined cyan DeviceCmyk color + + + Predefined magenta DeviceCmyk color + + + Predefined yellow DeviceCmyk color + + + Predefined black DeviceCmyk color + + + Creates DeviceCmyk color with all colorants intensities initialised as zeroes. + + + Creates DeviceCmyk color by intensities of cyan, magenta, yellow and black colorants. + + Creates DeviceCmyk color by intensities of cyan, magenta, yellow and black colorants. + The intensities are considered to be in [0, 100] gap, if not, + the intensity will be considered as 100 (when colorant's value is bigger than 100) + or 0 (when colorant's value is less than 0). + + the intensity of cyan colorant + the intensity of magenta colorant + the intensity of yellow colorant + the intensity of black colorant + + + Creates DeviceCmyk color by intensities of cyan, magenta, yellow and black colorants. + + Creates DeviceCmyk color by intensities of cyan, magenta, yellow and black colorants. + The intensities are considered to be in [0, 1] interval, if not, + the intensity will be considered as 1 (when colorant's value is bigger than 1) + or 0 (when colorant's value is less than 0). + + the intensity of cyan colorant + the intensity of magenta colorant + the intensity of yellow colorant + the intensity of black colorant + + + + Returns + DeviceCmyk + color which is lighter than given one + + the DeviceCmyk color to be made lighter + lighter color + + + + Returns + DeviceCmyk + color which is darker than given one + + the DeviceCmyk color to be made darker + darker color + + + Color space to specify shades of gray color. + + + Predefined white DeviceGray color. + + + Predefined gray DeviceGray color. + + + Predefined black DeviceGray color. + + + Creates DeviceGray color by given grayscale. + + Creates DeviceGray color by given grayscale. + The grayscale is considered to be in [0, 1] interval, if not, + the grayscale will be considered as 1 (when grayscale's value is bigger than 1) + or 0 (when grayscale's value is less than 0). + + the grayscale value + + + Creates DeviceGray color with grayscale value initialised as zero. + + + + Returns + DeviceGray + color which is lighter than given one + + the DeviceGray color to be made lighter + lighter color + + + + Returns + DeviceGray + color which is darker than given one + + the DeviceGray color to be made darker + darker color + + + Color space to specify colors according to RGB color model. + + + Creates DeviceRgb color by intensities of red, green and blue colorants. + + Creates DeviceRgb color by intensities of red, green and blue colorants. + The intensities are considered to be in [0, 255] gap, if not, + the intensity will be considered as 255 (when colorant's value is bigger than 255) + or 0 (when colorant's value is less than 0). + + the intensity of red colorant + the intensity of green colorant + the intensity of blue colorant + + + Creates DeviceRgb color by intensities of red, green and blue colorants. + + Creates DeviceRgb color by intensities of red, green and blue colorants. + The intensities are considered to be in [0, 1] interval, if not, + the intensity will be considered as 1 (when colorant's value is bigger than 1) + or 0 (when colorant's value is less than 0). + + the intensity of red colorant + the intensity of green colorant + the intensity of blue colorant + + + Creates DeviceRgb color with all colorants intensities initialised as zeroes. + + + + Returns + DeviceRgb + color which is lighter than given one + + the DeviceRgb color to be made lighter + lighter color + + + + Returns + DeviceRgb + color which is darker than given one + + the DeviceRgb color to be made darker + darker color + + + Creates IccBased color. + ICC profile stream. User is responsible for closing the stream. + + + Creates IccBased color. + ICC profile stream. User is responsible for closing the stream. + color value. + + + + This class is a HashMap that contains the names of colors as a key and the + corresponding BaseColor as value. + + + This class is a HashMap that contains the names of colors as a key and the + corresponding BaseColor as value. (Source: Wikipedia + http://en.wikipedia.org/wiki/Web_colors ) + + + + HashMap containing all the names and corresponding color values. + + + + A web color string without the leading # will be 3 or 6 characters long + and all those characters will be hex digits. + + + A web color string without the leading # will be 3 or 6 characters long + and all those characters will be hex digits. NOTE: colStr must be all + lower case or the current hex letter test will fail. + + + A non-null, lower case string that might describe an RGB color + in hex. + + Is this a web color hex string without the leading #? + + + Gives you a BaseColor based on a name. + + a name such as black, violet, cornflowerblue or #RGB or + #RRGGBB or RGB or RRGGBB or rgb(R,G,B) + + the corresponding BaseColor object. Never returns null. + if the String isn't a know representation of a color. + + + Creates an AES Cipher with CBC and padding PKCS5/7. + Paulo Soares + + + Creates a new instance of AESCipher + + + Creates an AES Cipher with CBC and no padding. + Paulo Soares + + + Creates a new instance of AESCipher + + + Creates a new instance of AesDecryption + + + Creates a new instance of ARCFOUREncryption + + + Bad password exception. + + + Exception class for exceptions in kernel module. + + + Object for more details + + + Creates a new instance of PdfException. + the detail message. + + + Creates a new instance of PdfException. + + the cause (which is saved for later retrieval by + + method). + + + + Creates a new instance of PdfException. + the detail message. + an object for more details. + + + Creates a new instance of PdfException. + the detail message. + + the cause (which is saved for later retrieval by + + method). + + + + Creates a new instance of PdfException. + the detail message. + + the cause (which is saved for later retrieval by + + method). + + an object for more details. + + + Sets additional params for Exception message. + additional params. + object itself. + + + Gets additional params for Exception message. + + + Creates a new BadPasswordException. + the detail message. + + the cause (which is saved for later retrieval by + + method). + + + + Creates a new BadPasswordException. + the detail message. + + + + This file is a helper class for internal usage only. + Be aware that it's API and functionality may be changed in future. + + + + + + + + An initialization vector generator for a CBC block encryption. + An initialization vector generator for a CBC block encryption. It's a random generator based on ARCFOUR. + + Paulo Soares + + + Creates a new instance of IVGenerator + + + Gets a 16 byte random initialization vector. + a 16 byte random initialization vector + + + Gets a random initialization vector. + the length of the initialization vector + a random initialization vector + + + Creates a new instance of OutputStreamCounter + + + + Closes this output stream and releases any system resources + associated with this stream. + + + Closes this output stream and releases any system resources + associated with this stream. The general contract of + close + is that it closes the output stream. A closed stream cannot perform + output operations and cannot be reopened. +

    + The + close + method of + OutputStream + does nothing. + + if an I/O error occurs. + + +

    + Flushes this output stream and forces any buffered output bytes + to be written out. + + + Flushes this output stream and forces any buffered output bytes + to be written out. The general contract of + flush + is + that calling it is an indication that, if any bytes previously + written have been buffered by the implementation of the output + stream, such bytes should immediately be written to their + intended destination. +

    + The + flush + method of + OutputStream + does nothing. + + if an I/O error occurs. + + +

    + Writes + b.length + bytes from the specified byte array + to this output stream. The general contract for + write(b) + is that it should have exactly the same effect as the call + write(b, 0, b.length) + . + + the data. + if an I/O error occurs. + +
    + + Writes the specified byte to this output stream. + + Writes the specified byte to this output stream. The general + contract for + write + is that one byte is written + to the output stream. The byte to be written is the eight + low-order bits of the argument + + . The 24 + high-order bits of + + are ignored. +

    + Subclasses of + OutputStream + must provide an + implementation for this method. + + + the + byte + . + + + if an I/O error occurs. In particular, an + IOException + may be thrown if the + output stream has been closed. + + + +

    + Writes + + bytes from the specified byte array + starting at offset + + to this output stream. + The general contract for + write(b, off, len) + is that + some of the bytes in the array + + are written to the + output stream in order; element + b[off] + is the first + byte written and + b[off+len-1] + is the last byte written + by this operation. +

    + The + write + method of + OutputStream + calls + the write method of one argument on each of the bytes to be + written out. Subclasses are encouraged to override this method and + provide a more efficient implementation. +

    + If + + is + + , a + NullPointerException + is thrown. +

    + If + + is negative, or + + is negative, or + off+len + is greater than the length of the array + + , then an IndexOutOfBoundsException is thrown. +

    + the data. + the start offset in the data. + the number of bytes to write. + + if an I/O error occurs. In particular, + an + IOException + is thrown if the output + stream is closed. + +
    + + Creates a new instance of OutputStreamCounter + + + + Writes + + bytes from the specified byte array + starting at offset + + to this output stream. + The general contract for + write(b, off, len) + is that + some of the bytes in the array + + are written to the + output stream in order; element + b[off] + is the first + byte written and + b[off+len-1] + is the last byte written + by this operation. +

    + The + write + method of + OutputStream + calls + the write method of one argument on each of the bytes to be + written out. Subclasses are encouraged to override this method and + provide a more efficient implementation. +

    + If + + is + + , a + NullPointerException + is thrown. +

    + If + + is negative, or + + is negative, or + off+len + is greater than the length of the array + + , then an IndexOutOfBoundsException is thrown. +

    + the data. + the start offset in the data. + the number of bytes to write. + + if an I/O error occurs. In particular, + an + IOException + is thrown if the output + stream is closed. + +
    + + Creates a new instance of OutputStreamStandardEncryption + + + + Writes + + bytes from the specified byte array + starting at offset + + to this output stream. + The general contract for + write(b, off, len) + is that + some of the bytes in the array + + are written to the + output stream in order; element + b[off] + is the first + byte written and + b[off+len-1] + is the last byte written + by this operation. +

    + The + write + method of + OutputStream + calls + the write method of one argument on each of the bytes to be + written out. Subclasses are encouraged to override this method and + provide a more efficient implementation. +

    + If + + is + + , a + NullPointerException + is thrown. +

    + If + + is negative, or + + is negative, or + off+len + is greater than the length of the array + + , then an IndexOutOfBoundsException is thrown. +

    + the data. + the start offset in the data. + the number of bytes to write. + + if an I/O error occurs. In particular, + an + IOException + is thrown if the output + stream is closed. + +
    + + + + + + + + + + Aiken Sam (aikensam@ieee.org) + + + The global encryption key + + + The encryption key for a particular object/generation. + + The encryption key for a particular object/generation. + It is recalculated with + + for every object individually based in its object/generation. + + + + + The encryption key length for a particular object/generation + It is recalculated with + + for every object individually based in its object/generation. + + + + Work area to prepare the object/generation bytes + + + + Note: For most of the supported security handlers algorithm to calculate encryption key for particular object + is the same. + + + + + + + + + + + + + + + + + + + + + + Gets bytes of String-value without considering encoding. + byte array + + + Creates a new instance of StandardDecryption + + + Describes abstract event. + + + A type of event. + + + Creates an event of the specified type. + type of event + + + Returns the type of this event. + type of this event + + + + IEventDispatcher implementation that forwards Events to registered + + implementations. + + + + Event dispatcher interface. + + + Adds new event handler. + a type of event to be handled. + event handler. + + + Dispatches an event. + + + + Dispatches a delayed event. + + Dispatches a delayed event. + Sometimes event cannot be handled immediately because event handler has not been set yet. + In this case event is placed into event ques of dispatcher and is waiting until handler is assigned. + + + + + + Checks if event dispatcher as an event handler assigned for a certain event type. + + + + + Removes event handler. + + + + + Remove all event handlers. + + + Interface for handling events. + + Interface for handling events. EventHandlers are added to the + + . + + + + Hook for handling events. + + Hook for handling events. Implementations can access the PdfDocument instance + associated to the specified Event or, if available, the PdfPage instance. + + the Event that needs to be processed + + + Event dispatched by PdfDocument. + + + Dispatched after page is created. + + + Dispatched after page is inserted/added into document. + + + Dispatched after page is removed from document. + + + Dispatched before page is closed and written. + + + The PdfPage associated with this event. + + + The PdfDocument associated with this event. + + + Creates a PdfDocumentEvent. + type of the event that fired this event + document that fired this event + + + Creates a PdfDocumentEvent. + type of the event that fired this event + page that fired this event + + + Returns the PdfDocument associated with this event. + the PdfDocument associated with this event + + + Returns the PdfPage associated with this event. + Returns the PdfPage associated with this event. Warning: this can be null. + the PdfPage associated with this event + + + This class allow to parse document font's encoding. + + + Marks object behind wrapper to be saved as indirect. + a document the indirect reference will belong to. + object itself. + + + Marks object behind wrapper to be saved as indirect. + a document the indirect reference will belong to. + object itself. + + + + Defines if the object behind this wrapper must be an indirect object in the + resultant document. + + + Defines if the object behind this wrapper must be an indirect object in the + resultant document. +

    + If this method returns true it doesn't necessarily mean that object + must be in the indirect state at any moment, but rather defines that + when the object will be written to the document it will be transformed into + indirect object if it's not indirect yet. +

    + Return value of this method shouldn't depend on any logic, it should return + always true or false. +
    + + true if in the resultant document the object behind the wrapper + must be indirect, otherwise false. + +
    + + + Some wrappers use object's indirect reference to obtain the + PdfDocument + to which the object belongs to. For this matter, for these wrappers it is implicitly defined + that they work with indirect objects only. Commonly these wrappers have two constructors: one with + PdfDocument + as parameter to create a new object, and the other one which + wraps around the given + PdfObject + . This method should be used in the second + type of constructors to ensure that wrapper will able to obtain the + PdfDocument + instance. + + + the + PdfObject + to be checked if it is indirect. + + + + false, if the font comes from PdfDocument. + + + true if the font is to be embedded in the PDF. + + + Indicates if all the glyphs and widths for that particular encoding should be included in the document. + + + + Get glyph by unicode + a unicode code point + + + Glyph + if it exists or .NOTDEF if supported, otherwise + + . + + + + Check whether font contains glyph with specified unicode. + a unicode code point + + true if font contains glyph, represented with the unicode code point, + otherwise false. + + + + Check whether font contains glyph with specified unicode. + a unicode code point + + true if font contains glyph, represented with the unicode code point, + otherwise false. + + + + Converts the text into bytes to be placed in the document. + + Converts the text into bytes to be placed in the document. + The conversion is done according to the font and the encoding and the characters + used are stored. + + the text to convert + the conversion + + + Returns the width of a certain character of this font in 1000 normalized units. + a certain character. + a width in Text Space. + + + Returns the width of a certain character of this font in points. + a certain character. + the font size. + a width in points. + + + Returns the width of a string of this font in 1000 normalized units. + a string content. + a width of string in Text Space. + + + + Gets the width of a + String + in points. + + + the + String + to get the width of + + the font size + the width in points + + + + Gets the descent of a + String + in points. The descent will always be + less than or equal to zero even if all the characters have an higher descent. + + + the + String + to get the descent of + + the font size + the descent in points + + + Gets the descent of a char code in points. + + Gets the descent of a char code in points. The descent will always be + less than or equal to zero even if all the characters have an higher descent. + + the char code to get the descent of + the font size + the descent in points + + + + Gets the ascent of a + String + in points. The ascent will always be + greater than or equal to zero even if all the characters have a lower ascent. + + + the + String + to get the ascent of + + the font size + the ascent in points + + + Gets the ascent of a char code in normalized 1000 units. + + Gets the ascent of a char code in normalized 1000 units. The ascent will always be + greater than or equal to zero even if all the characters have a lower ascent. + + the char code to get the ascent of + the font size + the ascent in points + + + + Indicates if all the glyphs and widths for that particular + encoding should be included in the document. + + false to include all the glyphs and widths. + + + + Indicates if all the glyphs and widths for that particular + encoding should be included in the document. + + + Indicates if all the glyphs and widths for that particular + encoding should be included in the document. When set to true + only the glyphs used will be included in the font. When set to false + and + + was not called the full font will be included + otherwise just the characters ranges will be included. + + new value of property subset + + + Adds a character range when subsetting. + + Adds a character range when subsetting. The range is an int array + where the first element is the start range inclusive and the second element is the + end range inclusive. Several ranges are allowed in the same array. + + the character range + + + + To manually flush a + PdfObject + behind this wrapper, you have to ensure + that this object is added to the document, i.e. it has an indirect reference. + Basically this means that before flushing you need to explicitly call + + . + For example: wrapperInstance.makeIndirect(document).flush(); + Note that not every wrapper require this, only those that have such warning in documentation. + + + + Creates a unique subset prefix to be added to the font name when the font is embedded and subset. + + the subset prefix + + + + Create + PdfStream + based on + + . + + original font data, must be not null. + + array to generate + Length* + keys, must be not null. + + + the PdfStream containing the font or + + , if there is an error reading the font. + + + Method will throw exception if + + is + + . + + + + Helper method for making an object indirect, if the object already is indirect. + + Helper method for making an object indirect, if the object already is indirect. + Useful for FontDescriptor and FontFile to make possible immediate flushing. + If there is no PdfDocument, mark the object as + MUST_BE_INDIRECT + . + + an object to make indirect. + + if current object isn't indirect, returns + + , otherwise + tree + + + + + This class provides helpful methods for creating fonts ready to be used in a + + + + + This is the default encoding to use. + + + This is the default value of the embedded variable. + + + This is the default value of the cached variable. + + + + Creates a default font, namely + + standard font with + + encoding. + + created font + if error occurred while creating the font, e.g. metrics loading failure + + + + + Creates a + + by existing font dictionary. + + the font dictionary to create the font from + + created + + instance + + + + + Creates a + + instance by the path of the font program file + + the path of the font program file + + created + + instance + + exception is thrown in case an I/O error occurs when reading the file + + + + + Creates a + + instance by the path of the font program file and given encoding. + + the path of the font program file + + the font encoding. See + + + + created + + instance + + exception is thrown in case an I/O error occurs when reading the file + + + + + Creates a + + instance from the TrueTypeCollection represented by its byte contents. + + the byte contents of the TrueTypeCollection + the index of the font in the collection, zero-based + + the encoding of the font to be created. See + + + indicates whether the font is to be embedded into the target document + indicates whether the font will be cached + + created + + instance + + in case the contents of the TrueTypeCollection is mal-formed or an error occurred during reading the font + + + + + Creates a + + instance from the TrueTypeCollection given by the path to the .ttc file. + + the path of the .ttc file + the index of the font in the collection, zero-based + + the encoding of the font to be created. See + + + indicates whether the font is to be embedded into the target document + indicates whether the font will be cached + + created + + instance + + + in case the file is not found, contents of the TrueTypeCollection is mal-formed + or an error occurred during reading the font + + + + + Created a + + instance given the path to the font file. + + the font program file + indicates whether the font is to be embedded into the target document + + created + + instance + + in case the file is not found or the contents of the font file is mal-formed + + + + + Created a + + instance given the path to the font file. + + the font program file + + the encoding of the font to be created. See + + + indicates whether the font is to be embedded into the target document + + created + + instance + + in case the file is not found or the contents of the font file is mal-formed + + + + + Created a + + instance given the path to the font file. + + the font program file + + the encoding of the font to be created. See + + + indicates whether the font is to be embedded into the target document + indicates whether the font will be cached + + created + + instance + + in case the file is not found or the contents of the font file is mal-formed + + + + + Created a + + instance given the given underlying + + instance. + + + the font program of the + + instance to be created + + + the encoding of the font to be created. See + + + indicates whether the font is to be embedded into the target document + + created + + instance + + this exception is actually never thrown and will be removed in 7.1. + + + + + Created a + + instance given the given underlying + + instance. + + + the font program of the + + instance to be created + + + the encoding of the font to be created. See + + + + created + + instance + + this exception is actually never thrown and will be removed in 7.1. + + + + + Created a + + instance given the given underlying + + instance. + + + the font program of the + + instance to be created + + + created + + instance + + this exception is actually never thrown and will be removed in 7.1. + + + + + Created a + + instance by the bytes of the underlying font program. + + the bytes of the underlying font program + + created + + instance + + this exception is actually never thrown. Will be removed in 7.1. + + + + Created a + + instance by the bytes of the underlying font program. + + the bytes of the underlying font program + indicates whether the font is to be embedded into the target document + + created + + instance + + this exception is actually never thrown. Will be removed in 7.1. + + + + Created a + + instance by the bytes of the underlying font program. + + the bytes of the underlying font program + + the encoding of the font to be created. See + + + indicates whether the font is to be embedded into the target document + + created + + instance + + this exception is actually never thrown. Will be removed in 7.1. + + + + Created a + + instance by the bytes of the underlying font program. + + the bytes of the underlying font program + + the encoding of the font to be created. See + + + indicates whether the font is to be embedded into the target document + indicates whether the font will be cached + + created + + instance + + this exception is actually never thrown. Will be removed in 7.1. + + + + Creates a new instance of + + + the target document of the new font + indicates whether the font will be colorized + created font + actually this exception is never thrown. This will be removed in 7.1. + + + + + Creates + + based on registered + + 's. + + + + + + + + + + + + + Creates + + based on registered + + 's. + + + + + + + + + + + + + Creates + + based on registered + + 's. + + + + + + + + + + + + + Creates + + based on registered + + 's. + + + + + + + + + + + + + Creates + + based on registered + + 's. + + + + + + + + + + + + + Creates + + based on registered + + 's. + + + + + + + + + + + + Register a font by giving explicitly the font family and name. + the font family + the font name + the font path + + + Registers a .ttf, .otf, .afm, .pfm, or a .ttc font file. + + Registers a .ttf, .otf, .afm, .pfm, or a .ttc font file. + In case if TrueTypeCollection (.ttc), an additional parameter may be specified defining the index of the font + to be registered, e.g. "path/to/font/collection.ttc,0". The index is zero-based. + + the path to a font file + + + Register a font file and use an alias for the font contained in it. + the path to a font file + the alias you want to use for the font + + + Registers all the fonts in a directory. + the directory path to be registered as a font directory path + the number of fonts registered + + + Register fonts in some probable directories. + + Register fonts in some probable directories. It usually works in Windows, + Linux and Solaris. + + the number of fonts registered + + + Gets a set of registered font names. + a set of registered fonts + + + Gets a set of registered font families. + a set of registered font families + + + Checks if a certain font is registered. + the name of the font that has to be checked. + true if the font is found, false otherwise + + + Checks if the provided dictionary is a valid font dictionary of the provided font type. + true if the passed dictionary is a valid dictionary, false otherwise + + + Forces the output of the width array. + Forces the output of the width array. Only matters for the 14 built-in fonts. + + + The array used with single byte encodings. + + + Gets the state of the property. + value of property forceWidthsOutput + + + + Set to + + to force the generation of the widths array. + + + + + to force the generation of the widths array + + + + + Generates the font descriptor for this font or + + if it is one of the 14 built in fonts. + + + the PdfDictionary containing the font descriptor or + + . + + + + Note. + Note. For TrueType FontNames.getStyle() is the same to Subfamily(). So, we shouldn't add style to /BaseFont. + + + + Generates the CIDFontTyte2 dictionary. + the indirect reference to the font descriptor + a name of the font + the horizontal width metrics + fully initialized CIDFont + + + Creates a ToUnicode CMap to allow copy and paste from Acrobat. + + metrics[0] contains the glyph index and metrics[2] + contains the Unicode code + + the stream representing this CMap or null + + + The method used to sort the metrics array. + the first element + the second element + the comparison + + + + If the embedded flag is + + or if the font is one of the 14 built in types, it returns + + , + otherwise the font is read and output in a PdfStream object. + + + + Low-level API class for Type 3 fonts. + + Low-level API class for Type 3 fonts. +

    + In Type 3 fonts, glyphs are defined by streams of PDF graphics operators. + These streams are associated with character names. A separate encoding entry + maps character codes to the appropriate character names for the glyphs. +

    + To be able to be wrapped with this + + the + + must be indirect. + + + +

    Creates a Type3 font. + defines whether the glyph color is specified in the glyph descriptions in the font. + +
    + + Creates a Type3 font based on an existing font dictionary, which must be an indirect object. + a dictionary of type /Font, must have an indirect reference. + + + Defines a glyph. + Defines a glyph. If the character was already defined it will return the same content + the character to match this glyph. + the advance this character will have + + the X lower left corner of the glyph bounding box. If the colorize option is + true the value is ignored + + + the Y lower left corner of the glyph bounding box. If the colorize option is + true the value is ignored + + + the X upper right corner of the glyph bounding box. If the colorize option is + true the value is ignored + + + the Y upper right corner of the glyph bounding box. If the colorize option is + true the value is ignored + + a content where the glyph can be defined + + + + Gets first empty code, that could use with + addSymbol() + + code from 1 to 255 or -1 if all slots are busy. + + + The content where Type3 glyphs are written to. + + + PdfCanvas class represents an algorithm for writing data into content stream. + + PdfCanvas class represents an algorithm for writing data into content stream. + To write into page content, create PdfCanvas from a page instance. + To write into form XObject, create PdfCanvas from a form XObject instance. + Make sure to call PdfCanvas.release() after you finished writing to the canvas. + It will save some memory. + + + + a LIFO stack of graphics state saved states. + + + the current graphics state. + + + the content stream for this canvas object. + + + the resources for the page that this canvas belongs to. + + + + the document that the resulting content stream of this canvas will be written to. + + + a counter variable for the marked content stack. + + + The list where we save/restore the layer depth. + + + Creates PdfCanvas from content stream of page, form XObject, pattern etc. + @see PdfStream. + the resources, a specialized dictionary that can be used by PDF instructions in the content stream + + the document that the resulting content stream will be written to + + + Convenience method for fast PdfCanvas creation by a certain page. + page to create canvas from. + + + Convenience method for fast PdfCanvas creation by a certain page. + page to create canvas from. + + true to wrap all old content streams into q/Q operators so that the state of old + content streams would not affect the new one + + + + Creates a PdfCanvas from a PdfFormXObject. + the PdfFormXObject used to create the PdfCanvas + the document to which the resulting content stream will be written + + + Convenience method for fast PdfCanvas creation by a certain page. + @see PdfDocument. + page number. + + + Get the resources of the page that this canvas belongs to.. + PdfResources of the page that this canvas belongs to.. + + + Attaches new content stream to the canvas. + + Attaches new content stream to the canvas. + This method is supposed to be used when you want to write in different PdfStream keeping context (gsStack, currentGs, ...) the same. + + a content stream to attach. + + + + Gets current + + . + + container containing properties for the current state of the canvas. + + + Releases the canvas. + + Releases the canvas. + Use this method after you finished working with canvas. + + + + Saves graphics state. + current canvas. + + + Restores graphics state. + current canvas. + + + + Concatenates the 2x3 affine transformation matrix to the current matrix + in the content stream managed by this Canvas. + + + Concatenates the 2x3 affine transformation matrix to the current matrix + in the content stream managed by this Canvas. + Contrast with + PdfCanvas#setTextMatrix + + operand 1,1 in the matrix. + operand 1,2 in the matrix. + operand 2,1 in the matrix. + operand 2,2 in the matrix. + operand 3,1 in the matrix. + operand 3,2 in the matrix. + current canvas + + + + Concatenates the 2x3 affine transformation matrix to the current matrix + in the content stream managed by this Canvas. + + + Concatenates the 2x3 affine transformation matrix to the current matrix + in the content stream managed by this Canvas. + If an array not containing the 6 values of the matrix is passed, + The current canvas is returned unchanged. + + affine transformation stored as a PdfArray with 6 values + current canvas + + + + Concatenates the affine transformation matrix to the current matrix + in the content stream managed by this Canvas. + + + Concatenates the affine transformation matrix to the current matrix + in the content stream managed by this Canvas. + See also + + + current canvas + + + Begins text block (PDF BT operator). + current canvas. + + + Ends text block (PDF ET operator). + current canvas. + + + Begins variable text block + current canvas + + + Ends variable text block + current canvas + + + Sets font and size (PDF Tf operator). + @see PdfFont. + Font size. + current canvas. + + + Moves text by shifting text line matrix (PDF Td operator). + x coordinate. + y coordinate. + current canvas. + + + + + Moves to the start of the next line. + current canvas. + + + + Moves to the next line and shows + + . + + the text to write + current canvas. + + + Moves to the next line and shows text string, using the given values of the character and word spacing parameters. + + a parameter + a parameter + the text to write + current canvas. + + + Sets text rendering mode. + text rendering mode @see PdfCanvasConstants. + current canvas. + + + + Sets the word spacing parameter. + a parameter + current canvas. + + + Sets the character spacing parameter. + a parameter + current canvas. + + + Sets the horizontal scaling parameter. + a parameter. + current canvas. + + + Replaces the text matrix. + + Replaces the text matrix. Contrast with + PdfCanvas#concatMatrix + + operand 1,1 in the matrix. + operand 1,2 in the matrix. + operand 2,1 in the matrix. + operand 2,2 in the matrix. + operand 3,1 in the matrix. + operand 3,2 in the matrix. + current canvas. + + + Changes the text matrix. + operand 3,1 in the matrix. + operand 3,2 in the matrix. + current canvas. + + + Shows text (operator Tj). + text to show. + current canvas. + + + Shows text (operator Tj). + text to show. + current canvas. + + + Shows text (operator TJ) + + the text array. Each element of array can be a string or a number. + If the element is a string, this operator shows the string. + If it is a number, the operator adjusts the text position by that amount. + The number is expressed in thousandths of a unit of text space. + This amount is subtracted from the current horizontal or vertical coordinate, depending on the writing mode. + + current canvas. + + + Move the current point (x, y), omitting any connecting line segment. + x coordinate. + y coordinate. + current canvas. + + + Appends a straight line segment from the current point (x, y). + + Appends a straight line segment from the current point (x, y). The new current + point is (x, y). + + x coordinate. + y coordinate. + current canvas. + + + Appends a Bêzier curve to the path, starting from the current point. + x coordinate of the first control point. + y coordinate of the first control point. + x coordinate of the second control point. + y coordinate of the second control point. + x coordinate of the ending point. + y coordinate of the ending point. + current canvas. + + + Appends a B??zier curve to the path, starting from the current point. + x coordinate of the second control point. + y coordinate of the second control point. + x coordinate of the ending point. + y coordinate of the ending point. + current canvas. + + + Appends a B??zier curve to the path, starting from the current point. + x coordinate of the first control point. + y coordinate of the first control point. + x coordinate of the ending point. + y coordinate of the ending point. + current canvas. + + + + Draws a partial ellipse inscribed within the rectangle x1,y1,x2,y2, + starting at startAng degrees and covering extent degrees. + + + Draws a partial ellipse inscribed within the rectangle x1,y1,x2,y2, + starting at startAng degrees and covering extent degrees. Angles + start with 0 to the right (+x) and increase counter-clockwise. + + a corner of the enclosing rectangle. + a corner of the enclosing rectangle. + a corner of the enclosing rectangle. + a corner of the enclosing rectangle. + starting angle in degrees. + angle extent in degrees. + current canvas. + + + Draws an ellipse inscribed within the rectangle x1,y1,x2,y2. + a corner of the enclosing rectangle + a corner of the enclosing rectangle + a corner of the enclosing rectangle + a corner of the enclosing rectangle + current canvas. + + + Generates an array of bezier curves to draw an arc. + + Generates an array of bezier curves to draw an arc. +

    + (x1, y1) and (x2, y2) are the corners of the enclosing rectangle. + Angles, measured in degrees, start with 0 to the right (the positive X + axis) and increase counter-clockwise. The arc extends from startAng + to startAng+extent. i.e. startAng=0 and extent=180 yields an openside-down + semi-circle. +

    + The resulting coordinates are of the form double[]{x1,y1,x2,y2,x3,y3, x4,y4} + such that the curve goes from (x1, y1) to (x4, y4) with (x2, y2) and + (x3, y3) as their respective Bezier control points. +

    + Note: this code was taken from ReportLab (www.reportlab.org), an excellent + PDF generator for Python (BSD license: http://www.reportlab.org/devfaq.html#1.3 ). + + a corner of the enclosing rectangle. + a corner of the enclosing rectangle. + a corner of the enclosing rectangle. + a corner of the enclosing rectangle. + starting angle in degrees. + angle extent in degrees. + a list of double[] with the bezier curves. + + +

    Draws a rectangle. + x coordinate of the starting point. + y coordinate of the starting point. + width. + height. + current canvas. +
    + + Draws a rectangle. + a rectangle to be drawn + current canvas. + + + Draws rounded rectangle. + x coordinate of the starting point. + y coordinate of the starting point. + width. + height. + radius of the arc corner. + current canvas. + + + Draws a circle. + Draws a circle. The endpoint will (x+r, y). + x center of circle. + y center of circle. + radius of circle. + current canvas. + + + Paints a shading object and adds it to the resources of this canvas + + current canvas. + + + + Closes the current subpath by appending a straight line segment from the current point + to the starting point of the subpath. + + current canvas. + + + Closes the path, fills it using the even-odd rule to determine the region to fill and strokes it. + + current canvas. + + + Closes the path, fills it using the non-zero winding number rule to determine the region to fill and strokes it. + + current canvas. + + + Ends the path without filling or stroking it. + current canvas. + + + Strokes the path. + current canvas. + + + + Modify the current clipping path by intersecting it with the current path, using the + nonzero winding rule to determine which regions lie inside the clipping path. + + current canvas. + + + + Modify the current clipping path by intersecting it with the current path, using the + even-odd rule to determine which regions lie inside the clipping path. + + current canvas. + + + Closes the path and strokes it. + current canvas. + + + Fills current path. + current canvas. + + + Fills the path using the non-zero winding number rule to determine the region to fill and strokes it. + + current canvas. + + + EOFills current path. + current canvas. + + + Fills the path, using the even-odd rule to determine the region to fill and strokes it. + current canvas. + + + Sets line width. + line width. + current canvas. + + + + Sets the line cap style, the shape to be used at the ends of open subpaths + when they are stroked. + + + current canvas. + for possible values. + + + + Sets the line join style, the shape to be used at the corners of paths + when they are stroked. + + + current canvas. + for possible values. + + + + Sets the miter limit, a parameter specifying the maximum length a miter join + may extend beyond the join point, relative to the angle of the line segments. + + + current canvas. + + + Changes the value of the line dash pattern. + + Changes the value of the line dash pattern. +

    + The line dash pattern controls the pattern of dashes and gaps used to stroke paths. + It is specified by an array and a phase. The array specifies the length + of the alternating dashes and gaps. The phase specifies the distance into the dash + pattern to start the dash. + + the value of the phase + current canvas. + + +

    Changes the value of the line dash pattern. + + Changes the value of the line dash pattern. +

    + The line dash pattern controls the pattern of dashes and gaps used to stroke paths. + It is specified by an array and a phase. The array specifies the length + of the alternating dashes and gaps. The phase specifies the distance into the dash + pattern to start the dash. + + the value of the phase + the number of units that must be 'on' (equals the number of units that must be 'off'). + + current canvas. + + +

    Changes the value of the line dash pattern. + + Changes the value of the line dash pattern. +

    + The line dash pattern controls the pattern of dashes and gaps used to stroke paths. + It is specified by an array and a phase. The array specifies the length + of the alternating dashes and gaps. The phase specifies the distance into the dash + pattern to start the dash. + + the value of the phase + the number of units that must be 'on' + the number of units that must be 'off' + current canvas. + + +

    Changes the value of the line dash pattern. + + Changes the value of the line dash pattern. +

    + The line dash pattern controls the pattern of dashes and gaps used to stroke paths. + It is specified by an array and a phase. The array specifies the length + of the alternating dashes and gaps. The phase specifies the distance into the dash + pattern to start the dash. + + length of the alternating dashes and gaps + the value of the phase + current canvas. + + +

    Set the rendering intent. + + Set the rendering intent. possible values are: PdfName.AbsoluteColorimetric, + PdfName.RelativeColorimetric, PdfName.Saturation, PdfName.Perceptual. + + a PdfName containing a color metric + current canvas. +
    + + + Changes the current color for filling paths. + fill color. + current canvas. + + + Changes the current color for stroking paths. + stroke color. + current canvas. + + + Changes the current color for paths. + the new color. + set fill color (true) or stroke color (false) + current canvas. + + + Changes the current color for paths. + the color space of the new color + a list of numerical values with a length corresponding to the specs of the color space. Values should be in the range [0,1] + + set fill color (true) or stroke color (false) + current canvas. + + + Changes the current color for paths with an explicitly defined pattern. + the color space of the new color + a list of numerical values with a length corresponding to the specs of the color space. Values should be in the range [0,1] + + a pattern for the colored line or area + set fill color (true) or stroke color (false) + current canvas. + + + Changes the current color for filling paths to a grayscale value. + a grayscale value in the range [0,1] + current canvas. + + + Changes the current color for stroking paths to a grayscale value. + a grayscale value in the range [0,1] + current canvas. + + + Changes the current color for filling paths to black. + current canvas. + + + Changes the current color for stroking paths to black. + current canvas. + + + Changes the current color for filling paths to an RGB value. + a red value in the range [0,1] + a green value in the range [0,1] + a blue value in the range [0,1] + current canvas. + + + Changes the current color for stroking paths to an RGB value. + a red value in the range [0,1] + a green value in the range [0,1] + a blue value in the range [0,1] + current canvas. + + + Adds or changes the shading of the current fill color path. + the shading + current canvas. + + + Adds or changes the shading of the current stroke color path. + the shading + current canvas. + + + Changes the current color for filling paths to black. + current canvas. + + + Changes the current color for stroking paths to black. + current canvas. + + + Changes the current color for filling paths to a CMYK value. + a cyan value in the range [0,1] + a magenta value in the range [0,1] + a yellow value in the range [0,1] + a key (black) value in the range [0,1] + current canvas. + + + Changes the current color for stroking paths to a CMYK value. + a cyan value in the range [0,1] + a magenta value in the range [0,1] + a yellow value in the range [0,1] + a key (black) value in the range [0,1] + current canvas. + + + Changes the current color for filling paths to black. + current canvas. + + + Changes the current color for stroking paths to black. + current canvas. + + + + Ends OCG layer. + current canvas. + + + Creates Image XObject from image and adds it to canvas (as Image XObject). + + the + PdfImageXObject + object + + an element of the transformation matrix + an element of the transformation matrix + an element of the transformation matrix + an element of the transformation matrix + an element of the transformation matrix + an element of the transformation matrix + created Image XObject. + + + Creates Image XObject from image and adds it to canvas. + + the + PdfImageXObject + object + + an element of the transformation matrix + an element of the transformation matrix + an element of the transformation matrix + an element of the transformation matrix + an element of the transformation matrix + an element of the transformation matrix + true if to add image as in-line. + created Image XObject or null in case of in-line image (asInline = true). + + + Creates Image XObject from image and adds it to canvas. + + + true if to add image as in-line. + created XObject or null in case of in-line image (asInline = true). + + + Creates Image XObject from image and adds it to canvas. + + + + true if to add image as in-line. + created XObject or null in case of in-line image (asInline = true). + + + Creates Image XObject from image and adds it to the specified position with specified width preserving aspect ratio. + + + + + + true if to add image as in-line. + created XObject or null in case of in-line image (asInline = true). + + + Creates Image XObject from image and adds it to the specified position with specified width preserving aspect ratio. + + + + + + true if to add image as in-line. + + created XObject or null in case of in-line image (asInline = true). + + + + Adds + PdfXObject + to canvas. + + + the + PdfImageXObject + object + + an element of the transformation matrix + an element of the transformation matrix + an element of the transformation matrix + an element of the transformation matrix + an element of the transformation matrix + an element of the transformation matrix + current canvas. + + + + Adds + PdfXObject + to the specified position. + + + + + current canvas. + + + + Adds + PdfXObject + to specified rectangle on canvas. + + + + current canvas. + + + + Adds + PdfXObject + to the specified position with specified width preserving aspect ratio. + + + + + + current canvas. + + + + Adds + PdfXObject + to the specified position with specified height preserving aspect ratio. + + + + + + + current canvas. + + + Sets the ExtGState dictionary for the current graphics state + a dictionary that maps resource names to graphics state parameter dictionaries + current canvas. + + + Sets the ExtGState dictionary for the current graphics state + a dictionary that maps resource names to graphics state parameter dictionaries + current canvas. + + + Manually start a Marked Content sequence. + Manually start a Marked Content sequence. Used primarily for Tagged PDF + the type of content contained + current canvas + + + Manually start a Marked Content sequence with properties. + Manually start a Marked Content sequence with properties. Used primarily for Tagged PDF + the type of content that will be contained + the properties of the content, including Marked Content ID. If null, the PDF marker is BMC, else it is BDC + + current canvas + + + Manually end a Marked Content sequence. + Manually end a Marked Content sequence. Used primarily for Tagged PDF + current canvas + + + Manually open a canvas tag, beginning a Marked Content sequence. + Manually open a canvas tag, beginning a Marked Content sequence. Used primarily for Tagged PDF + the type of content that will be contained + current canvas + + + Open a tag, beginning a Marked Content sequence. + + Open a tag, beginning a Marked Content sequence. This MC sequence will belong to the tag from the document + logical structure. +
    + CanvasTag will be automatically created with assigned mcid(Marked Content id) to it. Mcid serves as a reference + between Marked Content sequence and logical structure element. +
    + reference to the tag from the document logical structure + current canvas +
    + + Manually close a tag, ending a Marked Content sequence. + Manually close a tag, ending a Marked Content sequence. Used primarily for Tagged PDF + current canvas + + + + Outputs a + String + directly to the content. + + + the + String + + current canvas. + + + + Outputs a + char + directly to the content. + + + the + char + + current canvas. + + + + Outputs a + float + directly to the content. + + + the + float + + current canvas. + + + Please, use this method with caution and only if you know what you are doing. + + Please, use this method with caution and only if you know what you are doing. + Manipulating with underlying stream object of canvas could lead to corruption of it's data. + + the content stream to which this canvas object writes. + + + + Adds + PdfImageXObject + to canvas. + + + the + PdfImageXObject + object + + an element of the transformation matrix + an element of the transformation matrix + an element of the transformation matrix + an element of the transformation matrix + an element of the transformation matrix + an element of the transformation matrix + + + + Adds + PdfFormXObject + to canvas. + + + the + PdfImageXObject + object + + an element of the transformation matrix + an element of the transformation matrix + an element of the transformation matrix + an element of the transformation matrix + an element of the transformation matrix + an element of the transformation matrix + current canvas. + + + + Adds + PdfFormXObject + to the specified position. + + + + + current canvas. + + + + Adds + PdfFormXObject + to specified rectangle on canvas. + + + + current canvas. + + + + Adds I + PdfFormXObject + to the specified position with specified width preserving aspect ratio. + + + + + + current canvas. + + + + Adds + PdfFormXObject + to the specified position with specified height preserving aspect ratio. + + + + + + + + + + + Adds + PdfImageXObject + to canvas. + + + the + PdfImageXObject + object + + an element of the transformation matrix + an element of the transformation matrix + an element of the transformation matrix + an element of the transformation matrix + an element of the transformation matrix + an element of the transformation matrix + canvas a reference to this object. + + + + Adds + PdfImageXObject + to the specified position. + + + + + + + + + Adds + PdfImageXObject + to specified rectangle on canvas. + + + + + + + + Adds + PdfImageXObject + to the specified position with specified width preserving aspect ratio. + + + + + + + + + + Adds + PdfImageXObject + to the specified position with specified height preserving aspect ratio. + + + + + + + current canvas. + + + + A helper to insert into the content stream the + + converted to bytes according to the font's encoding. + + the text to write. + + + Creates a Type3Glyph canvas with a new Content Stream. + the document that this canvas is created for + + + Creates a Type3Glyph canvas with a non-empty Content Stream. + + + PdfStream + from existed document. + + + document to which + PdfStream + belongs. + + + + Indicates if the glyph color specified in the glyph description or not. + whether the glyph color is specified in the glyph description or not + + + Writes the width and optionally the bounding box parameters for a glyph + the advance this character will have + + the X lower left corner of the glyph bounding box. If the isColor option is + true the value is ignored + + + the Y lower left corner of the glyph bounding box. If the isColor option is + true the value is ignored + + + the X upper right corner of the glyph bounding box. If the isColor option is + true the value is ignored + + + the Y upper right corner of the glyph bounding box. If the isColor option is + true the value is ignored + + + defines whether the glyph color is specified in the glyph description in the font. + The consequence of value true is that the bounding box parameters are ignored. + + + + Creates Image XObject from image and adds it to canvas. + + Creates Image XObject from image and adds it to canvas. Performs additional checks to make + sure that we only add mask images to not colorized type 3 fonts. + + + the + PdfImageXObject + object + + an element of the transformation matrix + an element of the transformation matrix + an element of the transformation matrix + an element of the transformation matrix + an element of the transformation matrix + an element of the transformation matrix + true if to add image as in-line. + created Image XObject or null in case of in-line image (asInline = true). + + + The type of affine transformation. + + The type of affine transformation. See + + . + + + + The type of affine transformation. + + The type of affine transformation. See + + . + + + + The type of affine transformation. + + The type of affine transformation. See + + . + + + + The type of affine transformation. + + The type of affine transformation. See + + . + + + + The type of affine transformation. + + The type of affine transformation. See + + . + + + + The type of affine transformation. + + The type of affine transformation. See + + . + + + + The type of affine transformation. + + The type of affine transformation. See + + . + + + + The type of affine transformation. + + The type of affine transformation. See + + . + + + + The type of affine transformation. + + The type of affine transformation. See + + . + + + + The type of affine transformation. + + The type of affine transformation. See + + . + + + + The TYPE_UNKNOWN is an initial type value. + + + The min value equivalent to zero. + The min value equivalent to zero. If absolute value less then ZERO it considered as zero. + + + The values of transformation matrix + + + The transformation type + + + Method returns type of affine transformation. + + Method returns type of affine transformation. + Transform matrix is + m00 m01 m02 + m10 m11 m12 + According analytic geometry new basis vectors are (m00, m01) and (m10, m11), + translation vector is (m02, m12). Original basis vectors are (1, 0) and (0, 1). + Type transformations classification: +
      +
    • + + - new basis equals original one and zero translation
    • +
    • + + - translation vector isn't zero
    • +
    • + + - vectors length of new basis equals
    • +
    • + + - vectors length of new basis doesn't equal
    • +
    • + + - new basis vector orientation differ from original one
    • +
    • + + - new basis is rotated by 90, 180, 270, or 360 degrees
    • +
    • + + - new basis is rotated by arbitrary angle
    • +
    • + + - transformation can't be inversed
    • +
    +
    +
    + + Multiply matrix of two AffineTransform objects + - the AffineTransform object is a multiplicand + - the AffineTransform object is a multiplier + an AffineTransform object that is a result of t1 multiplied by matrix t2. + + + + + + + + + + + + + + + + + + Represents a Bezier curve. + + + Represents segment from a PDF path. + + + Treat base points as the points which are enough to construct a shape. + + Treat base points as the points which are enough to construct a shape. + E.g. for a bezier curve they are control points, for a line segment - the start and the end points + of the segment. + + + Ordered + + consisting of shape's base points. + + + + + If the distance between a point and a line is less than + this constant, then we consider the point lies on the line. + + + + + In the case when neither the line ((x1, y1), (x4, y4)) passes + through both (x2, y2) and (x3, y3) nor (x1, y1) = (x4, y4) we + use the square of the sum of the distances mentioned below in + compare to this field as the criterion of good approximation. + + + In the case when neither the line ((x1, y1), (x4, y4)) passes + through both (x2, y2) and (x3, y3) nor (x1, y1) = (x4, y4) we + use the square of the sum of the distances mentioned below in + compare to this field as the criterion of good approximation. + 1. The distance between the line and (x2, y2) + 2. The distance between the line and (x3, y3) + + + + + The Manhattan distance is used in the case when either the line + ((x1, y1), (x4, y4)) passes through both (x2, y2) and (x3, y3) + or (x1, y1) = (x4, y4). + + + The Manhattan distance is used in the case when either the line + ((x1, y1), (x4, y4)) passes through both (x2, y2) and (x3, y3) + or (x1, y1) = (x4, y4). The essential observation is that when + the curve is a uniform speed straight line from end to end, the + control points are evenly spaced from beginning to end. Our measure + of how far we deviate from that ideal uses distance of the middle + controls: point 2 should be halfway between points 1 and 3; point 3 + should be halfway between points 2 and 4. + + + + Constructs new bezier curve. + Curve's control points. + + + + + + + You can adjust precision of the approximation by varying the following + parameters: + + , + + , + + + + + + containing points of piecewise linear approximation + for this bezier curve. + + + + Represents a line. + + + Constructs a new zero-length line starting at zero. + + + Constructs a new line based on the given coordinates. + + + Constructs a new line based on the given coordinates. + + + Represents a line segment in a particular coordinate system. + Represents a line segment in a particular coordinate system. This class is immutable. + + + Start vector of the segment. + + + End vector of the segment. + + + Creates a new line segment. + the start point of a line segment. + the end point of a line segment. + + + the start point + + + the end point + + + the length of this line segment + + + Computes the bounding rectangle for this line segment. + + Computes the bounding rectangle for this line segment. The rectangle has a rotation 0 degrees + with respect to the coordinate system that the line system is in. For example, if a line segment + is 5 unit long and sits at a 37 degree angle from horizontal, the bounding rectangle will have + origin of the lower left hand end point of the segment, with width = 4 and height = 3. + + the bounding rectangle + + + Computes the bounding rectangle for this line segment. + + Computes the bounding rectangle for this line segment. The rectangle has a rotation 0 degrees + with respect to the coordinate system that the line system is in. For example, if a line segment + is 5 unit long and sits at a 37 degree angle from horizontal, the bounding rectangle will have + origin of the lower left hand end point of the segment, with width = 4 and height = 3. + + the bounding rectangle + + + Transforms the segment by the specified matrix + the matrix for the transformation + the transformed segment + + + Checks if a segment contains another segment in itself + a segment to be checked + true if this segment contains other one, false otherwise + + + Checks if a segment contains a given point in itself + a point to be checked + true if this segment contains given point, false otherwise + + + + Keeps all the values of a 3 by 3 matrix and allows you to + do some math with matrices. + + + + the row=1, col=1 position ('a') in the matrix. + + + the row=1, col=2 position ('b') in the matrix. + + + the row=1, col=3 position (always 0 for 2-D) in the matrix. + + + the row=2, col=1 position ('c') in the matrix. + + + the row=2, col=2 position ('d') in the matrix. + + + the row=2, col=3 position (always 0 for 2-D) in the matrix. + + + the row=3, col=1 ('e', or X translation) position in the matrix. + + + the row=3, col=2 ('f', or Y translation) position in the matrix. + + + the row=3, col=3 position (always 1 for 2-D) in the matrix. + + + + constructs a new Matrix with identity. + + + Constructs a matrix that represents translation + + + + + Creates a Matrix with 6 specified entries + + + + + + + + + + + multiplies this matrix by 'b' and returns the result + See http://en.wikipedia.org/wiki/Matrix_multiplication + + The matrix to multiply by + the resulting matrix + + + Subtracts a matrix from this matrix and returns the results + the matrix to subtract from this matrix + a Matrix object + + + Computes the determinant of the matrix. + the determinant of the matrix + + + Checks equality of matrices. + the other Matrix that needs to be compared with this matrix. + true if both matrices are equal + + + + Generates a hash code for this object. + the hash code of this object + + + + Generates a String representation of the matrix. + the values, delimited with tabs and newlines. + + + + NoninvertibleTransformException + Denis M. Kishenko + + + Creates a new NoninvertibleTransformException. + the detail message. + + + Calculates the common rectangle which includes all the input rectangles. + list of input rectangles. + common rectangle. + + + Sets the rectangle by the coordinates, specifying its lower left and upper right points. + + Sets the rectangle by the coordinates, specifying its lower left and upper right points. May be used in chain. +
    +
    + Note: this method will normalize coordinates, so the rectangle will have non negative width and height, + and its x and y coordinates specified lower left point. +
    + the X coordinate of lower left point + the Y coordinate of lower left point + the X coordinate of upper right point + the Y coordinate of upper right point + + this + + instance. + +
    + + Gets the X coordinate of lower left point. + the X coordinate of lower left point. + + + Sets the X coordinate of lower left point. + Sets the X coordinate of lower left point. May be used in chain. + the X coordinate of lower left point to be set. + + this + + instance. + + + + Gets the Y coordinate of lower left point. + the Y coordinate of lower left point. + + + Sets the Y coordinate of lower left point. + Sets the Y coordinate of lower left point. May be used in chain. + the Y coordinate of lower left point to be set. + + this + + instance. + + + + Gets the width of rectangle. + the width of rectangle. + + + Sets the width of rectangle. + Sets the width of rectangle. May be used in chain. + the the width of rectangle to be set. + + this + + instance. + + + + Gets the height of rectangle. + the height of rectangle. + + + Sets the height of rectangle. + Sets the height of rectangle. May be used in chain. + the the width of rectangle to be set. + + this + + instance. + + + + Increases the height of rectangle by the given value. + Increases the height of rectangle by the given value. May be used in chain. + the value of the extra height to be added. + + this + + instance. + + + + Decreases the height of rectangle by the given value. + Decreases the height of rectangle by the given value. May be used in chain. + the value of the extra height to be subtracted. + + this + + instance. + + + + Gets the X coordinate of the left edge of the rectangle. + + Gets the X coordinate of the left edge of the rectangle. Same as: + getX() + . + + the X coordinate of the left edge of the rectangle. + + + Gets the X coordinate of the right edge of the rectangle. + + Gets the X coordinate of the right edge of the rectangle. Same as: + getX() + getWidth() + . + + the X coordinate of the right edge of the rectangle. + + + Gets the Y coordinate of the upper edge of the rectangle. + + Gets the Y coordinate of the upper edge of the rectangle. Same as: + getY() + getHeight() + . + + the Y coordinate of the upper edge of the rectangle. + + + Gets the Y coordinate of the lower edge of the rectangle. + + Gets the Y coordinate of the lower edge of the rectangle. Same as: + getY() + . + + the Y coordinate of the lower edge of the rectangle. + + + Gets the copy of this rectangle. + the copied rectangle. + + + Rotates PageSize clockwise. + + + Paths define shapes, trajectories, and regions of all sorts. + + Paths define shapes, trajectories, and regions of all sorts. They shall be used + to draw lines, define the shapes of filled areas, and specify boundaries for clipping + other graphics. A path shall be composed of straight and curved line segments, which + may connect to one another or may be disconnected. + + + + + A + + of subpaths forming this path. + + + + Adds the subpath to this path. + The subpath to be added to this path. + + + Adds the subpaths to this path. + + + + of subpaths to be added to this path. + + + + The current point is the trailing endpoint of the segment most recently added to the current path. + + The current point. + + + Begins a new subpath by moving the current point to coordinates (x, y). + + + Appends a straight line segment from the current point to the point (x, y). + + + Appends a cubic Bezier curve to the current path. + + Appends a cubic Bezier curve to the current path. The curve shall extend from + the current point to the point (x3, y3). + + + + Appends a cubic Bezier curve to the current path. + + Appends a cubic Bezier curve to the current path. The curve shall extend from + the current point to the point (x3, y3) with the note that the current + point represents two control points. + + + + Appends a cubic Bezier curve to the current path. + + Appends a cubic Bezier curve to the current path. The curve shall extend from + the current point to the point (x3, y3) with the note that the (x3, y3) + point represents two control points. + + + + Appends a rectangle to the current path as a complete subpath. + + + Appends a rectangle to the current path as a complete subpath. + + + Closes the current subpath. + + + Closes all subpathes contained in this path. + + + Adds additional line to each closed subpath and makes the subpath unclosed. + + Adds additional line to each closed subpath and makes the subpath unclosed. + The line connects the last and the first points of the subpaths. + + Indices of modified subpaths. + + + Path is empty if it contains no subpaths. + + + As subpath is a part of a path comprising a sequence of connected segments. + + + Creates a new SubPath instance. + + + Copy constuctor. + + + + Constructs a new subpath starting at the given point. + + + Constructs a new subpath starting at the given point. + + + Sets the start point of the subpath. + + + + Sets the start point of the subpath. + + + + + The point this subpath starts at. + + + The last point of the subpath. + + + Adds a segment to the subpath. + + Adds a segment to the subpath. + Note: each new segment shall start at the end of the previous segment. + + new segment. + + + + + + comprising all the segments + the subpath made on. + + + + Checks whether subpath is empty or not. + true if the subpath is empty, false otherwise. + + + + true if this subpath contains only one point and it is not closed, + false otherwise + + + + + true if this subpath contains only one point and it is closed, + false otherwise + + + + Returns a boolean value indicating whether the subpath must be closed or not. + + Returns a boolean value indicating whether the subpath must be closed or not. + Ignore this value if the subpath is a rectangle because in this case it is already closed + (of course if you paint the path using re operator) + + boolean value indicating whether the path must be closed or not. + + + + See + + + + + Returns a boolean indicating whether the subpath is degenerate or not. + + Returns a boolean indicating whether the subpath is degenerate or not. + A degenerate subpath is the subpath consisting of a single-point closed path or of + two or more points at the same coordinates. + + boolean value indicating whether the path is degenerate or not. + + + + + + containing points of piecewise linear approximation + for this subpath. + + + + Represents a vector (i.e. + + Represents a vector (i.e. a point in space). This class is completely + unrelated to the + + class in the standard JRE. +

    + For many PDF related operations, the z coordinate is specified as 1 + This is to support the coordinate transformation calculations. If it + helps, just think of all PDF drawing operations as occurring in a single plane + with z=1. +
    +
    + + index of the X coordinate + + + index of the Y coordinate + + + index of the Z coordinate + + + the values inside the vector + + + Creates a new Vector + the X coordinate + the Y coordinate + the Z coordinate + + + Gets the value from a coordinate of the vector + the index of the value to get (I1, I2 or I3) + a coordinate value + + + Computes the cross product of this vector and the specified matrix + the matrix to cross this vector with + the result of the cross product + + + Computes the difference between this vector and the specified vector + the vector to subtract from this one + the results of the subtraction + + + Computes the cross product of this vector and the specified vector + the vector to cross this vector with + the cross product + + + Normalizes the vector (i.e. + Normalizes the vector (i.e. returns the unit vector in the same orientation as this vector) + the unit vector + + + Multiplies the vector by a scalar + the scalar to multiply by + the result of the scalar multiplication + + + Computes the dot product of this vector with the specified vector + the vector to dot product this vector with + the dot product + + + + Computes the length of this vector +
    + Note: If you are working with raw vectors from PDF, be careful - + the Z axis will generally be set to 1. +
    + + Computes the length of this vector +
    + Note: If you are working with raw vectors from PDF, be careful - + the Z axis will generally be set to 1. If you want to compute the + length of a vector, subtract it from the origin first (this will set + the Z axis to 0). +
    + For example: + aVector.subtract(originVector).length(); +
    + the length of this vector +
    + + Computes the length squared of this vector. + + Computes the length squared of this vector. +

    + The square of the length is less expensive to compute, and is often + useful without taking the square root. +

    + Note: See the important note under + + + the square of the length of the vector + + + + + +

    Calculates the hashcode using the values. +
    + + + + + + IMPORTANT: USE THIS METHOD CAREFULLY. + This method serves as replacement for the java method MessageDigest#digest(byte[] buf, int offset, int len). + However for now, we simply omit len parameter, because it doesn't affect anything for all current usages + (there are two of them at the moment of the method addition which are in StandardHandlerUsingAes256 class). + This may be not true for future possible usages, so be aware. + + + + + Gets a Counter instance for a specific class. + + + This method gets triggered if a document is read. + the length of the document that was read + + + This method gets triggered if a document is written. + the length of the document that was written + + + + The singleton instance. + + + The current counter implementation. + + + The empty constructor. + + + Returns the singleton instance of the factory. + + + Returns a counter factory. + + + Getter for the counter. + + + Setter for the counter. + + + Default implementation of the Counter interface that essentially doesn't do anything. + + + Implementation of the Counter interface that doesn't do anything. + + + + A + + implementation that outputs information about read and written documents to + + + + + + The name of the class for which the Counter was created + (or iText if no name is available) + + + + This is a general class for alphabet numbering. + + This is a general class for alphabet numbering. + You can specify an alphabet and convert an integer into the corresponding + alphabet number representation. + E.g.: if the alphabet is English letters 'a' to 'z', then + 1 is represented as "a", ..., 26 is represented as "z", + 27 is represented as "aa" and so on. + + + + + Translates a positive integer (not equal to zero) + into an alphabet number using the letters from the specified alphabet. + + the number + the array containing all possible letters from the alphabet + a translated number representation + + + + This class is responsible for converting integer numbers to their + English alphabet letter representations. + + + + Converts the given number to its English alphabet lowercase string representation. + + Converts the given number to its English alphabet lowercase string representation. + E.g. 1 will be converted to "a", 2 to "b", ..., 27 to "aa", and so on. + + the number to be converted + + + Converts the given number to its English alphabet lowercase string representation. + + Converts the given number to its English alphabet lowercase string representation. + E.g. 1 will be converted to "A", 2 to "B", ..., 27 to "AA", and so on. + + the number to be converted + + + Converts the given number to its English alphabet string representation. + + Converts the given number to its English alphabet string representation. + E.g. for upperCase set to false, + 1 will be converted to "a", 2 to "b", ..., 27 to "aa", and so on. + + the number to be converted + whether to use uppercase or lowercase alphabet + + + + This class is responsible for converting integer numbers to their + Greek alphabet letter representations. + + + This class is responsible for converting integer numbers to their + Greek alphabet letter representations. + We are aware of the fact that the original Greek numbering is different. + See http://www.cogsci.indiana.edu/farg/harry/lan/grknum.htm#ancient + but this isn't implemented yet; the main reason being the fact that we + need a font that has the obsolete Greek characters qoppa and sampi. + So we use standard 24 letter Greek alphabet + + + + Converts the given number to its Greek alphabet lowercase string representation. + + Converts the given number to its Greek alphabet lowercase string representation. + E.g. 1 will be converted to "?", 2 to "?", and so on. + + the number to be converted + + + Converts the given number to its Greek alphabet lowercase string representation. + + Converts the given number to its Greek alphabet lowercase string representation. + E.g. 1 will be converted to "A", 2 to "B", and so on. + + the number to be converted + + + Converts the given number to its Greek alphabet string representation. + + Converts the given number to its Greek alphabet string representation. + E.g. for upperCase set to false, + 1 will be converted to "?", 2 to "?", and so on. + + the number to be converted + whether to use uppercase or lowercase alphabet + + + Converts a given greek unicode character code into the code of the corresponding char Symbol font. + + original unicode char + the corresponding symbol code in Symbol standard font + + + This class can produce String combinations representing a roman number. + + This class can produce String combinations representing a roman number. + The first roman numbers are: I, II, III, IV, V, VI, VII, VIII, IX, X + See http://en.wikipedia.org/wiki/Roman_numerals + + + + Array with Roman digits. + + + Returns a lower case roman representation of an integer. + a number to be converted to roman notation. + + + Returns an upper case roman representation of an integer. + a number to be converted to roman notation. + + + Returns a roman representation of an integer. + a number to be converted to roman notation. + + true for upper case representation, + false for lower case one. + + + + Returns a roman representation of an integer. + the original number + the roman number representation (lower case) + + + Helper class for Roman Digits + + + part of a roman number + + + value of the roman digit + + + can the digit be used as a prefix + + + Constructs a roman digit + the roman digit + the value + can it be used as a prefix + + + A wrapper for action dictionaries (ISO 32000-1 section 12.6). + + A wrapper for action dictionaries (ISO 32000-1 section 12.6). + An action dictionary defines the characteristics and behaviour of an action. + + + + A possible submit value + + + A possible submit value + + + A possible submit value + + + A possible submit value + + + A possible submit value + + + A possible submit value + + + A possible submit value + + + A possible submit value + + + A possible submit value + + + A possible submit value + + + A possible submit value + + + A possible submit value + + + A possible submit value + + + A possible submit value + + + Constructs an empty action that can be further modified. + + + + Constructs a + + instance with a given dictionary. It can be used for handy + property reading in reading mode or modifying in stamping mode. + + the dictionary to construct the wrapper around + + + Creates a GoTo action (section 12.6.4.2 of ISO 32000-1) via a given destination. + the desired destination of the action + created action + + + + Creates a GoTo action (section 12.6.4.2 of ISO 32000-1) via a given + + name. + + + + + name + + created action + + + Creates a GoToR action, or remote action (section 12.6.4.3 of ISO 32000-1). + the file in which the destination shall be located + the destination in the remote document to jump to + a flag specifying whether to open the destination document in a new window + created action + + + Creates a GoToR action, or remote action (section 12.6.4.3 of ISO 32000-1). + the file in which the destination shall be located + the destination in the remote document to jump to + created action + + + Creates a GoToR action, or remote action (section 12.6.4.3 of ISO 32000-1). + the remote destination file to jump to + the remote destination document page to jump to + created action + + + Creates a GoToR action, or remote action (section 12.6.4.3 of ISO 32000-1). + the remote destination file to jump to + the remote destination document page to jump to + a flag specifying whether to open the destination document in a new window + created action + + + Creates a GoToR action, or remote action (section 12.6.4.3 of ISO 32000-1). + the remote destination file to jump to + the string destination in the remote document to jump to + a flag specifying whether to open the destination document in a new window + created action + + + Creates a GoToR action, or remote action (section 12.6.4.3 of ISO 32000-1). + the remote destination file to jump to + the string destination in the remote document to jump to + created action + + + Creates a GoToE action, or embedded file action (section 12.6.4.4 of ISO 32000-1). + the destination in the target to jump to + + if true, the destination document should be opened in a new window; + if false, the destination document should replace the current document in the same window + + + A target dictionary specifying path information to the target document. + Each target dictionary specifies one element in the full path to the target and + may have nested target dictionaries specifying additional elements + + created action + + + Creates a GoToE action, or embedded file action (section 12.6.4.4 of ISO 32000-1). + The root document of the target relative to the root document of the source + the destination in the target to jump to + + if true, the destination document should be opened in a new window; + if false, the destination document should replace the current document in the same window + + + A target dictionary specifying path information to the target document. + Each target dictionary specifies one element in the full path to the target and + may have nested target dictionaries specifying additional elements + + created action + + + Creates a Launch action (section 12.6.4.5 of ISO 32000-1). + the application that shall be launched or the document that shall beopened or printed + + a flag specifying whether to open the destination document in a new window + created action + + + Creates a Launch action (section 12.6.4.5 of ISO 32000-1). + the application that shall be launched or the document that shall beopened or printed + + created action + + + Creates a Launch action (section 12.6.4.5 of ISO 32000-1). + the application that shall be launched or the document that shall beopened or printed + + A dictionary containing Windows-specific launch parameters + a flag specifying whether to open the destination document in a new window + created action + + + Creates a Thread action (section 12.6.4.6 of ISO 32000-1). + + Creates a Thread action (section 12.6.4.6 of ISO 32000-1). + A thread action jumps to a specified bead on an article thread (see 12.4.3, “Articles”), + in either the current document or a different one. Table 205 shows the action dictionary + entries specific to this type of action. + + the file containing the thread. If this entry is absent, the thread is in the current file + + the destination thread + the bead in the destination thread + created action + + + Creates a Thread action (section 12.6.4.6 of ISO 32000-1). + + Creates a Thread action (section 12.6.4.6 of ISO 32000-1). + A thread action jumps to a specified bead on an article thread (see 12.4.3, “Articles”), + in either the current document or a different one. Table 205 shows the action dictionary + entries specific to this type of action. + + the file containing the thread. If this entry is absent, the thread is in the current file + + created action + + + Creates a URI action (section 12.6.4.7 of ISO 32000-1). + the uniform resource identifier to resolve + created action + + + Creates a URI action (section 12.6.4.7 of ISO 32000-1). + the uniform resource identifier to resolve + a flag specifying whether to track the mouse position when the URI is resolved + created action + + + Creates a Sound action (section 12.6.4.8 of ISO 32000-1). + a sound object defining the sound that shall be played (see section 13.3 of ISO 32000-1) + + created action + + + Creates a Sound action (section 12.6.4.8 of ISO 32000-1). + a sound object defining the sound that shall be played (see section 13.3 of ISO 32000-1) + + the volume at which to play the sound, in the range -1.0 to 1.0. Default value: 1.0 + + a flag specifying whether to play the sound synchronously or asynchronously. + If this flag is true, the conforming reader retains control, allowing no further user + interaction other than canceling the sound, until the sound has been completely played. + Default value: false + + + a flag specifying whether to repeat the sound indefinitely + If this entry is present, the Synchronous entry shall be ignored. Default value: false + + a flag specifying whether to mix this sound with any other sound already playing + created action + + + Creates a Movie annotation (section 12.6.4.9 of ISO 32000-1). + a movie annotation identifying the movie that shall be played + the title of a movie annotation identifying the movie that shall be played + + the operation that shall be performed on the movie. Shall be one of the following: + + , + + , + + , + + + created annotation + + + Creates a Hide action (section 12.6.4.10 of ISO 32000-1). + the annotation to be hidden or shown + a flag indicating whether to hide the annotation (true) or show it (false) + + created action + + + Creates a Hide action (section 12.6.4.10 of ISO 32000-1). + the annotations to be hidden or shown + a flag indicating whether to hide the annotation (true) or show it (false) + + created action + + + Creates a Hide action (section 12.6.4.10 of ISO 32000-1). + + a text string giving the fully qualified field name of an interactive form field whose + associated widget annotation or annotations are to be affected + + a flag indicating whether to hide the annotation (true) or show it (false) + + created action + + + Creates a Hide action (section 12.6.4.10 of ISO 32000-1). + + a text string array giving the fully qualified field names of interactive form fields whose + associated widget annotation or annotations are to be affected + + a flag indicating whether to hide the annotation (true) or show it (false) + + created action + + + Creates a Named action (section 12.6.4.11 of ISO 32000-1). + + the name of the action that shall be performed. Shall be one of the following: + + , + + , + + , + + + created action + + + Creates a Set-OCG-State action (section 12.6.4.12 of ISO 32000-1). + + a list of + + state descriptions + + created action + + + Creates a Set-OCG-State action (section 12.6.4.12 of ISO 32000-1). + + states a list of + + state descriptions + + + If true, indicates that radio-button state relationships between optional content groups + should be preserved when the states are applied + + created action + + + Creates a Rendition action (section 12.6.4.13 of ISO 32000-1). + the name of the media clip, for use in the user interface. + a full file specification or form XObject that specifies the actual media data + an ASCII string identifying the type of data + a screen annotation + created action + + + Creates a JavaScript action (section 12.6.4.16 of ISO 32000-1). + a text string containing the JavaScript script to be executed. + created action + + + Creates a Submit-Form Action (section 12.7.5.2 of ISO 32000-1). + a uniform resource locator, as described in 7.11.5, "URL Specifications" + + an array identifying which fields to include in the submission or which to exclude, + depending on the setting of the Include/Exclude flag in the Flags entry. + This is an optional parameter and can be null + + + a set of flags specifying various characteristics of the action (see Table 237 of ISO 32000-1). + Default value to be passed: 0. + + created action + + + Creates a Reset-Form Action (section 12.7.5.3 of ISO 32000-1). + + an array identifying which fields to reset or which to exclude from resetting, + depending on the setting of the Include/Exclude flag in the Flags entry (see Table 239 of ISO 32000-1). + + + a set of flags specifying various characteristics of the action (see Table 239 of ISO 32000-1). + Default value to be passed: 0. + + created action + + + + Adds a chained action. + the next action or sequence of actions that shall be performed after the current action + + + + + Inserts the value into the underlying object of this + + and associates it with the specified key. + If the key is already present in this + + , this method will override the old value with the specified one. + + key to insert or to override + the value to associate with the specified key + + this + + instance + + + + + To manually flush a + PdfObject + behind this wrapper, you have to ensure + that this object is added to the document, i.e. it has an indirect reference. + Basically this means that before flushing you need to explicitly call + + . + For example: wrapperInstance.makeIndirect(document).flush(); + Note that not every wrapper require this, only those that have such warning in documentation. + + + + + + + This is a helper class for optional content states use in Set-OCG-State actions. + + This is a helper class for optional content states use in Set-OCG-State actions. + See + + . + + + + + Can be: + + , + + , + + + + + Optional content group dictionaries + + + Constructs an optional content state object. + + a + + describing the state. Shall be one of the following: + + , + + , + + + a list of the OCG dictionaries + + + Gets the state the optional content groups should be switched to + + the state, one of the following: + + , + + , + + + + + Gets a list of optional content groups that shall have the state changed + the list of optional content groups + + + + Gets a list of + + that is describing this particular optional content group states. + + + a list of + + for construction of a + + + + + This class is a wrapper of media clip data dictionary that defines the data for a media object that can be played. + + + + + Constructs a new + + wrapper using an existing dictionary. + + the dictionary to construct the wrapper from + + + + Constructs a new + + wrapper around a newly created dictionary. + + the name of the file to create a media clip for + a file specification that specifies the actual media data + an ASCII string identifying the type of data + + + + To manually flush a + PdfObject + behind this wrapper, you have to ensure + that this object is added to the document, i.e. it has an indirect reference. + Basically this means that before flushing you need to explicitly call + + . + For example: wrapperInstance.makeIndirect(document).flush(); + Note that not every wrapper require this, only those that have such warning in documentation. + + + + + + + This a wrapper around a rendition dictionary. + This a wrapper around a rendition dictionary. See ISO 32000-1 sections 13.2.3.2, 13.2.3.3. + + + + Creates a new wrapper around an existing + + + a rendition object to create a wrapper for + + + Creates a new wrapper around a newly created media rendition dictionary object. + a text string specifying the name of the file to display + a file specification that specifies the actual media data + an ASCII string identifying the type of data + + + + To manually flush a + PdfObject + behind this wrapper, you have to ensure + that this object is added to the document, i.e. it has an indirect reference. + Basically this means that before flushing you need to explicitly call + + . + For example: wrapperInstance.makeIndirect(document).flush(); + Note that not every wrapper require this, only those that have such warning in documentation. + + + + + + + + A target dictionary locates the target in relation to the source, + in much the same way that a relative path describes the physical + relationship between two files in a file system. + + + A target dictionary locates the target in relation to the source, + in much the same way that a relative path describes the physical + relationship between two files in a file system. Target dictionaries may be + nested recursively to specify one or more intermediate targets before reaching the final one. + + + + + Creates a new + + object by the underlying dictionary. + + the underlying dictionary object + + + + Creates a new + + object given its type. The type must be either + + , or + + . If it is + + , additional entries must be specified + according to the spec. + + the relationship between the current document and the target + + + + Creates a new + + object. + + the relationship between the current document and the target + the name of the file in the EmbeddedFiles name tree + + if the value is an integer, it specifies the page number (zero-based) in the current + document containing the file attachment annotation. If the value is a string, + it specifies a named destination in the current document that provides the page + number of the file attachment annotation + + + If the value is an integer, it specifies the index (zero-based) of the annotation in the + Annots array of the page specified by P. If the value is a text string, + it specifies the value of NM in the annotation dictionary + + + A target dictionary specifying additional path information to the target document. + If this entry is absent, the current document is the target file containing the destination + + + + Creates a new target object pointing to the parent of the current document. + + created + + + + + Creates a new target object pointing to a file in the EmbeddedFiles name tree. + the name of the file in the EmbeddedFiles name tree + created object + + + Creates a new target object pointing to a file attachment annotation. + + a named destination in the current document that + provides the page number of the file attachment annotation + + + a unique annotation identifier ( + + entry) of the annotation + + created object + + + Creates a new target object pointing to a file attachment annotation. + the number of the page in the current document, one-based + the index of the annotation in the Annots entry of the page, zero-based + created object + + + + Sets the name of the file in the EmbeddedFiles name tree for the child target located + in the EmbeddedFiles. + + the name of the file + this object wrapper + + + + Gets name of the file in the EmbeddedFiles name tree for the child target located + in the EmbeddedFiles. + + the name of the child file for this target + + + + Sets the page number in the current document containing the file attachment annotation for the + child target associates with a file attachment annotation. + + + the page number (one-based) in the current document containing + the file attachment annotation + + this object wrapper + + + + Sets a named destination in the current document that provides the page number of the file + attachment annotation for the child target associated with a file attachment annotation. + + + a named destination in the current document that provides the page + number of the file attachment annotation + + this object wrapper + + + Get the contents of the /P entry of this target object. + + Get the contents of the /P entry of this target object. + If the value is an integer, it specifies the page number (zero-based) + in the current document containing the file attachment annotation. + If the value is a string, it specifies a named destination in the current + document that provides the page number of the file attachment annotation. + + the /P entry of target object + + + + Sets the index of the annotation in Annots array of the page specified by /P entry + for the child target associated with a file attachment annotation. + + the index (zero-based) of the annotation in the Annots array + this object wrapper + + + + Sets the text value, which uniquely identifies an annotation (/NM entry) in an annotation dictionary + for the child target associated with a file attachment annotation. + + specifies the value of NM in the annotation dictionary of the target annotation + + this object wrapper + + + Gets the object in the /A entry of the underlying object. + + Gets the object in the /A entry of the underlying object. If the value is an integer, + it specifies the index (zero-based) of the annotation in the Annots array of the page specified by P. + If the value is a text string, it specifies the value of NM in the annotation dictionary. + + the /A entry in the target object + + + Sets a target dictionary specifying additional path information to the target document. + + Sets a target dictionary specifying additional path information to the target document. + If this entry is absent, the current document is the target file containing the destination. + + the additional path target dictionary + this object wrapper + + + Get a target dictionary specifying additional path information to the target document. + + Get a target dictionary specifying additional path information to the target document. + If the current target object is the final node in the target path, null is returned. + + a target dictionary specifying additional path information to the target document + + + + This is a convenient method to put key-value pairs to the underlying + + . + + + the key, a + + instance + + the value + this object wrapper + + + + + + This class is a wrapper around a Windows launch parameter dictionary. + + + Creates a new wrapper around an existing Windows launch parameter dictionary. + the dictionary to create a wrapper for + + + Creates a new wrapper around a newly created Windows launch parameter dictionary. + + the file name of the application that shall be launched or the document that shall be opened or printed, + in standard Windows pathname format. If the name string includes a backslash character (\), + the backslash shall itself be preceded by a backslash. + + + + Creates a new wrapper around a newly created Windows launch parameter dictionary. + + the file name of the application that shall be launched or the document that shall be opened or printed, + in standard Windows pathname format. If the name string includes a backslash character (\), + the backslash shall itself be preceded by a backslash + + a bye string specifying the default directory in standard DOS syntax + + an ASCII string specifying the operation to perform on the file. Shall be one of the following: + "open", "print" + + + a parameter string that shall be passed to the application. + This entry shall be omitted if a document is abound to be opened + + + + + + + This is a super class for the annotation dictionary wrappers. + + This is a super class for the annotation dictionary wrappers. Derived classes represent + different standard types of annotations. See ISO-320001 12.5.6, “Annotation Types.” + + + + Annotation flag. + + Annotation flag. + See also + + and ISO-320001, table 165. + + + + Annotation flag. + + Annotation flag. + See also + + and ISO-320001, table 165. + + + + Annotation flag. + + Annotation flag. + See also + + and ISO-320001, table 165. + + + + Annotation flag. + + Annotation flag. + See also + + and ISO-320001, table 165. + + + + Annotation flag. + + Annotation flag. + See also + + and ISO-320001, table 165. + + + + Annotation flag. + + Annotation flag. + See also + + and ISO-320001, table 165. + + + + Annotation flag. + + Annotation flag. + See also + + and ISO-320001, table 165. + + + + Annotation flag. + + Annotation flag. + See also + + and ISO-320001, table 165. + + + + Annotation flag. + + Annotation flag. + See also + + and ISO-320001, table 165. + + + + Annotation flag. + + Annotation flag. + See also + + and ISO-320001, table 165. + + + + Widget annotation highlighting mode. + + Widget annotation highlighting mode. See ISO-320001, Table 188 (H key). + Also see + + . + + + + Widget annotation highlighting mode. + + Widget annotation highlighting mode. See ISO-320001, Table 188 (H key). + Also see + + . + + + + Widget annotation highlighting mode. + + Widget annotation highlighting mode. See ISO-320001, Table 188 (H key). + Also see + + . + + + + Widget annotation highlighting mode. + + Widget annotation highlighting mode. See ISO-320001, Table 188 (H key). + Also see + + . + + + + Widget annotation highlighting mode. + + Widget annotation highlighting mode. See ISO-320001, Table 188 (H key). + Also see + + . + + + + Annotation border style. + + Annotation border style. See ISO-320001, Table 166 (S key). + Also see + + + + + Annotation border style. + + Annotation border style. See ISO-320001, Table 166 (S key). + Also see + + + + + Annotation border style. + + Annotation border style. See ISO-320001, Table 166 (S key). + Also see + + + + + Annotation border style. + + Annotation border style. See ISO-320001, Table 166 (S key). + Also see + + + + + Annotation border style. + + Annotation border style. See ISO-320001, Table 166 (S key). + Also see + + + + + Annotation state. + + Annotation state. See ISO-320001 12.5.6.3 "Annotation States" and Table 171 in particular. + Also see + + . + + + + Annotation state. + + Annotation state. See ISO-320001 12.5.6.3 "Annotation States" and Table 171 in particular. + Also see + + . + + + + Annotation state. + + Annotation state. See ISO-320001 12.5.6.3 "Annotation States" and Table 171 in particular. + Also see + + . + + + + Annotation state. + + Annotation state. See ISO-320001 12.5.6.3 "Annotation States" and Table 171 in particular. + Also see + + . + + + + Annotation state. + + Annotation state. See ISO-320001 12.5.6.3 "Annotation States" and Table 171 in particular. + Also see + + . + + + + Annotation state. + + Annotation state. See ISO-320001 12.5.6.3 "Annotation States" and Table 171 in particular. + Also see + + . + + + + Annotation state. + + Annotation state. See ISO-320001 12.5.6.3 "Annotation States" and Table 171 in particular. + Also see + + . + + + + Annotation state model. + + Annotation state model. See ISO-320001, Table 172 (StateModel key). + Also see + + . + + + + Annotation state model. + + Annotation state model. See ISO-320001, Table 172 (StateModel key). + Also see + + . + + + + + Factory method that creates the type specific + + from the given + + that represents annotation object. This method is useful for property reading in reading mode or + modifying in stamping mode. See derived classes of this class to see possible specific annotation types + created. + + + a + + that represents annotation in the document. + + + created + + . + + + + + Factory method that creates the type specific + + from the given + + that represents annotation object. This method is useful for property reading in reading mode or + modifying in stamping mode. + + + a + + that represents annotation in the document. + + + parent annotation of the + + to be created. This parameter is + only needed if passed + + represents a pop-up annotation in the document. + + + created + + . + + + + + Gets a + + which value is a subtype of this annotation. + See ISO-320001 12.5.6, “Annotation Types” for the reference to the possible types. + + subtype of this annotation. + + + Sets the layer this annotation belongs to. + the layer this annotation belongs to + + + + Sets a + + to this annotation which will be performed when the annotation is activated. + + + + + to set to this annotation. + + + this + + instance. + + + + + Sets an additional + + to this annotation which will be performed in response to + the specific trigger event defined by + + . See ISO-320001 12.6.3, "Trigger Events". + + + a + + that denotes a type of the additional action to set. + + + + + to set as additional to this annotation. + + + this + + instance. + + + + + Gets the text that shall be displayed for the annotation or, if this type of annotation does not display text, + an alternate description of the annotation’s contents in human-readable form. + + annotation text content. + + + + Sets the text that shall be displayed for the annotation or, if this type of annotation does not display text, + an alternate description of the annotation’s contents in human-readable form. + + + a + + containing text content to be set to the annotation. + + + this + + instance. + + + + + Sets the text that shall be displayed for the annotation or, if this type of annotation does not display text, + an alternate description of the annotation’s contents in human-readable form. + + + a java + + containing text content to be set to the annotation. + + + this + + instance. + + + + + Gets a + + that represents a page of the document on which annotation is placed, + i.e. which has this annotation in it's /Annots array. + + + + + that is a page pdf object or null if annotation is not added to the page yet. + + + + + Gets a + + on which annotation is placed. + + + + + on which annotation is placed or null if annotation is not placed yet. + + + + Method that modifies annotation page property, which defines to which page annotation belongs. + + Method that modifies annotation page property, which defines to which page annotation belongs. + Keep in mind that this doesn't actually add an annotation to the page, + it should be done via + + . + Also you don't need to set this property manually, this is done automatically on addition to the page. + + + the + + to which annotation will be added. + + + this + + instance. + + + + + Gets the annotation name, a text string uniquely identifying it among all the + annotations on its page. + + + a + + with annotation name as it's value or null if name + is not specified. + + + + + Sets the annotation name, a text string uniquely identifying it among all the + annotations on its page. + + + a + + to be set as annotation name. + + + this + + instance. + + + + The date and time when the annotation was most recently modified. + + The date and time when the annotation was most recently modified. + This is an optional property of the annotation. + + + a + + with the modification date as it's value or null if date is not specified. + + + + The date and time when the annotation was most recently modified. + + a + + with date. The format should be a date string as described + in ISO-320001 7.9.4, “Dates”. + + + this + + instance. + + + + A set of flags specifying various characteristics of the annotation (see ISO-320001 12.5.3, “Annotation Flags”). + + + A set of flags specifying various characteristics of the annotation (see ISO-320001 12.5.3, “Annotation Flags”). + For specific annotation flag constants see + + . + Default value: 0. + + an integer interpreted as one-bit flags specifying various characteristics of the annotation. + + + Sets a set of flags specifying various characteristics of the annotation (see ISO-320001 12.5.3, “Annotation Flags”). + + + Sets a set of flags specifying various characteristics of the annotation (see ISO-320001 12.5.3, “Annotation Flags”). + On the contrary from + + , this method sets a complete set of enabled and disabled flags at once. + If not set specifically the default value is 0. + + an integer interpreted as set of one-bit flags specifying various characteristics of the annotation. + + + this + + instance. + + + + Sets a flag that specifies a characteristic of the annotation to enabled state (see ISO-320001 12.5.3, “Annotation Flags”). + + + Sets a flag that specifies a characteristic of the annotation to enabled state (see ISO-320001 12.5.3, “Annotation Flags”). + On the contrary from + + , this method sets only specified flags to enabled state, + but doesn't disable other flags. + Possible flags: +
      +
    • + + - If set, do not display the annotation if it does not belong to one of the + standard annotation types and no annotation handler is available. If clear, display such unknown annotation + using an appearance stream specified by its appearance dictionary, if any. +
    • +
    • + + - If set, do not display or print the annotation or allow it to interact with + the user, regardless of its annotation type or whether an annotation handler is available. +
    • +
    • + + - If set, print the annotation when the page is printed. If clear, never print + the annotation, regardless of whether it is displayed on the screen. +
    • +
    • + + - If set, do not scale the annotation’s appearance to match the magnification of + the page. The location of the annotation on the page (defined by the upper-left corner of its annotation + rectangle) shall remain fixed, regardless of the page magnification.} +
    • +
    • + + - If set, do not rotate the annotation’s appearance to match the rotation + of the page. The upper-left corner of the annotation rectangle shall remain in a fixed location on the page, + regardless of the page rotation. +
    • +
    • + + - If set, do not display the annotation on the screen or allow it to interact + with the user. The annotation may be printed (depending on the setting of the Print flag) but should be considered + hidden for purposes of on-screen display and user interaction. +
    • +
    • + + - If set, do not allow the annotation to interact with the user. The annotation + may be displayed or printed (depending on the settings of the NoView and Print flags) but should not respond to mouse + clicks or change its appearance in response to mouse motions. +
    • +
    • + + - If set, do not allow the annotation to be deleted or its properties + (including position and size) to be modified by the user. However, this flag does not restrict changes to + the annotation’s contents, such as the value of a form field. +
    • +
    • + + - If set, invert the interpretation of the NoView flag for certain events. +
    • +
    • + + - If set, do not allow the contents of the annotation to be modified + by the user. This flag does not restrict deletion of the annotation or changes to other annotation properties, + such as position and size. +
    • +
    +
    + - an integer interpreted as set of one-bit flags which will be enabled for this annotation. + + + this + + instance. + +
    + + Resets a flag that specifies a characteristic of the annotation to disabled state (see ISO-320001 12.5.3, “Annotation Flags”). + + an integer interpreted as set of one-bit flags which will be reset to disabled state. + + this + + instance. + + + + + Checks if the certain flag that specifies a characteristic of the annotation + is in enabled state (see ISO-320001 12.5.3, “Annotation Flags”). + + + Checks if the certain flag that specifies a characteristic of the annotation + is in enabled state (see ISO-320001 12.5.3, “Annotation Flags”). + This method allows only one flag to be checked at once, use constants listed in + + . + + + an integer interpreted as set of one-bit flags. Only one bit must be set in this integer, otherwise + exception is thrown. + + true if the given flag is in enabled state. + + + + An appearance dictionary specifying how the annotation shall be presented visually on the page during its + interactions with the user (see ISO-320001 12.5.5, “Appearance Streams”). + + + An appearance dictionary specifying how the annotation shall be presented visually on the page during its + interactions with the user (see ISO-320001 12.5.5, “Appearance Streams”). An appearance dictionary is a dictionary + containing one or several appearance streams or subdictionaries. + + + an appearance + + or null if it is not specified. + + + + Specific appearance object corresponding to the specific appearance type. + + Specific appearance object corresponding to the specific appearance type. This object might be either an appearance + stream or an appearance subdictionary. In the latter case, the subdictionary defines multiple appearance streams + corresponding to different appearance states of the annotation. See ISO-320001 12.5.5, “Appearance Streams”. + + + a + + specifying appearance type. Possible types are + Normal + , + Rollover + and + Down + . + + + null if their is no such appearance type or an appearance object which might be either + an appearance stream or an appearance subdictionary. + + + + The normal appearance is used when the annotation is not interacting with the user. + + The normal appearance is used when the annotation is not interacting with the user. + This appearance is also used for printing the annotation. + See also + + . + + an appearance object which might be either an appearance stream or an appearance subdictionary. + + + + The rollover appearance is used when the user moves the cursor into the annotation’s active area + without pressing the mouse button. + + + The rollover appearance is used when the user moves the cursor into the annotation’s active area + without pressing the mouse button. If not specified normal appearance is used. + See also + + . + + + null if rollover appearance is not specified or an appearance object which might be either + an appearance stream or an appearance subdictionary. + + + + The down appearance is used when the mouse button is pressed or held down within the annotation’s active area. + + + The down appearance is used when the mouse button is pressed or held down within the annotation’s active area. + If not specified normal appearance is used. + See also + + . + + + null if down appearance is not specified or an appearance object which might be either + an appearance stream or an appearance subdictionary. + + + + Sets a specific type of the appearance. + + Sets a specific type of the appearance. See + + and + + for more info. + + + a + + specifying appearance type. Possible types are + Normal + , + Rollover + and + Down + . + + an appearance object which might be either an appearance stream or an appearance subdictionary. + + + this + + instance. + + + + Sets normal appearance. + + Sets normal appearance. See + + and + + for more info. + + an appearance object which might be either an appearance stream or an appearance subdictionary. + + + this + + instance. + + + + Sets rollover appearance. + + Sets rollover appearance. See + + and + + for more info. + + an appearance object which might be either an appearance stream or an appearance subdictionary. + + + this + + instance. + + + + Sets down appearance. + + Sets down appearance. See + + and + + for more info. + + an appearance object which might be either an appearance stream or an appearance subdictionary. + + + this + + instance. + + + + + Sets a specific type of the appearance using + + wrapper. + This method is used to set only an appearance subdictionary. See + + and + + for more info. + + + a + + specifying appearance type. Possible types are + Normal + , + Rollover + and + Down + . + + + an appearance subdictionary wrapped in + + . + + + this + + instance. + + + + + Sets normal appearance using + + wrapper. This method is used to set only + appearance subdictionary. See + + and + + for more info. + + + an appearance subdictionary wrapped in + + . + + + this + + instance. + + + + + Sets rollover appearance using + + wrapper. This method is used to set only + appearance subdictionary. See + + and + + for more info. + + + an appearance subdictionary wrapped in + + . + + + this + + instance. + + + + + Sets down appearance using + + wrapper. This method is used to set only + appearance subdictionary. See + + and + + for more info. + + + an appearance subdictionary wrapped in + + . + + + this + + instance. + + + + + The annotation’s appearance state, which selects the applicable appearance stream + from an appearance subdictionary if there is such. + + + The annotation’s appearance state, which selects the applicable appearance stream + from an appearance subdictionary if there is such. See + + for more info. + + + a + + which defines selected appearance state. + + + + + Sets the annotation’s appearance state, which selects the applicable appearance stream + from an appearance subdictionary. + + + Sets the annotation’s appearance state, which selects the applicable appearance stream + from an appearance subdictionary. See + + for more info. + + + a + + which defines appearance state to be selected. + + + this + + instance. + + + + + Sets the characteristics of the annotation’s border. + + an + + specifying the characteristics of the annotation’s border. + See + + for more detailes. + + + this + + instance. + + + + + An array of numbers in the range 0.0 to 1.0, representing a colour used for the following purposes: +
      +
    • The background of the annotation’s icon when closed
    • +
    • The title bar of the annotation’s pop-up window
    • +
    • The border of a link annotation
    • +
    + The number of array elements determines the colour space in which the colour shall be defined: +
      +
    • 0 - No colour; transparent
    • +
    • 1 - DeviceGray
    • +
    • 3 - DeviceRGB
    • +
    • 4 - DeviceCMYK
    • +
    +
    + An array of numbers in the range 0.0 to 1.0, representing an annotation colour. +
    + + Sets an annotation color. + + Sets an annotation color. For more details on annotation color purposes and the format + of the passing + + see + + . + + an array of numbers in the range 0.0 to 1.0, specifying color. + + this + + instance. + + + + Sets an annotation color. + + Sets an annotation color. For more details on annotation color purposes and the format + of the passing array see + + . + + an array of numbers in the range 0.0 to 1.0, specifying color. + + this + + instance. + + + + Sets an annotation color. + + Sets an annotation color. For more details on annotation color purposes + see + + . + + + + + object of the either + + , + + or + + type. + + + this + + instance. + + + + + The integer key of the annotation’s entry in the structural parent tree + (see ISO-320001 14.7.4.4, “Finding Structure Elements from Content Items”). + + integer key in structural parent tree or -1 if annotation is not tagged. + + + + Sets he integer key of the annotation’s entry in the structural parent tree + (see ISO-320001 14.7.4.4, “Finding Structure Elements from Content Items”). + + + Sets he integer key of the annotation’s entry in the structural parent tree + (see ISO-320001 14.7.4.4, “Finding Structure Elements from Content Items”). + Note: Normally, there is no need to take care of this manually, struct parent index is set automatically + if annotation is added to the tagged document's page. + + + integer which is to be the key of the annotation's entry + in structural parent tree. + + + this + + instance. + + + + A flag specifying whether the annotation shall initially be displayed open. + + A flag specifying whether the annotation shall initially be displayed open. + This flag has affect to not all kinds of annotations. + + true if annotation is initially open, false - if closed. + + + Sets a flag specifying whether the annotation shall initially be displayed open. + + Sets a flag specifying whether the annotation shall initially be displayed open. + This flag has affect to not all kinds of annotations. + + true if annotation shall initially be open, false - if closed. + + this + + instance. + + + + + + + Sets border style dictionary that has more settings than the array specified for the Border entry ( + + ). + See ISO-320001, Table 166 and + + for more info. + + + a border style dictionary specifying the line width and dash pattern that shall be used + in drawing the annotation’s border. + + + this + + instance. + + + + Setter for the annotation's preset border style. + + Setter for the annotation's preset border style. Possible values are +
      +
    • + + - A solid rectangle surrounding the annotation.
    • +
    • + + - A dashed rectangle surrounding the annotation.
    • +
    • + + - A simulated embossed rectangle that appears to be raised above the surface of the page.
    • +
    • + + - A simulated engraved rectangle that appears to be recessed below the surface of the page.
    • +
    • + + - A single line along the bottom of the annotation rectangle.
    • +
    + See also ISO-320001, Table 166. +
    + The new value for the annotation's border style. + The annotation which this method was called on. + +
    + + Setter for the annotation's preset dashed border style. + + Setter for the annotation's preset dashed border style. This property has affect only if + + style was used for the annotation border style (see + + . + See ISO-320001 8.4.3.6, “Line Dash Pattern” for the format in which dash pattern shall be specified. + + + a dash array defining a pattern of dashes and gaps that + shall be used in drawing a dashed border. + + + this + + instance. + + + + The dictionaries for some annotation types (such as free text and polygon annotations) can include the BS entry. + + + The dictionaries for some annotation types (such as free text and polygon annotations) can include the BS entry. + That entry specifies a border style dictionary that has more settings than the array specified for the Border + entry (see + + ). If an annotation dictionary includes the BS entry, then the Border + entry is ignored. If annotation includes AP (see + + ) it takes + precedence over the BS entry. For more info on BS entry see ISO-320001, Table 166. + + + + + which is a border style dictionary or null if it is not specified. + + + + Sets annotation title. + Sets annotation title. This property affects not all annotation types. + + a + + which value is to be annotation title. + + + this + + instance. + + + + Annotation title. + + Annotation title. For example for markup annotations, the title is the text label that shall be displayed in the + title bar of the annotation’s pop-up window when open and active. For movie annotation Movie actions + (ISO-320001 12.6.4.9, “Movie Actions”) may use this title to reference the movie annotation. + + + + + which value is an annotation title or null if it isn't specifed. + + + + + Sets an appearance characteristics dictionary containing additional information for constructing the + annotation’s appearance stream. + + + Sets an appearance characteristics dictionary containing additional information for constructing the + annotation’s appearance stream. See ISO-320001, Table 189. + This property affects + + and + + . + + + the + + with additional information for appearance stream. + + + this + + instance. + + + + + An appearance characteristics dictionary containing additional information for constructing the + annotation’s appearance stream. + + + An appearance characteristics dictionary containing additional information for constructing the + annotation’s appearance stream. See ISO-320001, Table 189. + This property affects + + and + + . + + an appearance characteristics dictionary or null if it isn't specified. + + + + An + + to perform, such as launching an application, playing a sound, + changing an annotation’s appearance state etc, when the annotation is activated. + + + + + which defines the characteristics and behaviour of an action. + + + + An additional actions dictionary that extends the set of events that can trigger the execution of an action. + + + An additional actions dictionary that extends the set of events that can trigger the execution of an action. + See ISO-320001 12.6.3 Trigger Events. + + + an additional actions + + . + + + + + The annotation rectangle, defining the location of the annotation on the page in default user space units. + + + a + + which specifies a rectangle by two diagonally opposite corners. + Typically, the array is of form [llx lly urx ury]. + + + this + + instance. + + + + The annotation rectangle, defining the location of the annotation on the page in default user space units. + + + a + + which specifies a rectangle by two diagonally opposite corners. + Typically, the array is of form [llx lly urx ury]. + + + + + Inserts the value into into the underlying + + of this + + and associates it + with the specified key. If the key is already present in this + + , this method will override + the old value with the specified one. + + key to insert or to override + the value to associate with the specified key + + this + + instance. + + + + + Removes the specified key from the underlying + + of this + + . + + key to be removed + + this + + instance. + + + + + To manually flush a + PdfObject + behind this wrapper, you have to ensure + that this object is added to the document, i.e. it has an indirect reference. + Basically this means that before flushing you need to explicitly call + + . + For example: wrapperInstance.makeIndirect(document).flush(); + Note that not every wrapper require this, only those that have such warning in documentation. + + + + + This is a super class for the annotations which are defined as markup annotations + because they are used primarily to mark up PDF documents. + + + This is a super class for the annotations which are defined as markup annotations + because they are used primarily to mark up PDF documents. These annotations have + text that appears as part of the annotation and may be displayed in other ways + by a conforming reader, such as in a Comments pane. + See also ISO-320001 12.5.6.2 "Markup Annotations". + + + + + The text label that will be displayed in the title bar of the annotation's pop-up window + when open and active. + + + The text label that will be displayed in the title bar of the annotation's pop-up window + when open and active. This entry shall identify the user who added the annotation. + + + + + which value is an annotation text label content + or null if text is not specified. + + + + + Sets the text label that will be displayed in the title bar of the annotation's pop-up window + when open and active. + + + Sets the text label that will be displayed in the title bar of the annotation's pop-up window + when open and active. This entry shall identify the user who added the annotation. + + + + + which value is an annotation text label content. + + + this + + instance. + + + + The constant opacity value that will be used in painting the annotation. + + The constant opacity value that will be used in painting the annotation. + This value is applied to all visible elements of the annotation in its closed state + (including its background and border) but not to the pop-up window that appears when + the annotation is opened. Default value: 1.0. + + + a + + which value is in range between 0 and 1, which specifies the + level of opacity. This method returns null if opacity is not specified; in this case default + value is used, which is 1. + + + + Sets the constant opacity value that will be used in painting the annotation. + + a + + which value is in range between 0 and 1, which specifies the + level of opacity. + + + this + + instance. + + + + + + A rich text string (see ISO-320001 12.7.3.4, “Rich Text Strings”) that + shall be displayed in the pop-up window when the annotation is opened. + + + text string or text stream that specifies rich text or null if + rich text is not specified. + + + + + Sets a rich text string (see ISO-320001 12.7.3.4, “Rich Text Strings”) that + shall be displayed in the pop-up window when the annotation is opened. + + text string or text stream that specifies rich text. + + this + + instance. + + + + The date and time when the annotation was created. + + a + + which value should be in the date format specified in (ISO-320001 7.9.4, “Dates”). + + + + Sets the date and time when the annotation was created. + + + + which value should be in the date format + specified in (ISO-320001 7.9.4, “Dates”). + + + this + + instance. + + + + + An annotation object that this annotation is “in reply to.” + Both annotations shall be on the same page of the document. + + + An annotation object that this annotation is “in reply to.” + Both annotations shall be on the same page of the document. + The relationship between the two annotations shall be specified by the RT entry + (see + + ). + + + a + + that represents an annotation that this annotation is “in reply to.” + + + + + An annotation that this annotation is “in reply to.” + Both annotations shall be on the same page of the document. + + + An annotation that this annotation is “in reply to.” + Both annotations shall be on the same page of the document. + The relationship between the two annotations shall be specified by the RT entry + (see + + ). + + + a + + that this annotation is “in reply to.” + + + + + Sets an annotation that this annotation is “in reply to.” + Both annotations shall be on the same page of the document. + + + Sets an annotation that this annotation is “in reply to.” + Both annotations shall be on the same page of the document. + The relationship between the two annotations shall be specified by the RT entry + (see + + ). + + + a + + that this annotation is “in reply to.” + + + this + + instance. + + + + Sets a pop-up annotation for entering or editing the text associated with this annotation. + + Sets a pop-up annotation for entering or editing the text associated with this annotation. + Pop-up annotation defines an associated with this annotation pop-up window that may contain text. + The Contents (see + + ) entry of the annotation that has + an associated popup specifies the text that shall be displayed when the pop-up window is opened. + + + an + + that will be associated with this annotation. + + + this + + instance. + + + + An associated pop-up annotation object. + + An associated pop-up annotation object. See + + for more info. + + + a + + that represents an associated pop-up annotation, + or null if popup annotation is not specified. + + + + An associated pop-up annotation for entering or editing the text associated with this annotation. + + + An associated pop-up annotation for entering or editing the text associated with this annotation. + Pop-up annotation defines an associated with this annotation pop-up window that may contain text. + The Contents (see + + ) entry of the annotation that has + an associated popup specifies the text that shall be displayed when the pop-up window is opened. + + + an + + that is associated with this annotation, or null if there is none. + + + + Text representing a short description of the subject being addressed by the annotation. + + a + + which value is a annotation subject. + + + + Sets the text representing a short description of the subject being addressed by the annotation. + + a + + which value is a annotation subject. + + + this + + instance. + + + + + A name specifying the relationship (the “reply type”) between this annotation and one specified by IRT entry + (see + + ). Valid values are: +
      +
    • + + - The annotation shall be considered a reply to the annotation specified by IRT. + Conforming readers shall not display replies to an annotation individually but together in the form of + threaded comments.
    • +
    • + + - The annotation shall be grouped with the annotation specified by IRT.
    • +
    +
    + + a + + specifying relationship with the specified by the IRT entry; or null if reply + type is not specified, in this case the default value is + + . + +
    + + + Sets the relationship (the “reply type”) between this annotation and one specified by IRT entry + (see + + ). For valid values see + + . + + + a + + specifying relationship with the specified by the IRT entry. + + + this + + instance. + + + + A name describing the intent of the markup annotation. + + A name describing the intent of the markup annotation. + See + + for more info. + + + a + + describing the intent of the markup annotation, or null if not specified. + + + + Sets a name describing the intent of the markup annotation. + + Sets a name describing the intent of the markup annotation. + Intents allow conforming readers to distinguish between different uses and behaviors + of a single markup annotation type. If this entry is not present or its value is the same as the annotation type, + the annotation shall have no explicit intent and should behave in a generic manner in a conforming reader. +

    + See ISO-320001, free text annotations (Table 174), line annotations (Table 175), polygon annotations (Table 178), + and polyline annotations (Table 178) for the specific intent values for those types. +

    +
    + + a + + describing the intent of the markup annotation. + + + this + + instance. + +
    + + An external data dictionary specifying data that shall be associated with the annotation. + + An external data dictionary specifying data that shall be associated with the annotation. + This dictionary contains the following entries: +
      +
    • + + - (optional) If present, shall be + + .
    • +
    • + + - (required) a name specifying the type of data that the markup annotation + shall be associated with. The only defined value is + + . Table 298 (ISO-320001) + lists the values that correspond to a subtype of Markup3D (See also + + ).
    • +
    +
    + + An external data + + , or null if not specified. + +
    + + Sets an external data dictionary specifying data that shall be associated with the annotation. + + Sets an external data dictionary specifying data that shall be associated with the annotation. + This dictionary should contain the following entries: +
      +
    • + + - (optional) If present, shall be + + .
    • +
    • + + - (required) a name specifying the type of data that the markup annotation + shall be associated with. The only defined value is + + . Table 298 (ISO-320001) + lists the values that correspond to a subtype of Markup3D (See also + + ).
    • +
    +
    + + this + + instance. + +
    + + + A set of four numbers describing the numerical differences between two rectangles: + the Rect entry of the annotation and another rectangle within that one, which + meaning depends on the type of the annotation: +
      +
    • for + + the inner rectangle is where the annotation's text should be displayed;
    • +
    • + for + + and + + the inner rectangle is the actual boundaries + of the underlying square or circle; +
    • +
    • for + + the inner rectangle is the actual boundaries of the underlying caret.
    • +
    +
    + + a + + with four numbers which correspond to the differences in default user space between + the left, top, right, and bottom coordinates of Rect and those of the inner rectangle, respectively. + Each value shall be greater than or equal to 0. The sum of the top and bottom differences shall be + less than the height of Rect, and the sum of the left and right differences shall be less than + the width of Rect. + + + this + + instance. + +
    + + + A set of four numbers describing the numerical differences between two rectangles: + the Rect entry of the annotation and another rectangle within that one, which + meaning depends on the type of the annotation (see + + ). + + + null if not specified, otherwise a + + with four numbers which correspond to the + differences in default user space between the left, top, right, and bottom coordinates of Rect and those + of the inner rectangle, respectively. + + + + + Some annotations types ( + + , + + , + + and + + ) may have a + + entry, which is a border effect dictionary that specifies + an effect that shall be applied to the border of the annotations. + + + a + + which contents shall be specified in accordance to ISO-320001, Table 167. + + + this + + instance. + + + + A border effect dictionary that specifies an effect that shall be applied to the border of the annotations. + + + a + + , which is a border effect dictionary (see ISO-320001, Table 167). + + + + The interior color which is used to fill areas specific for different types of annotation. + + The interior color which is used to fill areas specific for different types of annotation. For + + and polyline annotation ( + + - the annotation's line endings, for + + and + + - the annotation's rectangle or ellipse, for + + - the redacted + region after the affected content has been removed. + + + + + of either + + , + + or + + type which defines + interior color of the annotation, or null if interior color is not specified. + + + + + An array of numbers in the range 0.0 to 1.0 specifying the interior color which is used to fill areas specific + for different types of annotation. + + + An array of numbers in the range 0.0 to 1.0 specifying the interior color which is used to fill areas specific + for different types of annotation. For + + and polyline annotation ( + + - + the annotation's line endings, for + + and + + - the annotation's + rectangle or ellipse, for + + - the redacted region after the affected content has been removed. + + + a + + of numbers in the range 0.0 to 1.0. The number of array elements determines + the colour space in which the colour is defined: 0 - No colour, transparent; 1 - DeviceGray, + 3 - DeviceRGB, 4 - DeviceCMYK. For the + + number of elements shall be + equal to 3 (which defines DeviceRGB colour space). + + + this + + instance. + + + + + An array of numbers in the range 0.0 to 1.0 specifying the interior color which is used to fill areas specific + for different types of annotation. + + + An array of numbers in the range 0.0 to 1.0 specifying the interior color which is used to fill areas specific + for different types of annotation. See + + for more info. + + an array of floats in the range 0.0 to 1.0. + + this + + instance. + + + + The name of an icon that is used in displaying the annotation. + + The name of an icon that is used in displaying the annotation. Possible values are different for different + annotation types. See + + . + + + a + + that specifies the icon for displaying annotation, or null if icon name is not specified. + + + + The name of an icon that is used in displaying the annotation. + + a + + that specifies the icon for displaying annotation. Possible values are different + for different annotation types: +
      +
    • + + - Comment, Key, Note, Help, NewParagraph, Paragraph, Insert;
    • +
    • + + - Approved, Experimental, NotApproved, AsIs, Expired, NotForPublicRelease, + Confidential, Final, Sold, Departmental, ForComment, TopSecret, Draft, ForPublicRelease.
    • +
    • + + - GraphPushPin, PaperclipTag. Additional names may be supported as well.
    • +
    • + + - Speaker and Mic. Additional names may be supported as well.
    • +
    + + + this + + instance. + +
    + + The default appearance string that shall be used in formatting the text. + The default appearance string that shall be used in formatting the text. See ISO-32001 12.7.3.3, “Variable Text”. + + + a + + that specifies the default appearance. + + + this + + instance. + + + + The default appearance string that shall be used in formatting the text. + The default appearance string that shall be used in formatting the text. See ISO-32001 12.7.3.3, “Variable Text”. + + + a + + that specifies the default appearance, or null if default appereance is not specified. + + + + + A code specifying the form of quadding (justification) that is used in displaying the annotation's text: + 0 - Left-justified, 1 - Centered, 2 - Right-justified. + + + A code specifying the form of quadding (justification) that is used in displaying the annotation's text: + 0 - Left-justified, 1 - Centered, 2 - Right-justified. Default value: 0 (left-justified). + + a code specifying the form of quadding (justification), returns the default value if not explicitly specified. + + + + + A code specifying the form of quadding (justification) that is used in displaying the annotation's text: + 0 - Left-justified, 1 - Centered, 2 - Right-justified. + + + A code specifying the form of quadding (justification) that is used in displaying the annotation's text: + 0 - Left-justified, 1 - Centered, 2 - Right-justified. Default value: 0 (left-justified). + + a code specifying the form of quadding (justification). + + this + + instance. + + + + Text justification options. + + + The purpose of a line annotation is to display a single straight line on the page. + + The purpose of a line annotation is to display a single straight line on the page. + When opened, it displays a pop-up window containing the text of the associated note. + See also ISO-320001 12.5.6.7 "Line Annotations". + + + + + Creates a + + instance. + + + the annotation rectangle, defining the location of the annotation on the page + in default user space units. See + + . + + + an array of four numbers, [x1 y1 x2 y2], specifying the starting and ending coordinates + of the line in default user space. See also + + . + + + + + Creates a + + instance from the given + + that represents annotation object. This method is useful for property reading in reading mode or + modifying in stamping mode. + + + a + + that represents existing annotation in the document. + + + + + + + + An array of four numbers, [x1 y1 x2 y2], specifying the starting and ending coordinates of the line + in default user space. + + + An array of four numbers, [x1 y1 x2 y2], specifying the starting and ending coordinates of the line + in default user space. If the + + (see + + ) entry is present, this value represents + the endpoints of the leader lines rather than the endpoints of the line itself. + + An array of four numbers specifying the starting and ending coordinates of the line in default user space. + + + + An array of two names specifying the line ending styles that is used in drawing the line. + + An array of two names specifying the line ending styles that is used in drawing the line. + The first and second elements of the array shall specify the line ending styles for the endpoints defined, + respectively, by the first and second pairs of coordinates, (x1, y1) and (x2, y2), in the + + array + (see + + . For possible values see + + . + + + An array of two names specifying the line ending styles that is used in drawing the line; or null if line + endings style is not explicitly defined, default value is [/None /None]. + + + + Sets the line ending styles that are used in drawing the line. + + Sets the line ending styles that are used in drawing the line. + The first and second elements of the array shall specify the line ending styles for the endpoints defined, + respectively, by the first and second pairs of coordinates, (x1, y1) and (x2, y2), in the + + array + (see + + . Possible values for styles are: +
      +
    • + + - A square filled with the annotation's interior color, if any;
    • +
    • + + - A circle filled with the annotation's interior color, if any;
    • +
    • + + - A diamond shape filled with the annotation's interior color, if any;
    • +
    • + + - Two short lines meeting in an acute angle to form an open arrowhead;
    • +
    • + + - Two short lines meeting in an acute angle as in the + + style and + connected by a third line to form a triangular closed arrowhead filled with the annotation's interior color, if any;
    • +
    • + + - No line ending;
    • +
    • + + - A short line at the endpoint perpendicular to the line itself;
    • +
    • + + - Two short lines in the reverse direction from + + ;
    • +
    • + + - A triangular closed arrowhead in the reverse direction from + + ;
    • +
    • + + - A short line at the endpoint approximately 30 degrees clockwise from perpendicular to the line itself;
    • +
    + see also ISO-320001, Table 176 "Line ending styles". +
    + An array of two names specifying the line ending styles that is used in drawing the line. + + + this + + instance. + +
    + + + The length of leader lines in default user space that extend from each endpoint of the line perpendicular + to the line itself. + + + The length of leader lines in default user space that extend from each endpoint of the line perpendicular + to the line itself. A positive value means that the leader lines appear in the direction that is clockwise + when traversing the line from its starting point to its ending point (as specified by + + (see + + ); + a negative value indicates the opposite direction. + + a float specifying the length of leader lines in default user space. + + + + Sets the length of leader lines in default user space that extend from each endpoint of the line perpendicular + to the line itself. + + + Sets the length of leader lines in default user space that extend from each endpoint of the line perpendicular + to the line itself. A positive value means that the leader lines appear in the direction that is clockwise + when traversing the line from its starting point to its ending point (as specified by + + (see + + ); + a negative value indicates the opposite direction. + + a float specifying the length of leader lines in default user space. + + this + + instance. + + + + + The length of leader lines in default user space that extend from each endpoint of the line perpendicular + to the line itself. + + + The length of leader lines in default user space that extend from each endpoint of the line perpendicular + to the line itself. A positive value means that the leader lines appear in the direction that is clockwise + when traversing the line from its starting point to its ending point (as specified by + + (see + + ); + a negative value indicates the opposite direction. + + a float specifying the length of leader lines in default user space. + + + + Sets the length of leader lines in default user space that extend from each endpoint of the line perpendicular + to the line itself. + + + Sets the length of leader lines in default user space that extend from each endpoint of the line perpendicular + to the line itself. A positive value means that the leader lines appear in the direction that is clockwise + when traversing the line from its starting point to its ending point (as specified by + + (see + + ); + a negative value indicates the opposite direction. + + a float specifying the length of leader lines in default user space. + + this + + instance. + + + + + A non-negative number that represents the length of leader line extensions that extend from the line proper + 180 degrees from the leader lines. + + + a non-negative float that represents the length of leader line extensions; or if the leader line extension + is not explicitly set, returns the default value, which is 0. + + + + Sets the length of leader line extensions that extend from the line proper 180 degrees from the leader lines. + + + Sets the length of leader line extensions that extend from the line proper 180 degrees from the leader lines. + This value shall not be set unless + + (see + + ) is set. + + a non-negative float that represents the length of leader line extensions. + + + this + + instance. + + + + + A non-negative number that represents the length of the leader line offset, which is the amount of empty space + between the endpoints of the annotation and the beginning of the leader lines. + + + a non-negative number that represents the length of the leader line offset, + or null if leader line offset is not set. + + + + + Sets the length of the leader line offset, which is the amount of empty space between the endpoints of the + annotation and the beginning of the leader lines. + + a non-negative number that represents the length of the leader line offset. + + + this + + instance. + + + + + If true, the text specified by the + + or + + entries + (see + + and + + ) + is replicated as a caption in the appearance of the line. + + + true, if the annotation text is replicated as a caption, false otherwise. If this property is + not set, default value is used which is false. + + + + + If set to true, the text specified by the + + or + + entries + (see + + and + + ) + will be replicated as a caption in the appearance of the line. + + true, if the annotation text should be replicated as a caption, false otherwise. + + + this + + instance. + + + + A name describing the annotation's caption positioning. + + A name describing the annotation's caption positioning. Valid values are + + , meaning the caption + is centered inside the line, and + + , meaning the caption is on top of the line. + + + a name describing the annotation's caption positioning, or null if the caption positioning is not + explicitly defined (in this case the default value is used, which is + + ). + + + + Sets annotation's caption positioning. + + Sets annotation's caption positioning. Valid values are + + , meaning the caption + is centered inside the line, and + + , meaning the caption is on top of the line. + + a name describing the annotation's caption positioning. + + this + + instance. + + + + A measure dictionary (see ISO-320001, Table 261) that specifies the scale and units that apply to the line annotation. + + + a + + that represents a measure dictionary. + + + + Sets a measure dictionary that specifies the scale and units that apply to the line annotation. + + a + + that represents a measure dictionary, see ISO-320001, Table 261 for valid + contents specification. + + + this + + instance. + + + + An array of two numbers that specifies the offset of the caption text from its normal position. + + An array of two numbers that specifies the offset of the caption text from its normal position. + The first value is the horizontal offset along the annotation line from its midpoint, with a positive value + indicating offset to the right and a negative value indicating offset to the left. The second value is the vertical + offset perpendicular to the annotation line, with a positive value indicating a shift up and a negative value indicating + a shift down. + + + a + + of two numbers that specifies the offset of the caption text from its normal position, + or null if caption offset is not explicitly specified (in this case a default value is used, which is [0, 0]). + + + + Sets the offset of the caption text from its normal position. + + a + + of two numbers that specifies the offset of the caption text from its + normal position. The first value defines the horizontal offset along the annotation line from + its midpoint, with a positive value indicating offset to the right and a negative value indicating + offset to the left. The second value defines the vertical offset perpendicular to the annotation line, + with a positive value indicating a shift up and a negative value indicating a shift down. + + + this + + instance. + + + + Sets the offset of the caption text from its normal position. + + an array of two floats that specifies the offset of the caption text from its + normal position. The first value defines the horizontal offset along the annotation line from + its midpoint, with a positive value indicating offset to the right and a negative value indicating + offset to the left. The second value defines the vertical offset perpendicular to the annotation line, + with a positive value indicating a shift up and a negative value indicating a shift down. + + + this + + instance. + + + + Highlight modes. + + + Subtypes + + + + + + Subtypes + + + + + + + Setter for the annotation's highlighting mode. + + Setter for the annotation's highlighting mode. Possible values are +
      +
    • + + - No highlighting.
    • +
    • + + - Invert the contents of the annotation rectangle.
    • +
    • + + - Invert the annotation's border.
    • +
    • + + - Display the annotation?s down appearance, if any.
    • +
    • + + - Same as P.
    • +
    +
    + The new value for the annotation's highlighting mode. + The widget annotation which this method was called on. +
    + + Getter for the annotation's highlighting mode. + Current value of the annotation's highlighting mode. + + + This method removes all widget annotation entries from the form field the given annotation merged with. + + + + Acts like a StringBuffer but works with byte arrays. + + + Acts like a StringBuffer but works with byte arrays. + Floating point is converted to a format suitable to the PDF. + + Paulo Soares + + + The count of bytes in the buffer. + + + The buffer where the bytes are stored. + + + If true always output floating point numbers with 6 decimal digits. + + + If true always output floating point numbers with 6 decimal digits. + If false uses the faster, although less precise, representation. + + + + Creates new ByteBufferOutputStream with capacity 128 + + + Creates a byte buffer with a certain capacity. + the initial capacity + + + + You can fill the cache in advance if you want to. + + + + Converts an double (multiplied by 100 and cast to an int) into an array of bytes. + + the int + a byte array + + + Appends an int. + Appends an int. The size of the array will grow by one. + the int to be appended + a reference to this ByteBufferOutputStream object + + + Appends the subarray of the byte array. + + Appends the subarray of the byte array. The buffer will grow by + len bytes. + + the array to be appended + the offset to the start of the array + the length of bytes to append + a reference to this ByteBufferOutputStream object + + + Appends an array of bytes. + the array to be appended + a reference to this ByteBufferOutputStream object + + + Appends a String to the buffer. + + Appends a String to the buffer. The String is + converted according to the encoding ISO-8859-1. + + the String to be appended + a reference to this ByteBufferOutputStream object + + + Appends a char to the buffer. + + Appends a char to the buffer. The char is + converted according to the encoding ISO-8859-1. + + the char to be appended + a reference to this ByteBufferOutputStream object + + + Appends another ByteBufferOutputStream to this buffer. + the ByteBufferOutputStream to be appended + a reference to this ByteBufferOutputStream object + + + Appends the string representation of an int. + the int to be appended + a reference to this ByteBufferOutputStream object + + + Appends the string representation of a long. + the long to be appended + a reference to this ByteBufferOutputStream object + + + + Appends a string representation of a float according + to the Pdf conventions. + + the float to be appended + a reference to this ByteBufferOutputStream object + + + + Appends a string representation of a double according + to the Pdf conventions. + + the double to be appended + a reference to this ByteBufferOutputStream object + + + Outputs a double into a format suitable for the PDF. + a double + the String representation of the double + + + Outputs a double into a format suitable for the PDF. + a double + a ByteBufferOutputStream + + the String representation of the double if + buf is null. If buf is not null, + then the double is appended directly to the buffer and this methods returns null. + + + + Sets the size to zero. + + + Creates a newly allocated byte array. + + Creates a newly allocated byte array. Its size is the current + size of this output stream and the valid contents of the buffer + have been copied into it. + + the current contents of this output stream, as a byte array. + + + Returns the current size of the buffer. + the value of the count field, which is the number of valid bytes in this byte buffer. + + + + + Converts the buffer's contents into a string, translating bytes into + characters according to the platform's default character encoding. + + String translated from the buffer's contents. + + + + Converts the buffer's contents into a string, translating bytes into + characters according to the specified character encoding. + + a character-encoding name. + String translated from the buffer's contents. + If the named encoding is not supported. + + + + + Writes the complete contents of this byte buffer output to + the specified output stream argument, as if by calling the output + stream's write method using out.write(buf, 0, count). + + the output stream to which to write the data. + + java.io.IOException + if an I/O error occurs. + + + + + + + + + A subclass of + + for Artifacts. + In Tagged PDF, an object can be marked as an Artifact in order to signify + that it is more part of the document structure than of the document content. + Examples are page headers, layout features, etc. Screen readers can choose to + ignore Artifacts. + + + + This class represents a single tag on a single piece of marked content. + + This class represents a single tag on a single piece of marked content. +

    + In Tagged PDF, a tag is the basic structure unit for marking content. The tag + structure and hierarchy is largely comparable to HTML. As in HTML, every tag + type has a name, defined here in the role attribute. The tagging + mechanism in Tagged PDF is extensible, so PDF creators can choose to create + custom tags.

    +
    +
    + + The type of the tag. + + + The properties of the tag. + + + Creates a tag that is referenced to the document's tag structure (i.e. + + Creates a tag that is referenced to the document's tag structure (i.e. + logical structure). + + the type of tag + + + Creates a tag that is referenced to the document's tag structure (i.e. + + Creates a tag that is referenced to the document's tag structure (i.e. + logical structure). + + the type of tag + marked content id which serves as a reference to the document's logical structure + + + Creates a tag that is referenced to the document's tag structure (i.e. + + Creates a tag that is referenced to the document's tag structure (i.e. + logical structure). + + + the + Marked Content Reference + wrapper object + + + + Get the role of the tag. + the role of the tag as a PdfName + + + Get the marked content id of the tag. + marked content id + if there is no MCID + + + Determine if an MCID is available + true if the MCID is available, false otherwise + + + + Adds a dictionary of properties to the + tag + 's properties. + + a dictionary + + current + + + + + + Adds a single property to the + tag + 's properties. + + a key + the value for the key + + current + + + + + + Removes a single property from the + tag + 's properties. + + the key of the key-value pair to be removed + + current + + + + + + Gets a property from the + tag + 's properties dictionary. + + the key of the key-value pair to be retrieved + the value corresponding to the key + + + Get the properties of the tag. + properties of the tag + + + + Creates a CanvasArtifact object, which is a + + with a role + of + Artifact + . + + + + This class is designed for internal usage. + + This class is designed for internal usage.
    + Use PdfExtGState class and PdfCanvas#setExtGState() method for setting extended graphics properties. +
    +
    + + The current transformation matrix, which maps positions from user coordinates to device coordinates. + + + The current transformation matrix, which maps positions from user coordinates to device coordinates. + We use an identity matrix as a default value, but in spec a default value is: + "a matrix that transforms default user coordinates to device coordinates". + + + + A description of the dash pattern to be used when paths are stroked. + + A description of the dash pattern to be used when paths are stroked. Default value is solid line. + The line dash pattern is expressed as an array of the form [ dashArray dashPhase ], + where dashArray is itself an array and dashPhase is an integer. + An empty dash array (first element in the array) and zero phase (second element in the array) + can be used to restore the dash pattern to a solid line. + + + + + Copy constructor. + the Graphics State to copy from + + + Updates this object with the values from a dictionary. + the dictionary containing source parameters + + + current transformation matrix. + + + Updates current transformation matrix. + + + Updates current transformation matrix. + new current transformation matrix. + + + Updates current graphic state with values from extended graphic state dictionary. + the wrapper around the extended graphic state dictionary + + + + Implementation of + + which draws a dashed horizontal line over + the middle of the specified rectangle. + + + + + The + + defines a drawing operation on a + +

    + This interface allows to customize the 'empty' space in a + TabStop + through a Strategy design + pattern +

    +
    + + + Performs configurable drawing operations related to specific region + coordinates on a canvas. + + the canvas to draw on + + the rectangle in relation to which to fulfill drawing + instructions + + + + Gets the width of the line + width of the line + + + Sets line width in points + new line width + + + Gets the color of the line + color of the line + + + Sets line color + new line color + + + + Creates an instance of + + with the specified line width. + + + + + Gets line width in points + line thickness + + + Sets line width in points + new line width + + + + Implementation of + + which draws a dotted horizontal line along + the bottom edge of the specified rectangle. + + + + the gap between the dots. + + + Constructs a dotted horizontal line which will be drawn along the bottom edge of the specified rectangle. + + + + Constructs a dotted horizontal line which will be drawn along the bottom edge of the specified rectangle. + + the width of the line + the gap between the center of the dots of the dotted line. + + + Constructs a dotted horizontal line which will be drawn along the bottom edge of the specified rectangle. + + the width of the line + + + Getter for the gap between the center of the dots of the dotted line. + the gap between the center of the dots + + + Setter for the gap between the center of the dots of the dotted line. + the gap between the center of the dots + + + Gets line width in points + line thickness + + + Sets line width in points + new line width + + + + Implementation of + + which draws a solid horizontal line along + the bottom edge of the specified rectangle. + + + + Constructs an instance of solid line drawer + + + Constructs an instance of solid line drawer with the specified line thickness + line width + + + Gets line width in points + line thickness + + + Sets line width in points + new line width + + + + This class contains variety of methods allowing to convert iText + abstractions into the abstractions of the Clipper library and vise versa. + + + This class contains variety of methods allowing to convert iText + abstractions into the abstractions of the Clipper library and vise versa. +

    + For example: +

      +
    • + + to + +
    • +
    • + + to + +
    • +
    • + + to + +
    • +
    +

    +
    +
    + + + Since the clipper library uses integer coordinates, we should convert + our floating point numbers into fixed point numbers by multiplying by + this coefficient. + + + Since the clipper library uses integer coordinates, we should convert + our floating point numbers into fixed point numbers by multiplying by + this coefficient. Vary it to adjust the preciseness of the calculations. + + + + + Converts Clipper library + + abstraction into iText + + object. + + + + + + + Converts list of + + objects into list of + + objects. + + + + + Converts list of + + objects into list of + + objects. + + + + + Converts iText line join style constant into the corresponding constant + of the Clipper library. + + + iText line join style constant. See + + + Clipper line join style constant. + + + + Converts iText line cap style constant into the corresponding constant + of the Clipper library. + + + iText line cap style constant. See + + + Clipper line cap (end type) style constant. + + + + Converts iText filling rule constant into the corresponding constant + of the Clipper library . + + + Either + + or + + . + + Clipper fill type constant. + + + + Marker interface for the set of classes used to combine + parameters required for different types of events. + + + + The path to be rendered. + The path to be rendered. + + + + The + + which represents current clipping path. + + + + Current transformation matrix. + + + Represents image data from a PDF + + + The coordinate transformation matrix that was in effect when the image was rendered + + + the color space dictionary from resources which are associated with the image + + + defines if the encountered image was inline + + + Create an ImageRenderInfo + the coordinate transformation matrix at the time the image is rendered + image stream object + the color space dictionary from resources which are associated with the image + + defines if the encountered image was inline + + + Gets an image wrapped in ImageXObject. + + Gets an image wrapped in ImageXObject. + You can: +
      +
    • get image bytes with + + , these image bytes + represent native image, i.e you can write these bytes to disk and get just an usual image;
    • +
    • obtain PdfStream object which contains image dictionary with + + method;
    • +
    • convert image to + + with + + ;
    • +
    +
    +
    + + a vector in User space representing the start point of the image + + + The coordinate transformation matrix which was active when this image was rendered. Coordinates are in User space. + + + + the size of the image, in User space units + + + true if image was inlined in original stream. + + + the color space dictionary from resources which are associated with the image + + + Contains information relating to painting current path. + + + End the path object without filling or stroking it. + + End the path object without filling or stroking it. This operator shall be a path-painting no-op, + used primarily for the side effect of changing the current clipping path + + + + Value specifying stroke operation to perform on the current path. + + + Value specifying fill operation to perform on the current path. + + Value specifying fill operation to perform on the current path. When the fill operation + is performed it should use either nonzero winding or even-odd rule. + + + + The path to be rendered. + + One of the possible combinations of + + and + + values or + + + + Either + + or + + . + + True indicates that current path modifies the clipping path, false - if not. + + Either + + or + + . + + The graphics state. + + + + If the operation is + + then the rule is ignored, + otherwise + + is used by default. + With this constructor path is considered as not modifying clipping path. + See + + + + + + The + + to be rendered. + + + + + int value which is either + + or one of possible + combinations of + + and + + + + + + Either + + or + + . + + + + true indicates that current path modifies the clipping path, false - if not. + + + + Either + + or + + . + + + + Current transformation matrix. + + + + Provides information and calculations needed by render listeners + to display/evaluate text render operations. + + + Provides information and calculations needed by render listeners + to display/evaluate text render operations. +

    + This is passed between the + + and + + objects as text rendering operations are + discovered +
    +
    + + Hierarchy of nested canvas tags for the text from the most inner (nearest to text) tag to the most outer. + + + + Creates a new TextRenderInfo object + the PDF string that should be displayed + the graphics state (note: at this time, this is not immutable, so don't cache it) + the text matrix at the time of the render operation + the marked content tags sequence, if available + + + Used for creating sub-TextRenderInfos for each individual character + the parent TextRenderInfo + the content of a TextRenderInfo + the unscaled horizontal offset of the character that this TextRenderInfo represents + + + + the text to render + + + original PDF string + + + + Checks if the text belongs to a marked content sequence + with a given mcid. + + a marked content id + true if the text is marked with this id + + + + Checks if the text belongs to a marked content sequence + with a given mcid. + + a marked content id + indicates whether to check the topmost level of marked content stack only + + true if the text is marked with this id + + + the marked content associated with the TextRenderInfo instance. + + + Gets the baseline for the text (i.e. + + Gets the baseline for the text (i.e. the line that the text 'sits' on) + This value includes the Rise of the draw operation - see + + for the amount added by Rise + + the baseline line segment + + + Gets the ascentline for the text (i.e. + + Gets the ascentline for the text (i.e. the line that represents the topmost extent that a string of the current font could have) + This value includes the Rise of the draw operation - see + + for the amount added by Rise + + the ascentline line segment + + + Gets the descentline for the text (i.e. + + Gets the descentline for the text (i.e. the line that represents the bottom most extent that a string of the current font could have). + This value includes the Rise of the draw operation - see + + for the amount added by Rise + + the descentline line segment + + + Getter for the font + the font + + + The rise represents how far above the nominal baseline the text should be rendered. + + The rise represents how far above the nominal baseline the text should be rendered. The + + , + + and + + methods already include Rise. + This method is exposed to allow listeners to determine if an explicit rise was involved in the computation of the baseline (this might be useful, for example, for identifying superscript rendering) + + The Rise for the text draw operation, in user space units (Ts value, scaled to user space) + + + Provides detail useful if a listener needs access to the position of each individual glyph in the text render operation + + + A list of + + objects that represent each glyph used in the draw operation. The next effect is if there was a separate Tj opertion for each character in the rendered string + + + + The width, in user space units, of a single space character in the current font + + + + the text render mode that should be used for the text. From the + PDF specification, this means: +
      +
    • 0 = Fill text
    • +
    • 1 = Stroke text
    • +
    • 2 = Fill, then stroke text
    • +
    • 3 = Invisible
    • +
    • 4 = Fill text and add to path for clipping
    • +
    • 5 = Stroke text and add to path for clipping
    • +
    • 6 = Fill, then stroke text and add to path for clipping
    • +
    • 7 = Add text to padd for clipping
    • +
    +
    +
    + + the current fill color. + + + the current stroke color. + + + Gets /ActualText tag entry value if this text chunk is marked content. + /ActualText value + + + Gets hierarchy of the canvas tags that wraps given text. + list of the wrapping canvas tags. The first tag is the innermost (nearest to the text). + + + the unscaled (i.e. in Text space) width of the text + + + the width, in text space + the width in user space + + + the height, in text space + the height in user space + + + Calculates the width of a space character. + + Calculates the width of a space character. If the font does not define + a width for a standard space character \u0020, we also attempt to use + the width of \u00A0 (a non-breaking space in many fonts) + + the width of a single space character in text space units + + + Gets the width of a String in text space units + the string that needs measuring + the width of a String in text space units + + + Gets the width of a PDF string in text space units + the string that needs measuring + the width of a String in text space units + + + Calculates width and word spacing of a single character PDF string. + + Calculates width and word spacing of a single character PDF string. + IMPORTANT: Shall ONLY be used for a single character pdf strings. + + a character to calculate width. + array of 2 items: first item is a character width, second item is a calculated word spacing. + + + Converts a single character string to char code. + single character string to convert to. + char code. + + + Split PDF string into array of single character PDF strings. + PDF string to be splitted. + splitted PDF string. + + + Specifies different types of events where a callback should be notified. + + + This is an interface which helps to filter events. + + + + This method checks an event and decides whether it should be processed further (corresponds to + + return value), or filtered out (corresponds to + + return value). + + event data + event type + true to process event further, false to filter event out + + + + This + + implementation only accepts text render events within the specified + rectangular region. + + + + Constructs a filter instance. + the rectangle to filter text against + + + Root interface for a series of handlers for content stream operators. + + + Called when a content operator should be processed. + The processor that is dealing with the PDF content stream. + The literal PDF syntax of the operator. + The operands that come with the operator. + + + Kevin Day + + + An event listener which filters events on the fly before passing them on to the delegate. + + + + A callback interface that receives notifications from the + + as various events occur (see + + ). + + + + Called when some event occurs during parsing a content stream. + Combines the data required for processing corresponding event type. + Event type. + + + Provides the set of event types this listener supports. + + Provides the set of event types this listener supports. + Returns null if all possible event types are supported. + + + Set of event types supported by this listener or + null if all possible event types are supported. + + + + + Constructs a + + empty instance. + Use + + to add an event listener along with its filters. + + + + + Constructs a + + instance with one delegate. + Use + + to add more + + delegates + along with their filters. + + a delegate that fill be called when all the corresponding filters for an event pass + + filters attached to the delegate that will be tested before passing an event on to the delegate + + + + + Attaches another + + delegate with its filters. + When all the filters attached to the delegate for an event accept the event, the event will be passed on to + the delegate. + You can attach multiple delegates to this + + instance. The content stream will + be parsed just once, so it is better for performance than creating multiple + + instances and parsing the content stream multiple times. This is useful, for instance, when you want + to extract content from multiple regions of a page. + + a delegate that fill be called when all the corresponding filters for an event pass + + filters attached to the delegate that will be tested before passing an event on to the delegate + + delegate that has been passed to the method, used for convenient call chaining + + + A text event listener which filters events on the fly before passing them on to the delegate. + + A text event listener which filters events on the fly before passing them on to the delegate. + The only difference from + + is that this class conveniently implements + + and can therefore used as a strategy on its own, apart from the inherited + function of filtering event appropriately to its delegates. + + + + + This is a special interface for + + that returns text as result of its work. + + + + Returns the text that has been processed so far. + + + + instance with the current resultant text + + + + + Constructs a + + instance with a + + delegate. + + a delegate that fill be called when all the corresponding filters for an event pass + + filters attached to the delegate that will be tested before passing an event on to the delegate + + + + + As an resultant text we use the concatenation of all the resultant text of all the delegates that implement + + . + + the resulting concatenation of the text extracted from the delegates + + + + This class expands each + + for + + event types into + multiple + + instances for each glyph occurred. + + + + + Constructs a + + instance by a delegate to which the expanded text events for each + glyph occurred will be passed on. + + delegate to pass the expanded glyph render events to. + + + + This class expands each + + for + + event types into + multiple + + instances for each glyph occurred. + The only difference from + + is that this class conveniently implements + + and can therefore used as a strategy on its own. + + + + + Constructs a + + instance by a + + delegate to which + the expanded text events for each glyph occurred will be passed on. + + delegate to pass the expanded glyph render events to. + + + + As an resultant text we use the the resultant text of the delegate that implement + + and was passed to this class. + + the resulting text extracted from the delegate + + + set to true for debugging + + + a summary of all found text + + + Creates a new text extraction renderer. + + + + Creates a new text extraction renderer, with a custom strategy for + creating new TextChunkLocation objects based on the input of the + TextRenderInfo. + + the custom strategy + + + + Changes the behavior of text extraction so that if the parameter is set to + + , + /ActualText marked content property will be used instead of raw decoded bytes. + Beware: the logic is not stable yet. + + true to use /ActualText, false otherwise + this object + + + + Gets the value of the property which determines if /ActualText will be used when extracting + the text + + true if /ActualText value is used, false otherwise + + + Determines if a space character should be inserted between a previous chunk and the current chunk. + + + Determines if a space character should be inserted between a previous chunk and the current chunk. + This method is exposed as a callback so subclasses can fine time the algorithm for determining whether a space should be inserted or not. + By default, this method will insert a space if the there is a gap of more than half the font space character width between the end of the + previous chunk and the beginning of the current chunk. It will also indicate that a space is needed if the starting point of the new chunk + appears *before* the end of the previous chunk (i.e. overlapping text). + + the new chunk being evaluated + the chunk that appeared immediately before the current chunk + true if the two chunks represent different words (i.e. should have a space between them). False otherwise. + + + + Checks if the string starts with a space character, false if the string is empty or starts with a non-space character. + + the string to be checked + true if the string starts with a space character, false if the string is empty or starts with a non-space character + + + + Checks if the string ends with a space character, false if the string is empty or ends with a non-space character + + the string to be checked + true if the string ends with a space character, false if the string is empty or ends with a non-space character + + + + Used for debugging only + + + Represents a chunk of text, it's orientation, and location relative to the orientation vector + + + the text of the chunk + + + the text captured by this chunk + + + Compares based on orientation, perpendicular distance, then parallel distance + + + + the starting location of the chunk + + + the ending location of the chunk + + + unit vector in the orientation of the chunk + + + the orientation as a scalar for quick sorting + + + perpendicular distance to the orientation unit vector (i.e. + + perpendicular distance to the orientation unit vector (i.e. the Y position in an unrotated coordinate system) + we round to the nearest integer to handle the fuzziness of comparing floats + + + + distance of the start of the chunk parallel to the orientation unit vector (i.e. + distance of the start of the chunk parallel to the orientation unit vector (i.e. the X position in an unrotated coordinate system) + + + + distance of the end of the chunk parallel to the orientation unit vector (i.e. + distance of the end of the chunk parallel to the orientation unit vector (i.e. the X position in an unrotated coordinate system) + + + + the width of a single space character in the font of the chunk + + + the start location of the text + + + the end location of the text + + + the width of a single space character as rendered by this chunk + + + the location to compare to + true is this location is on the the same line as the other + + + + Computes the distance between the end of 'other' and the beginning of this chunk + in the direction of this chunk's orientation vector. + + + Computes the distance between the end of 'other' and the beginning of this chunk + in the direction of this chunk's orientation vector. Note that it's a bad idea + to call this for chunks that aren't on the same line and orientation, but we don't + explicitly check for that condition for performance reasons. + + + the number of spaces between the end of 'other' and the beginning of this chunk + + + used to store the resulting String. + + + Returns the result so far. + a String with the resulting text. + + + Used to actually append text to the text results. + + Used to actually append text to the text results. Subclasses can use this to insert + text that wouldn't normally be included in text parsing (e.g. result of OCR performed against + image content) + + the text to append to the text results accumulated so far + + + This class allows you to find the rectangle which contains all the text in the given content stream. + + + + + Returns the common text rectangle, containing all the text found in the stream so far, ot + + , if no + text has been found yet. + + common text rectangle + + + + Internal class which is essentially a + + which supports tracking of + clipping path state and changes. + + + + + Copy constructor. + the Graphics State to copy from + + + Sets the current clipping path to the specified path. + + Sets the current clipping path to the specified path. +
    + Note:This method doesn't modify existing clipping path, + it simply replaces it with the new one instead. +
    + New clipping path. +
    + + Intersects the current clipping path with the given path. + + Intersects the current clipping path with the given path. +
    + Note: Coordinates of the given path should be in + the transformed user space. +
    + The path to be intersected with the current clipping path. + + The filling rule which should be applied to the given path. + It should be either + + or + + +
    + + Getter for the current clipping path. + + Getter for the current clipping path. +
    + Note: The returned clipping path is in the transformed user space, so + if you want to get it in default user space, apply transformation matrix ( + + ). +
    + The current clipping path. +
    + + Processor for a PDF content stream. + + + Listener that will be notified of render events + + + + Cache supported events in case the user's + + method is not very efficient + + + + + Indicates whether the current clipping path should be modified by + intersecting it with the current path. + + + + + Specifies the filling rule which should be applied while calculating + new clipping path. + + + + A map with all supported operators (PDF syntax). + + + Resources for the content stream. + + Resources for the content stream. + Current resources are always at the top of the stack. + Stack is needed in case if some "inner" content stream with it's own resources + is encountered (like Form XObject). + + + + Stack keeping track of the graphics state. + + + A map with all supported XObject handlers + + + The font cache + + + A stack containing marked content info. + + + + Creates a new PDF Content Stream Processor that will send its output to the + designated render listener. + + + the + + that will receive rendering notifications + + + + + Creates a new PDF Content Stream Processor that will send its output to the + designated render listener. + + + Creates a new PDF Content Stream Processor that will send its output to the + designated render listener. + Also allows registration of custom IContentOperators that can influence + how (and whether or not) the PDF instructions will be parsed. + + + the + + that will receive rendering notifications + + + an optional map of custom + + s for rendering instructions + + + + Registers a Do handler that will be called when Do for the provided XObject subtype is encountered during content processing. + + + Registers a Do handler that will be called when Do for the provided XObject subtype is encountered during content processing. +
    + If you register a handler, it is a very good idea to pass the call on to the existing registered handler (returned by this call), otherwise you + may inadvertently change the internal behavior of the processor. +
    + the XObject subtype this handler will process, or PdfName.DEFAULT for a catch-all handler + + the handler that will receive notification when the Do operator for the specified subtype is encountered + + the existing registered handler, if any +
    + + Registers a content operator that will be called when the specified operator string is encountered during content processing. + + + Registers a content operator that will be called when the specified operator string is encountered during content processing. +
    + If you register an operator, it is a very good idea to pass the call on to the existing registered operator (returned by this call), otherwise you + may inadvertently change the internal behavior of the processor. +
    + the operator id, or DEFAULT_OPERATOR for a catch-all operator + the operator that will receive notification when the operator is encountered + the existing registered operator, if any +
    + + + + + containing all the registered operators strings + + + + Resets the graphics state stack, matrices and resources. + + + the current graphics state + + + Processes PDF syntax. + + Processes PDF syntax. + Note: If you re-use a given + + , you must call + + + the bytes of a content stream + the resources of the content stream. Must not be null. + + + Processes PDF syntax. + + Processes PDF syntax. +
    + Note: If you re-use a given + + , you must call + +
    + the page to process +
    + + + Accessor method for the + + object maintained in this class. + Necessary for implementing custom ContentOperator implementations. + + the renderListener + + + Loads all the supported graphics and text state operators in a map. + + + Displays the current path. + + One of the possible combinations of + + and + + values or + + + + Either + + or + + In case it isn't applicable pass any byte value. + + + + Invokes an operator. + the PDF Syntax of the operator + a list with operands + + + Gets the font pointed to by the indirect reference. + Gets the font pointed to by the indirect reference. The font may have been cached. + + the font + + + Add to the marked content stack + the tag of the marked content + the PdfDictionary associated with the marked content + + + Remove the latest marked content from the stack. + Remove the latest marked content from the stack. Keeps track of the BMC, BDC and EMC operators. + + + Used to trigger beginTextBlock on the renderListener + + + Used to trigger endTextBlock on the renderListener + + + This is a proxy to pass only those events to the event listener which are supported by it. + event data + event type + + + Displays text. + the text to display + + + Displays an XObject using the registered handler for this XObject's subtype + the name of the XObject to retrieve from the resource dictionary + + + Adjusts the text matrix for the specified adjustment value (see TJ operator in the PDF spec for information) + + the text adjustment + + + Gets a color based on a list of operands and Color space. + + + Gets a color based on a list of operands. + + + A content operator implementation (unregistered). + + + A content operator implementation (TJ). + + + A content operator implementation ("). + + + A content operator implementation ('). + + + A content operator implementation (Tj). + + + A content operator implementation (T*). + + + A content operator implementation (Tm). + + + A content operator implementation (TD). + + + A content operator implementation (Td). + + + A content operator implementation (Tf). + + + A content operator implementation (Tr). + + + A content operator implementation (Ts). + + + A content operator implementation (TL). + + + A content operator implementation (Tz). + + + A content operator implementation (Tc). + + + A content operator implementation (Tw). + + + A content operator implementation (gs). + + + A content operator implementation (q). + + + A content operator implementation (cm). + + + A content operator implementation (Q). + + + A content operator implementation (g). + + + A content operator implementation (G). + + + A content operator implementation (rg). + + + A content operator implementation (RG). + + + A content operator implementation (k). + + + A content operator implementation (K). + + + A content operator implementation (CS). + + + A content operator implementation (cs). + + + A content operator implementation (sc / scn). + + + A content operator implementation (SC / SCN). + + + A content operator implementation (BT). + + + A content operator implementation (ET). + + + A content operator implementation (BMC). + + + A content operator implementation (BDC). + + + A content operator implementation (EMC). + + + A content operator implementation (Do). + + + A content operator implementation (EI). + + A content operator implementation (EI). BI and ID operators are parsed along with this operator. + This not a usual operator, it will have a single operand, which will be a PdfStream object which + encapsulates inline image dictionary and bytes + + + + A content operator implementation (w). + + + A content operator implementation (J). + + + A content operator implementation (j). + + + A content operator implementation (M). + + + A content operator implementation (d). + + + An XObject subtype handler for FORM + + + An XObject subtype handler for IMAGE + + + An XObject subtype handler that does nothing + + + A content operator implementation (m). + + + A content operator implementation (l). + + + A content operator implementation (c). + + + A content operator implementation (v). + + + A content operator implementation (y). + + + A content operator implementation (h). + + + A content operator implementation (re). + + + A content operator implementation (S, s, f, F, f*, B, B*, b, b*). + + + Constructs PainPath object. + + One of the possible combinations of + + and + + values or + + + + Either + + or + + In case it isn't applicable pass any value. + + Indicates whether the path should be closed or not. + + + A content operator implementation (W, W*) + + + + A utility class that makes it cleaner to process content from pages of a + + through a specified RenderListener. + + + + Processes content from the specified page number using the specified listener. + + Processes content from the specified page number using the specified listener. + Also allows registration of custom IContentOperators that can influence + how (and whether or not) the PDF instructions will be parsed. + + + the page number to process + the listener that will receive render callbacks + an optional map of custom ContentOperators for rendering instructions + + the provided renderListener + + + Processes content from the specified page number using the specified listener + + the page number to process + the listener that will receive render callbacks + the provided renderListener + + + Extract text from a specified page using an extraction strategy. + + Extract text from a specified page using an extraction strategy. + Also allows registration of custom IContentOperators that can influence + how (and whether or not) the PDF instructions will be parsed. + + the page for the text to be extracted from + the strategy to use for extracting text + + an optional map of custom + + s for rendering instructions + + the extracted text + + + Extract text from a specified page using an extraction strategy. + the page for the text to be extracted from + the strategy to use for extracting text + the extracted text + + + Extract text from a specified page using the default strategy. + + Extract text from a specified page using the default strategy. + Node: the default strategy is subject to change. If using a specific strategy + is important, please use + + . + + the page for the text to be extracted from + the extracted text + + + Utility methods to help with processing of inline images + + + + Map between key abbreviations allowed in dictionary of inline images and their + equivalent image dictionary keys + + + + Map between value abbreviations allowed in dictionary of inline images for COLORSPACE + + + Map between value abbreviations allowed in dictionary of inline images for FILTER + + + Parses an inline image from the provided content parser. + + Parses an inline image from the provided content parser. The parser must be positioned immediately following the BI operator in the content stream. + The parser will be left with current position immediately following the EI operator that terminates the inline image + + the content parser to use for reading the image. + a color space dictionary + the parsed image + if anything goes wring with the parsing + if parsing of the inline image failed due to issues specific to inline image processing + + + + Parses the next inline image dictionary from the parser. + + Parses the next inline image dictionary from the parser. The parser must be positioned immediately following the BI operator. + The parser will be left with position immediately following the whitespace character that follows the ID operator that ends the inline image dictionary. + + the parser to extract the embedded image information from + the dictionary for the inline image, with any abbreviations converted to regular image dictionary keys and values + + if the parse fails + + + Transforms value abbreviations into their corresponding real value + the key that the value is for + the value that might be an abbreviation + if value is an allowed abbreviation for the key, the expanded value for that abbreviation. Otherwise, value is returned without modification + + + + the name of the color space. If null, a bi-tonal (black and white) color space is assumed. + + the components per pixel for the specified color space + + + Computes the number of unfiltered bytes that each row of the image will contain. + + Computes the number of unfiltered bytes that each row of the image will contain. + If the number of bytes results in a partial terminating byte, this number is rounded up + per the PDF specification + + the dictionary of the inline image + the number of bytes per row of the image + + + Parses the samples of the image from the underlying content parser, ignoring all filters. + + Parses the samples of the image from the underlying content parser, ignoring all filters. + The parser must be positioned immediately after the ID operator that ends the inline image's dictionary. + The parser will be left positioned immediately following the EI operator. + This is primarily useful if no filters have been applied. + + the dictionary of the inline image + the content parser + the samples of the image + if anything bad happens during parsing + + + + Parses the samples of the image from the underlying content parser, accounting for filters + The parser must be positioned immediately after the ID operator that ends the inline image's dictionary. + + + Parses the samples of the image from the underlying content parser, accounting for filters + The parser must be positioned immediately after the ID operator that ends the inline image's dictionary. + The parser will be left positioned immediately following the EI operator. + Note:This implementation does not actually apply the filters at this time + + the dictionary of the inline image + the content parser + the samples of the image + if anything bad happens during parsing + + + + + Simple class in case users need to differentiate an exception from processing + inline images vs other exceptions + + + + Parses the page or form XObject content. + Paulo Soares + + + Holds value of property tokeniser. + + + Creates a new instance of PdfContentParser + the tokeniser with the content + + + Creates a new instance of PdfContentParser + the tokeniser with the content + + current resources of the content stream. + It is optional parameter, which is used for performance improvements of specific cases of + inline images parsing. + + + + Parses a single command from the content. + + Parses a single command from the content. Each command is output as an array of arguments + having the command itself as the last element. The returned array will be empty if the + end of content was reached. +
    + A specific behaviour occurs when inline image is encountered (BI command): + in that case, parser would continue parsing until it meets EI - end of the inline image; + as a result in this case it will return an array with inline image dictionary and image bytes + encapsulated in PdfStream object as first element and EI command as second element. +
    + + an ArrayList to use. It will be cleared before using. If it's + null will create a new ArrayList + + the same ArrayList given as argument or a new one + on error +
    + + Gets the tokeniser. + the tokeniser. + + + Sets the tokeniser. + the tokeniser + + + Reads a dictionary. + Reads a dictionary. The tokeniser must be positioned past the "<<" token. + the dictionary + on error + + + Reads an array. + Reads an array. The tokeniser must be positioned past the "[" token. + an array + on error + + + Reads a pdf object. + the pdf object + on error + + + Reads the next token skipping over the comments. + true if a token was read, false if the end of content was reached + on error + + + A container for constants defined in the PDF specification (ISO 32000-1). + + + + The text rendering mode determines whether showing text causes glyph + outlines to be stroked, filled, used as a clipping boundary, or some + combination of the three. + + + The text rendering mode determines whether showing text causes glyph + outlines to be stroked, filled, used as a clipping boundary, or some + combination of the three. Stroking, filling, and clipping have the same + effects for a text object as they do for a path object, although they are + specified in an entirely different way. + If the text rendering mode calls for filling, the current nonstroking + color in the graphics state is used; if it calls for stroking, the + current stroking color is used. + All documentation for this class is taken from ISO 32000-1, section 9.3.6 + "Text Rendering Mode". + + + + Fill text + + + Stroke text, providing the outline of the glyphs + + + Fill and stroke text + + + Neither fill nor stroke, i.e. + Neither fill nor stroke, i.e. render invisibly + + + Fill text and add to path for clipping + + + Stroke text and add to path for clipping + + + Fill, then stroke text and add to path for clipping + + + Add text to path for clipping + + + + The line cap style specifies the shape to be used at the ends of open + subpaths (and dashes, if any) when they are stroked. + + + The line cap style specifies the shape to be used at the ends of open + subpaths (and dashes, if any) when they are stroked. + All documentation for this class is taken from ISO 32000-1, section + 8.4.3.3 "Line Cap Style". + + + + The stroke is squared of at the endpoint of the path. + + The stroke is squared of at the endpoint of the path. There is no + projection beyond the end of the path. + + + + + A semicircular arc with a diameter equal to the line width is drawn + around the endpoint and filled in. + + + + + The stroke continues beyond the endpoint of the path for a distance + equal to half the line width and is squared off. + + + + + The line join style specifies the shape to be used at the corners of + paths that are stroked. + + + The line join style specifies the shape to be used at the corners of + paths that are stroked. Join styles are significant only at points where + consecutive segments of a path connect at an angle; segments that meet or + intersect fortuitously receive no special treatment. + All documentation for this class is taken from ISO 32000-1, section + 8.4.3.4 "Line Join Style". + + + + + The outer edges of the strokes for the two segments are extended + until they meet at an angle, as in a picture frame. + + + The outer edges of the strokes for the two segments are extended + until they meet at an angle, as in a picture frame. If the segments + meet at too sharp an angle, a bevel join is used instead. + + + + + An arc of a circle with a diameter equal to the line width is drawn + around the point where the two segments meet, connecting the outer + edges of the strokes for the two segments. + + + An arc of a circle with a diameter equal to the line width is drawn + around the point where the two segments meet, connecting the outer + edges of the strokes for the two segments. This pieslice-shaped + figure is filled in, producing a rounded corner. + + + + + The two segments are finished with butt caps (@see LineCapStyle#BUTT) + and the resulting notch beyond the ends of the segments is filled + with a triangle. + + + + A PdfCanvas instance with an inherent tiling pattern. + + + Creates PdfPatternCanvas from content stream of page, form XObject, pattern etc. + @see PdfStream. + the resources, a specialized dictionary that can be used by PDF instructions in the content stream + + the document that the resulting content stream will be written to + + + Creates PdfPatternCanvas for a document from a provided Tiling pattern + @see PdfPattern.Tiling. The Tiling pattern must be colored + the document that the resulting content stream will be written to + + + Helper class to read nt, short, words, etc. + Helper class to read nt, short, words, etc. from an InputStream. + + + Creates an InputMeta object. + InputStream containing the WMF data + + + Read the next word from the InputStream. + the next word or 0 if the end of the stream has been reached + + + + Read the next short from the InputStream. + the next short value + + + + Read the next int from the InputStream. + the next int + + + + Read the next byte from the InputStream. + the next byte + + + + Skips "len" amount of bytes from the InputStream. + Skips "len" amount of bytes from the InputStream. If len is < 0, nothing is skipped. + amount of bytes needed to skip + + + + Get the amount of bytes read and/or skipped from the InputStream. + number of bytes read + + + + Read the next + + from the InputStream. This reads 4 bytes. + + the next Color + + + + + A Brush bject that holds information about the style, the hatch and the color of + the brush. + + + + A meta object. + + + Creates a new MetaObject. + Creates a new MetaObject. This constructor doesn't set the type. + + + Creates a MetaObject with a type. + the type of meta object + + + Get the type of this MetaObject. + type of MetaObject + + + Creates a MetaBrush object. + + + Initializes this MetaBrush object. + the InputMeta + + + + Get the style of the MetaBrush. + style of the brush + + + Get the hatch pattern of the MetaBrush + hatch of the brush + + + Get the color of the MetaBrush. + color of the brush + + + A class to process WMF files. + + A class to process WMF files. Used internally by + + . + + + + PdfCanvas of the MetaDo object. + + + The InputMeta instance containing the data. + + + Creates a MetaDo instance. + inputstream containing the data + PdfCanvas + + + Reads and processes all the data of the InputMeta. + + + + Output Text at a certain x and y coordinate. + Output Text at a certain x and y coordinate. Clipped or opaque text isn't supported as of yet. + x-coordinate + y-coordinate + flag indicating clipped or opaque + x1-coordinate of the rectangle if clipped or opaque + y1-coordinate of the rectangle if clipped or opaque + x2-coordinate of the rectangle if clipped or opaque + y1-coordinate of the rectangle if clipped or opaque + text to output + + + + Return true if the pen style is null and if it isn't a brush. + + value to decide how to change the state. If true state.setLineJoinRectangle(cb) is called, + if false state.setLineJoinPolygon(cb) is called. + + true if the pen style is null and if it isn't a brush + + + Stroke and fill the MetaPen and MetaBrush paths. + + + Wrap a BMP image in an WMF. + the BMP image to be wrapped + the wrapped BMP + + + + Writes the specified value to the specified outputstream as a word. + outputstream to write the word to + value to be written + + + + Writes the specified value to the specified outputstream as a dword. + outputstream to write the dword to + value to be written + + + + A Meta Font. + + + Creates a MetaFont instance. + + + Initializes the MetaFont instance. + InputMeta containing the WMF data + + + + Returns the Font. + the font + + + + Returns the encoding used in the MetaFont. + the font encoding + + + Returns the angle of the MetaFont. + the angle + + + Returns a boolean value indicating if the font is underlined or not. + true if the font is underlined + + + Returns a boolean value indicating if a font has a strikeout. + true if the font set strikeout + + + Returns the font size. + the MetaState + font size + + + A Pen object of the WMF format. + A Pen object of the WMF format. Holds the color, style and width information of the pen. + + + Creates a MetaPen object. + + + Initializes a MetaPen object. + the InputMeta object that holds the inputstream of the WMF image + + + + Get the style of the MetaPen. + style of the pen + + + Get the width of the MetaPen. + width of the pen + + + Get the color of the MetaPen. + color of the pen + + + Class to keep the state. + + + Stack of saved states. + + + List of MetaObjects. + + + Current Point. + + + Current Pen. + + + Current Brush. + + + Current Font. + + + The current background color. + The current background color. Default value is DeviceRgb#WHITE. + + + Current text color. + Current text color. Default value is DeviceRgb#BLACK. + + + The current background mode. + The current background mode. Default value is OPAQUE. + + + Current polygon fill mode. + Current polygon fill mode. Default value is ALTERNATE. + + + Curent line join. + Curent line join. Default value is 1. + + + Current text alignment. + + + Current offset for Wx. + + + Current offset for Wy. + + + Current extent for Wx. + + + Current extent for Wy. + + + Current x value for scaling. + + + Current y value for scaling. + + + Creates new MetaState + + + Clones a new MetaState from the specified MetaState. + the state to clone + + + Sets every field of this MetaState to the values of the fields of the specified MetaState. + state to copy + + + Add a MetaObject to the State. + MetaObject to be added + + + Select the MetaObject at the specified index and prepare the PdfCanvas. + position of the MetaObject + PdfCanvas to prepare + + + Deletes the MetaObject at the specified index. + index of the MetaObject to delete + + + Saves the state of this MetaState object. + PdfCanvas object on which saveState() will be called + + + Restores the state to the next state on the saved states stack. + index of the state to be restored + PdfCanvas object on which restoreState() will be called + + + Restres the state of the specified PdfCanvas object for as many times as there are saved states on the stack. + + PdfCanvas object + + + Transform the specified value. + the value to transform + the transformed value + + + Transform the specified value. + the value to transform + transformed value + + + Sets the x value for scaling. + x value for scaling + + + Sets the y value for scaling. + y value for scaling + + + Sets the Wx offset value. + Wx offset value + + + Sets the Wy offset value. + Wy offset value + + + Sets the Wx extent value. + Wx extent value + + + Sets the Wy extent value. + Wy extent value + + + Transforms the specified angle. + + Transforms the specified angle. If scalingY is less than 0, the angle is multiplied by -1. If scalingX is less + than 0, the angle is subtracted from Math.PI. + + the angle to transform + the transformed angle + + + Sets the current Point to the specified Point. + Point to set + + + Returns the current Point. + current Point + + + Returns the current MetaBrush object. + current MetaBrush + + + Returns the current MetaPen object. + current MetaPen + + + Returns the current MetaFont object. + current MetaFont + + + Getter for property currentBackgroundColor. + Value of property currentBackgroundColor. + + + Setter for property currentBackgroundColor. + New value of property currentBackgroundColor. + + + Getter for property currentTextColor. + Value of property currentTextColor. + + + Setter for property currentTextColor. + New value of property currentTextColor. + + + Getter for property backgroundMode. + Value of property backgroundMode. + + + Setter for property backgroundMode. + New value of property backgroundMode. + + + Getter for property textAlign. + Value of property textAlign. + + + Setter for property textAlign. + New value of property textAlign. + + + Getter for property polyFillMode. + Value of property polyFillMode. + + + Setter for property polyFillMode. + New value of property polyFillMode. + + + + Sets the line join style to + + if lineJoin isn't 0. + + PdfCanvas to set the line join style + + + + Sets the line join style to + + if lineJoin is 0. + + PdfCanvas to set the line join style + + + Returns true if lineJoin is 0. + true if lineJoin is 0 + + + Image implementation for WMF, Windows Metafile. + + + Creates a WmfImage from a file. + pah to the file + + + + Creates a WmfImage from a URL. + URL to the file + + + Creates a WmfImage from a byte[]. + the image bytes + + + Helper class for the WmfImage implementation. + + Helper class for the WmfImage implementation. Assists in the creation of a + + . + + + + Scales the WMF font size. + Scales the WMF font size. The default value is 0.86. + + + Creates a helper instance. + + the + + object + + + + This method checks if the image is a valid WMF and processes some parameters. + + + Create a PdfXObject based on the WMF image. + + Create a PdfXObject based on the WMF image. The PdfXObject will have the dimensions of the + WMF image. + + PdfDocument to add the PdfXObject to + PdfXObject based on the WMF image + + + A type of initial view + + + A type of initial view + + + A type of initial view + + + Constructs a PDF Collection. + + + Sets the Collection schema dictionary. + an overview of the collection fields + + + + + Identifies the document that will be initially presented + in the user interface. + + a string that identifies an entry in the EmbeddedFiles name tree + + + + Sets the initial view. + + + + + Sets the Collection sort dictionary. + + + + + A possible type of collection field. + + + A possible type of collection field. + + + A possible type of collection field. + + + A possible type of collection field. + + + A possible type of collection field. + + + A possible type of collection field. + + + A possible type of collection field. + + + A possible type of collection field. + + + Creates a PdfCollectionField. + the field name + the field subtype + + + The relative order of the field name. + The relative order of the field name. Fields are sorted in ascending order. + a number indicating the order of the field + + + + Sets the initial visibility of the field. + + + + + Indication if the field value should be editable in the viewer. + + + + + Sets the value of the collection item. + + + + + + Sets the value of the collection item. + + + + Sets the value of the collection item. + + + + Adds a prefix for the Collection item. + + Adds a prefix for the Collection item. + You can only use this method after you have set the value of the item. + + + + + + + Creates a Collection Schema dictionary. + + + Adds a Collection field to the Schema. + the name of the collection field + a Collection Field + + + + Constructs a PDF Collection Sort Dictionary. + the key of the field that will be used to sort entries + + + Constructs a PDF Collection Sort Dictionary. + the keys of the fields that will be used to sort entries + + + Defines the sort order of the field (ascending or descending). + true is the default, use false for descending order + + + + Defines the sort order of the field (ascending or descending). + an array with every element corresponding with a name of a field. + + + + Represents the most common properties of color spaces. + + + + To manually flush a + PdfObject + behind this wrapper, you have to ensure + that this object is added to the document, i.e. it has an indirect reference. + Basically this means that before flushing you need to explicitly call + + . + For example: wrapperInstance.makeIndirect(document).flush(); + Note that not every wrapper require this, only those that have such warning in documentation. + + + + + To manually flush a + PdfObject + behind this wrapper, you have to ensure + that this object is added to the document, i.e. it has an indirect reference. + Basically this means that before flushing you need to explicitly call + + . + For example: wrapperInstance.makeIndirect(document).flush(); + Note that not every wrapper require this, only those that have such warning in documentation. + + + + The abstract PdfShading class that represents the Shading Dictionary PDF object. + + + + Creates the + + object from the existing + + with corresponding type. + + + + + from which the + + object will be created. + + + Created + + object. + + + + Gets the shading type. + + int value of + + . + + + + Gets the color space in which colour values shall be expressed. + + + + Color space + + + + + Gets the function PdfObject that represents color transitions + across the shading geometry. + + + + + Function + + + + + Sets the function that represents color transitions + across the shading geometry as one object. + + + The + + to set. + + + + + Sets the function object that represents color transitions + across the shading geometry as an array of functions. + + + The array of + + to be set. + + + + + To manually flush a + PdfObject + behind this wrapper, you have to ensure + that this object is added to the document, i.e. it has an indirect reference. + Basically this means that before flushing you need to explicitly call + + . + For example: wrapperInstance.makeIndirect(document).flush(); + Note that not every wrapper require this, only those that have such warning in documentation. + + + + + constants of shading type + ISO-320001 Table 78 + + + + + The int value of function-based shading type + + + The int value of axial shading type + + + The int value of radial shading type + + + The int value of free-form Gouraud-shaded triangle mesh shading type + + + The int value of lattice-form Gouraud-shaded triangle mesh shading type + + + The int value of coons patch meshes shading type + + + The int value of tensor-product patch meshes shading type + + + + The class that extends + + class and is in charge of Shading Dictionary with function-based type, + that defines color at every point in the domain by a specified mathematical function. + + + + + Creates the new instance of the class from the existing + + object. + + + + + from which the instance is created. + + + + Creates the new instance of the class. + + the + + object in which colour values shall be expressed. + + + the + + , that is used to calculate color transitions. + + + + Creates the new instance of the class. + + the + + , that represents color space in which colour values shall be expressed. + + + the + + , that is used to calculate color transitions. + + + + + Gets the + + domain rectangle object that establishes an internal coordinate space + for the shading that is independent of the target coordinate space in which it shall be painted. + + + + + domain rectangle. + + + + + Sets the + + domain rectangle object that establishes an internal coordinate space + for the shading that is independent of the target coordinate space in which it shall be painted. + + the Xmin coordinate of rectangle. + the Xmax coordinate of rectangle. + the Ymin coordinate of rectangle. + the Ymax coordinate of rectangle. + + + + Sets the + + domain rectangle object that establishes an internal coordinate space + for the shading that is independent of the target coordinate space in which it shall be painted. + + + the + + domain rectangle object to be set. + + + + + Gets the array of floats that represents the transformation matrix that maps the domain rectangle + into a corresponding figure in the target coordinate space. + + + the + float[] + of transformation matrix (identical matrix by default). + + + + + Sets the array of floats that represents the transformation matrix that maps the domain rectangle + into a corresponding figure in the target coordinate space. + + + the + float[] + of transformation matrix to be set. + + + + + Sets the array of floats that represents the transformation matrix that maps the domain rectangle + into a corresponding figure in the target coordinate space. + + + the + + transformation matrix object to be set. + + + + + The class that extends + + class and is in charge of Shading Dictionary with axial type, + that define a colour blend that varies along a linear axis between two endpoints + and extends indefinitely perpendicular to that axis. + + + + + Creates the new instance of the class from the existing + + object. + + + + + from which the instance is created. + + + + Creates the new instance of the class. + + the + + object in which colour values shall be expressed. + The special Pattern space isn't excepted. + + the start coordinate of X axis expressed in the shading's target coordinate space. + the start coordinate of Y axis expressed in the shading's target coordinate space. + + the + float[] + that represents the color in the start point. + + the end coordinate of X axis expressed in the shading's target coordinate space. + the end coordinate of Y axis expressed in the shading's target coordinate space. + + the + float[] + that represents the color in the end point. + + + + Creates the new instance of the class. + + the + + object in which colour values shall be expressed. + The special Pattern space isn't excepted. + + the start coordinate of X axis expressed in the shading's target coordinate space. + the start coordinate of Y axis expressed in the shading's target coordinate space. + + the + float[] + that represents the color in the start point. + + the end coordinate of X axis expressed in the shading's target coordinate space. + the end coordinate of Y axis expressed in the shading's target coordinate space. + + the + float[] + that represents the color in the end point. + + + the array of two booleans that specified whether to extend the shading + beyond the starting and ending points of the axis, respectively. + + + + Creates the new instance of the class. + + the + + object in which colour values shall be expressed. + The special Pattern space isn't excepted. + + + the + + of four number four numbers [x0 y0 x1 y1] that specified the starting + and the endings coordinates of thew axis, expressed in the shading's target coordinate space. + + + the + + object, that is used to calculate color transitions. + + + + + Gets the Coords object - a + + of four numbers [x0 y0 x1 y1] that specified the starting + and the endings coordinates of thew axis, expressed in the shading's target coordinate space. + + + the + + Coords object. + + + + Sets the Choords object with the four params expressed in the shading's target coordinate space. + the start coordinate of X axis to be set. + the start coordinate of Y axis to be set. + the end coordinate of X axis to be set. + the end coordinate of Y axis to be set. + + + + Sets the Choords object with the + + of four numbers [x0 y0 x1 y1], + that specified the starting and the endings coordinates of thew axis, + expressed in the shading's target coordinate space. + + + the Chords + + to be set. + + + + + Gets the array of two + float + [t0, t1] that represent the limiting values of a parametric + variable t, that becomes an input of color function(s). + + + + float[] + of Domain object ([0.0 1.0] by default) + + + + + Sets the Domain with the array of two + float + [t0, t1] that represent the limiting values + of a parametric variable t, that becomes an input of color function(s). + + first limit of variable t + second limit of variable t + + + + Gets the array of two + boolean + that specified whether to extend the shading + beyond the starting and ending points of the axis, respectively. + + + + boolean[] + of Extended object ([false false] by default) + + + + + Sets the Extend object with the two + boolean + value. + + if true will extend shading beyond the starting point of Coords + if true will extend shading beyond the ending point of Coords + + + + The class that extends + + class and is in charge of Shading Dictionary with radial type, + that define a colour blend that varies between two circles. + This type of shading shall not be used with an Indexed colour space + + + + + Creates the new instance of the class from the existing + + object. + + + - + + from which the instance is created. + + + + Creates the new instance of the class. + + the + + object in which colour values shall be expressed. + The Indexed color space isn't excepted. + + the X coordinate of starting circle's centre, expressed in in the shading’s target coordinate space. + + the Y coordinate of starting circle's centre, expressed in in the shading’s target coordinate space. + + + the radius of starting circle's centre, should be greater or equal to 0. + If 0 then starting circle is treated as point. + If both radii are 0, nothing shall be painted. + + + the + float[] + that represents the color in the start circle. + + the X coordinate of ending circle's centre, expressed in in the shading’s target coordinate space. + + the Y coordinate of ending circle's centre, expressed in in the shading’s target coordinate space. + + + the radius of ending circle's centre, should be greater or equal to 0. + If 0 then ending circle is treated as point. + If both radii are 0, nothing shall be painted. + + + the + float[] + that represents the color in the end circle. + + + + Creates the new instance of the class. + + the + + object in which colour values shall be expressed. + The Indexed color space isn't excepted. + + the X coordinate of starting circle's centre, expressed in in the shading’s target coordinate space. + + the Y coordinate of starting circle's centre, expressed in in the shading’s target coordinate space. + + + the radius of starting circle's centre, should be greater or equal to 0. + If 0 then starting circle is treated as point. + If both radii are 0, nothing shall be painted. + + + the + float[] + that represents the color in the start circle. + + the X coordinate of ending circle's centre, expressed in in the shading’s target coordinate space. + + the Y coordinate of ending circle's centre, expressed in in the shading’s target coordinate space. + + + the radius of ending circle's centre, should be greater or equal to 0. + If 0 then ending circle is treated as point. + If both radii are 0, nothing shall be painted. + + + the + float[] + that represents the color in the end circle. + + + the array of two + boolean + that specified whether to extend the shading + beyond the starting and ending points of the axis, respectively. + + + + Creates the new instance of the class. + + the + + object in which colour values shall be expressed. + The Indexed color space isn't excepted. + + + the + + of of six numbers [x0 y0 r0 x1 y1 r1], + specifying the centres and radii of the starting and ending circles, + expressed in the shading’s target coordinate space. + The radii r0 and r1 shall both be greater than or equal to 0. + If one radius is 0, the corresponding circle shall be treated as a point; + if both are 0, nothing shall be painted. + + + the + + object, that is used to calculate color transitions. + + + + + Gets the coords + + object - an array of six numbers [x0 y0 r0 x1 y1 r1], + specifying the centres and radii of the starting and ending circles, + expressed in the shading’s target coordinate space. + The radii r0 and r1 shall both be greater than or equal to 0. + If one radius is 0, the corresponding circle shall be treated as a point; + if both are 0, nothing shall be painted. + + + the + + coords object. + + + + Sets the coords object. + the X coordinate of starting circle's centre, expressed in in the shading’s target coordinate space. + + the Y coordinate of starting circle's centre, expressed in in the shading’s target coordinate space. + + + the radius of starting circle's centre, should be greater or equal to 0. + If 0 then starting circle is treated as point. + If both radii are 0, nothing shall be painted. + + the X coordinate of ending circle's centre, expressed in in the shading’s target coordinate space. + + the Y coordinate of ending circle's centre, expressed in in the shading’s target coordinate space. + + + the radius of ending circle's centre, should be greater or equal to 0. + If 0 then ending circle is treated as point. + If both radii are 0, nothing shall be painted. + + + + + Sets the coords + + object - an array of six numbers [x0 y0 r0 x1 y1 r1], + specifying the centres and radii of the starting and ending circles, + expressed in the shading’s target coordinate space. + The radii r0 and r1 shall both be greater than or equal to 0. + If one radius is 0, the corresponding circle shall be treated as a point; + if both are 0, nothing shall be painted. + + + - + + choords object to be set. + + + + + Gets the array of two + float + [t0, t1] that represent the limiting values of a parametric + variable t, that becomes an input of color function(s). + + + + float[] + of Domain object ([0.0 1.0] by default) + + + + + Sets the Domain with the array of two + float + [t0, t1] that represent the limiting values + of a parametric variable t, that becomes an input of color function(s). + + first limit of variable t + second limit of variable t + + + + Gets the array of two + boolean + that specified whether to extend the shading + beyond the starting and ending circles of the axis, respectively. + + + + boolean[] + of Extended object ([false false] by default) + + + + + Sets the Extend object with the two + boolean + value. + + if true will extend shading beyond the starting circle of Coords. + if true will extend shading beyond the ending circle of Coords. + + + + The class that extends + + class and is in charge of Shading Dictionary with + free-form Gouraud-shaded triangle mesh type. + The area to be shaded is defined by a path composed entirely of triangles. + The colour at each vertex of the triangles is specified, + and a technique known as Gouraud interpolation is used to colour the interiors. + The object shall be represented as stream containing a sequence of vertex data. + Each vertex is specified by the following values, in the order shown: + f x y c1 … cn where: + f - the vertex’s edge flag, that determines the vertex is connected to other vertices of the triangle mesh. + For full description + ISO-320001 Paragph 8.7.4.5.5 + x, y - vertex’s horizontal and vertical coordinates, expressed in the shading’s target coordinate space. + c1…cn - vertex’s colour components. + If the shading dictionary includes a Function entry, only a single parametric value, t, + shall be specified for each vertex in place of the colour components c1…cn. + + + + + Creates the new instance of the class from the existing + + object. + + + + + from which the instance is created. + + + + Creates the new instance of the class. + + the + + object in which colour values shall be expressed. + The special Pattern space isn't excepted. + + + the number of bits used to represent each vertex coordinate. + The value shall be 1, 2, 4, 8, 12, 16, 24, or 32. + + + the number of bits used to represent each colour component. + The value shall be 1, 2, 4, 8, 12, or 16. + + + the number of bits used to represent the edge flag for each vertex. + The value of BitsPerFlag shall be 2, 4, or 8, + but only the least significant 2 bits in each flag value shall be used. + The value for the edge flag shall be 0, 1, or 2. + + + the + int[] + of numbers specifying how to map vertex coordinates and colour components + into the appropriate ranges of values. The ranges shall be specified as follows: + [x_min x_max y_min y_max c1_min c1_max … cn_min cn_max]. + Only one pair of color values shall be specified if a Function entry is present. + + + + Creates the new instance of the class. + + the + + object in which colour values shall be expressed. + The special Pattern space isn't excepted. + + + the number of bits used to represent each vertex coordinate. + The value shall be 1, 2, 4, 8, 12, 16, 24, or 32. + + + the number of bits used to represent each colour component. + The value shall be 1, 2, 4, 8, 12, or 16. + + + the number of bits used to represent the edge flag for each vertex. + The value of BitsPerFlag shall be 2, 4, or 8, + but only the least significant 2 bits in each flag value shall be used. + The value for the edge flag shall be 0, 1, or 2. + + + the + + of numbers specifying how to map vertex coordinates and colour components + into the appropriate ranges of values. The ranges shall be specified as follows: + [x_min x_max y_min y_max c1_min c1_max … cn_min cn_max]. + Only one pair of color values shall be specified if a Function entry is present. + + + + Gets the number of bits used to represent each vertex coordinate. + the number of bits. Can be 1, 2, 4, 8, 12, 16, 24, or 32. + + + Sets the number of bits used to represent each vertex coordinate. + the number of bits to be set. Shall be 1, 2, 4, 8, 12, 16, 24, or 32. + + + Gets the number of bits used to represent each colour component. + the number of bits. Can be 1, 2, 4, 8, 12, or 16. + + + Sets the number of bits used to represent each colour component. + the number of bits to be set. Shall be 1, 2, 4, 8, 12, or 16. + + + Gets the number of bits used to represent the edge flag for each vertex. + + Gets the number of bits used to represent the edge flag for each vertex. + But only the least significant 2 bits in each flag value shall be used. + The valid flag values are 0, 1 or 2. + + the number of bits. Can be 2, 4 or 8. + + + Sets the number of bits used to represent the edge flag for each vertex. + + Sets the number of bits used to represent the edge flag for each vertex. + But only the least significant 2 bits in each flag value shall be used. + The valid flag values are 0, 1 or 2. + + the number of bits to be set. Shall be 2, 4 or 8. + + + + Gets the + + of numbers specifying how to map vertex coordinates and colour components + into the appropriate ranges of values. The ranges shall be specified as follows: + [x_min x_max y_min y_max c1_min c1_max … cn_min cn_max]. + Only one pair of color values shall be specified if a Function entry is present. + + + the + + Decode object. + + + + + Sets the + float[] + of numbers specifying how to map vertex coordinates and colour components + into the appropriate ranges of values. The ranges shall be specified as follows: + [x_min x_max y_min y_max c1_min c1_max … cn_min cn_max]. + Only one pair of color values shall be specified if a Function entry is present. + + + the + float[] + of Decode object to set. + + + + + Sets the + + of numbers specifying how to map vertex coordinates and colour components + into the appropriate ranges of values. The ranges shall be specified as follows: + [x_min x_max y_min y_max c1_min c1_max … cn_min cn_max]. + Only one pair of color values shall be specified if a Function entry is present. + + + the + + Decode object to set. + + + + + The class that extends + + class and is in charge of Shading Dictionary with + lattice-form Gouraud-shaded triangle mesh type. + This type is similar to + + but instead of using free-form geometry, + the vertices are arranged in a pseudorectangular lattice, + which is topologically equivalent to a rectangular grid. + The vertices are organized into rows, which need not be geometrically linear. + The verticals data in stream is similar to + + , + except there is no edge flag. + + + + + Creates the new instance of the class from the existing + + object. + + + + + from which the instance is created. + + + + Creates the new instance of the class. + + the + + object in which colour values shall be expressed. + The special Pattern space isn't excepted. + + + the number of bits used to represent each vertex coordinate. + The value shall be 1, 2, 4, 8, 12, 16, 24, or 32. + + + the number of bits used to represent each colour component. + The value shall be 1, 2, 4, 8, 12, or 16. + + + the number of vertices in each row of the lattice (shall be > 1). + The number of rows need not be specified. + + + the + int[] + of numbers specifying how to map vertex coordinates and colour components + into the appropriate ranges of values. The ranges shall be specified as follows: + [x_min x_max y_min y_max c1_min c1_max … cn_min cn_max]. + Only one pair of color values shall be specified if a Function entry is present. + + + + Creates the new instance of the class. + + the + + object in which colour values shall be expressed. + The special Pattern space isn't excepted. + + + the number of bits used to represent each vertex coordinate. + The value shall be 1, 2, 4, 8, 12, 16, 24, or 32. + + + the number of bits used to represent each colour component. + The value shall be 1, 2, 4, 8, 12, or 16. + + + the number of vertices in each row of the lattice (shall be > 1). + The number of rows need not be specified. + + + the + + of numbers specifying how to map vertex coordinates and colour components + into the appropriate ranges of values. The ranges shall be specified as follows: + [x_min x_max y_min y_max c1_min c1_max … cn_min cn_max]. + Only one pair of color values shall be specified if a Function entry is present. + + + + Gets the number of bits used to represent each vertex coordinate. + the number of bits. Can be 1, 2, 4, 8, 12, 16, 24, or 32. + + + Sets the number of bits used to represent each vertex coordinate. + the number of bits to be set. Shall be 1, 2, 4, 8, 12, 16, 24, or 32. + + + Gets the number of bits used to represent each colour component. + the number of bits. Can be 1, 2, 4, 8, 12, or 16. + + + Sets the number of bits used to represent each colour component. + the number of bits to be set. Shall be 1, 2, 4, 8, 12, or 16. + + + Gets the number of vertices in each row of the lattice. + the number of vertices. Can only be greater than 1. + + + Sets the number of vertices in each row of the lattice. + + Sets the number of vertices in each row of the lattice. + The number of rows need not be specified. + + the number of vertices to be set. Shall be greater than 1. + + + + Gets the + + of numbers specifying how to map vertex coordinates and colour components + into the appropriate ranges of values. The ranges shall be specified as follows: + [x_min x_max y_min y_max c1_min c1_max … cn_min cn_max]. + Only one pair of color values shall be specified if a Function entry is present. + + + the + + Decode object. + + + + + Sets the + float[] + of numbers specifying how to map vertex coordinates and colour components + into the appropriate ranges of values. The ranges shall be specified as follows: + [x_min x_max y_min y_max c1_min c1_max … cn_min cn_max]. + Only one pair of color values shall be specified if a Function entry is present. + + + the + float[] + of Decode object to set. + + + + + Sets the + + of numbers specifying how to map vertex coordinates and colour components + into the appropriate ranges of values. The ranges shall be specified as follows: + [x_min x_max y_min y_max c1_min c1_max … cn_min cn_max]. + Only one pair of color values shall be specified if a Function entry is present. + + + the + + Decode object to set. + + + + + The class that extends + + class and is in charge of Shading Dictionary with + Coons Patch mesh type. + This type of shading is constructed from one or more colour patches, each bounded by four cubic Bézier curves. + Degenerate Bézier curves are allowed and are useful for certain graphical effects. + At least one complete patch shall be specified. + The shape of patch is defined by 12 control points. + Colours are specified for each corner of the unit square, + and bilinear interpolation is used to fill in colours over the entire unit square. + Coordinates are mapped from the unit square into a four-sided patch whose sides are not necessarily linear. + The mapping is continuous: the corners of the unit square map to corners of the patch + and the sides of the unit square map to sides of the patch. + For the format of data stream, that defines patches + ISO-320001 Table 85 + . + If the shading dictionary contains a Function entry, the colour data for each corner of a patch + shall be specified by a single parametric value t rather than by n separate colour components c1…cn. + + + + + Creates the new instance of the class from the existing + + object. + + + + + from which the instance is created. + + + + Creates the new instance of the class. + + the + + object in which colour values shall be expressed. + The special Pattern space isn't excepted. + + + the number of bits used to represent each vertex coordinate. + The value shall be 1, 2, 4, 8, 12, 16, 24, or 32. + + + the number of bits used to represent each colour component. + The value shall be 1, 2, 4, 8, 12, or 16. + + + the number of bits used to represent the edge flag for each vertex. + The value of BitsPerFlag shall be 2, 4, or 8, + but only the least significant 2 bits in each flag value shall be used. + The value for the edge flag shall be 0, 1, 2 or 3. + + + the + int[] + of numbers specifying how to map vertex coordinates and colour components + into the appropriate ranges of values. The ranges shall be specified as follows: + [x_min x_max y_min y_max c1_min c1_max … cn_min cn_max]. + Only one pair of color values shall be specified if a Function entry is present. + + + + Creates the new instance of the class. + + the + + object in which colour values shall be expressed. + The special Pattern space isn't excepted. + + + the number of bits used to represent each vertex coordinate. + The value shall be 1, 2, 4, 8, 12, 16, 24, or 32. + + + the number of bits used to represent each colour component. + The value shall be 1, 2, 4, 8, 12, or 16. + + + the number of bits used to represent the edge flag for each vertex. + The value of BitsPerFlag shall be 2, 4, or 8, + but only the least significant 2 bits in each flag value shall be used. + The value for the edge flag shall be 0, 1, 2 or 3. + + + the + + of numbers specifying how to map vertex coordinates and colour components + into the appropriate ranges of values. The ranges shall be specified as follows: + [x_min x_max y_min y_max c1_min c1_max … cn_min cn_max]. + Only one pair of color values shall be specified if a Function entry is present. + + + + Gets the number of bits used to represent each vertex coordinate. + the number of bits. Can be 1, 2, 4, 8, 12, 16, 24, or 32. + + + Sets the number of bits used to represent each vertex coordinate. + the number of bits to be set. Shall be 1, 2, 4, 8, 12, 16, 24, or 32. + + + Gets the number of bits used to represent each colour component. + the number of bits. Can be 1, 2, 4, 8, 12, or 16. + + + Sets the number of bits used to represent each colour component. + the number of bits to be set. Shall be 1, 2, 4, 8, 12, or 16. + + + Gets the number of bits used to represent the edge flag for each vertex. + + Gets the number of bits used to represent the edge flag for each vertex. + But only the least significant 2 bits in each flag value shall be used. + The valid flag values are 0, 1, 2 or 3. + + the number of bits. Can be 2, 4 or 8. + + + Sets the number of bits used to represent the edge flag for each vertex. + + Sets the number of bits used to represent the edge flag for each vertex. + But only the least significant 2 bits in each flag value shall be used. + The valid flag values are 0, 1, 2 or 3. + + the number of bits to be set. Shall be 2, 4 or 8. + + + + Gets the + + of numbers specifying how to map vertex coordinates and colour components + into the appropriate ranges of values. The ranges shall be specified as follows: + [x_min x_max y_min y_max c1_min c1_max … cn_min cn_max]. + Only one pair of color values shall be specified if a Function entry is present. + + + the + + Decode object. + + + + + Sets the + float[] + of numbers specifying how to map vertex coordinates and colour components + into the appropriate ranges of values. The ranges shall be specified as follows: + [x_min x_max y_min y_max c1_min c1_max … cn_min cn_max]. + Only one pair of color values shall be specified if a Function entry is present. + + + the + float[] + of Decode object to set. + + + + + Sets the + + of numbers specifying how to map vertex coordinates and colour components + into the appropriate ranges of values. The ranges shall be specified as follows: + [x_min x_max y_min y_max c1_min c1_max … cn_min cn_max]. + Only one pair of color values shall be specified if a Function entry is present. + + + the + + Decode object to set. + + + + + The class that extends + + class and is in charge of Shading Dictionary with + Tensor-Product Patch mesh type. + This type of shading is identical to + + , except that it's based on a + bicubic tensor-product patch defined by 16 control points. + For the format of data stream, that defines patches + ISO-320001 Table 86 + . + + + + + Creates the new instance of the class from the existing + + object. + + + + + from which the instance is created. + + + + Creates the new instance of the class. + + the + + object in which colour values shall be expressed. + The special Pattern space isn't excepted. + + + the number of bits used to represent each vertex coordinate. + The value shall be 1, 2, 4, 8, 12, 16, 24, or 32. + + + the number of bits used to represent each colour component. + The value shall be 1, 2, 4, 8, 12, or 16. + + + the number of bits used to represent the edge flag for each vertex. + The value of BitsPerFlag shall be 2, 4, or 8, + but only the least significant 2 bits in each flag value shall be used. + The value for the edge flag shall be 0, 1, 2 or 3. + + + the + int[] + of numbers specifying how to map vertex coordinates and colour components + into the appropriate ranges of values. The ranges shall be specified as follows: + [x_min x_max y_min y_max c1_min c1_max … cn_min cn_max]. + Only one pair of color values shall be specified if a Function entry is present. + + + + Creates the new instance of the class. + + the + + object in which colour values shall be expressed. + The special Pattern space isn't excepted. + + + the number of bits used to represent each vertex coordinate. + The value shall be 1, 2, 4, 8, 12, 16, 24, or 32. + + + the number of bits used to represent each colour component. + The value shall be 1, 2, 4, 8, 12, or 16. + + + the number of bits used to represent the edge flag for each vertex. + The value of BitsPerFlag shall be 2, 4, or 8, + but only the least significant 2 bits in each flag value shall be used. + The value for the edge flag shall be 0, 1, 2 or 3. + + + the + + of numbers specifying how to map vertex coordinates and colour components + into the appropriate ranges of values. The ranges shall be specified as follows: + [x_min x_max y_min y_max c1_min c1_max … cn_min cn_max]. + Only one pair of color values shall be specified if a Function entry is present. + + + + Gets the number of bits used to represent each vertex coordinate. + the number of bits. Can be 1, 2, 4, 8, 12, 16, 24, or 32. + + + Sets the number of bits used to represent each vertex coordinate. + the number of bits to be set. Shall be 1, 2, 4, 8, 12, 16, 24, or 32. + + + Gets the number of bits used to represent each colour component. + the number of bits. Can be 1, 2, 4, 8, 12, or 16. + + + Sets the number of bits used to represent each colour component. + the number of bits to be set. Shall be 1, 2, 4, 8, 12, or 16. + + + Gets the number of bits used to represent the edge flag for each vertex. + + Gets the number of bits used to represent the edge flag for each vertex. + But only the least significant 2 bits in each flag value shall be used. + The valid flag values are 0, 1, 2 or 3. + + the number of bits. Can be 2, 4 or 8. + + + Sets the number of bits used to represent the edge flag for each vertex. + + Sets the number of bits used to represent the edge flag for each vertex. + But only the least significant 2 bits in each flag value shall be used. + The valid flag values are 0, 1, 2 or 3. + + the number of bits to be set. Shall be 2, 4 or 8. + + + + Gets the + + of numbers specifying how to map vertex coordinates and colour components + into the appropriate ranges of values. The ranges shall be specified as follows: + [x_min x_max y_min y_max c1_min c1_max … cn_min cn_max]. + Only one pair of color values shall be specified if a Function entry is present. + + + the + + Decode object. + + + + + Sets the + float[] + of numbers specifying how to map vertex coordinates and colour components + into the appropriate ranges of values. The ranges shall be specified as follows: + [x_min x_max y_min y_max c1_min c1_max … cn_min cn_max]. + Only one pair of color values shall be specified if a Function entry is present. + + + the + float[] + of Decode object to set. + + + + + Sets the + + of numbers specifying how to map vertex coordinates and colour components + into the appropriate ranges of values. The ranges shall be specified as follows: + [x_min x_max y_min y_max c1_min c1_max … cn_min cn_max]. + Only one pair of color values shall be specified if a Function entry is present. + + + the + + Decode object to set. + + + + + To manually flush a + PdfObject + behind this wrapper, you have to ensure + that this object is added to the document, i.e. it has an indirect reference. + Basically this means that before flushing you need to explicitly call + + . + For example: wrapperInstance.makeIndirect(document).flush(); + Note that not every wrapper require this, only those that have such warning in documentation. + + + + + To manually flush a + PdfObject + behind this wrapper, you have to ensure + that this object is added to the document, i.e. it has an indirect reference. + Basically this means that before flushing you need to explicitly call + + . + For example: wrapperInstance.makeIndirect(document).flush(); + Note that not every wrapper require this, only those that have such warning in documentation. + + + + + Compression constants for . + + + + A possible compression level. + + + A possible compression level. + + + A possible compression level. + + + A possible compression level. + + + A possible compression level. + + + + Encryption constants for + + . + + + + Type of encryption. + + + Type of encryption. + + + Type of encryption. + + + Type of encryption. + + + Mask to separate the encryption type from the encryption mode. + + + Add this to the mode to keep the metadata in clear text. + + + Add this to the mode to keep encrypt only the embedded files. + + + The operation permitted when the document is opened with the user password. + + + The operation permitted when the document is opened with the user password. + + + The operation permitted when the document is opened with the user password. + + + The operation permitted when the document is opened with the user password. + + + The operation permitted when the document is opened with the user password. + + + The operation permitted when the document is opened with the user password. + + + The operation permitted when the document is opened with the user password. + + + The operation permitted when the document is opened with the user password. + + + StandardEncryption properties + + + PublicKeyEncryption properties + + + Sets the encryption options for the document. + + Sets the encryption options for the document. The userPassword and the + ownerPassword can be null or have zero length. In this case the ownerPassword + is replaced by a random string. The open permissions for the document can be + ALLOW_PRINTING, ALLOW_MODIFY_CONTENTS, ALLOW_COPY, ALLOW_MODIFY_ANNOTATIONS, + ALLOW_FILL_IN, ALLOW_SCREENREADERS, ALLOW_ASSEMBLY and ALLOW_DEGRADED_PRINTING. + The permissions can be combined by ORing them. + See + + . + + the user password. Can be null or empty + the owner password. Can be null or empty + the user permissions + + the type of encryption. It can be one of STANDARD_ENCRYPTION_40, STANDARD_ENCRYPTION_128, + ENCRYPTION_AES128 or ENCRYPTION_AES256 + Optionally DO_NOT_ENCRYPT_METADATA can be ored to output the metadata in cleartext + + + + Sets the certificate encryption options for the document. + + Sets the certificate encryption options for the document. An array of one or more public certificates + must be provided together with an array of the same size for the permissions for each certificate. + The open permissions for the document can be + AllowPrinting, AllowModifyContents, AllowCopy, AllowModifyAnnotations, + AllowFillIn, AllowScreenReaders, AllowAssembly and AllowDegradedPrinting. + The permissions can be combined by ORing them. + Optionally DO_NOT_ENCRYPT_METADATA can be ORed to output the metadata in cleartext + See + + . + + the public certificates to be used for the encryption + the user permissions for each of the certificates + + the type of encryption. It can be one of STANDARD_ENCRYPTION_40, STANDARD_ENCRYPTION_128, + ENCRYPTION_AES128 or ENCRYPTION_AES256. + + + + Graphics state parameter dictionary wrapper. + + Graphics state parameter dictionary wrapper. + See ISO-320001, 8.4.5 Graphics State Parameter Dictionaries. + + + + Standard separable blend mode. + Standard separable blend mode. See ISO-320001, table 136 + + + Standard separable blend mode. + Standard separable blend mode. See ISO-320001, table 136 + + + Standard separable blend mode. + Standard separable blend mode. See ISO-320001, table 136 + + + Standard separable blend mode. + Standard separable blend mode. See ISO-320001, table 136 + + + Standard separable blend mode. + Standard separable blend mode. See ISO-320001, table 136 + + + Standard separable blend mode. + Standard separable blend mode. See ISO-320001, table 136 + + + Standard separable blend mode. + Standard separable blend mode. See ISO-320001, table 136 + + + Standard separable blend mode. + Standard separable blend mode. See ISO-320001, table 136 + + + Standard separable blend mode. + Standard separable blend mode. See ISO-320001, table 136 + + + Standard separable blend mode. + Standard separable blend mode. See ISO-320001, table 136 + + + Standard separable blend mode. + Standard separable blend mode. See ISO-320001, table 136 + + + Standard separable blend mode. + Standard separable blend mode. See ISO-320001, table 136 + + + Standard nonseparable blend mode. + Standard nonseparable blend mode. See ISO-320001, table 137 + + + Standard nonseparable blend mode. + Standard nonseparable blend mode. See ISO-320001, table 137 + + + Standard nonseparable blend mode. + Standard nonseparable blend mode. See ISO-320001, table 137 + + + Standard nonseparable blend mode. + Standard nonseparable blend mode. See ISO-320001, table 137 + + + + Create instance of graphics state parameter dictionary wrapper + by existed + PdfDictionary + object + + instance of graphics state parameter dictionary + + + Create default instance of graphics state parameter dictionary + + + + Gets line width value, + LW + key. + + + a + float + value if exist, otherwise + + . + + + + + Sets line width value, + LW + key. + + + a + float + value. + + object itself. + + + + Gets line gap style value, + LC + key. + + 0 - butt cap, 1 - round cap, 2 - projecting square cap. + + + + Sets line gap style value, + LC + key. + + 0 - butt cap, 1 - round cap, 2 - projecting square cap. + object itself. + + + + Sets line gap style value, + LC + key. + + 0 - butt cap, 1 - round cap, 2 - projecting square cap. + object itself. + + + + Gets line join style value, + LJ + key. + + 0 - miter join (see also miter limit), 1 - round join, 2 - bevel join. + + + + Sets line join style value, + LJ + key. + + 0 - miter join (see also miter limit), 1 - round join, 2 - bevel join. + object itself. + + + + Gets miter limit value, + ML key + . See also line join style. + + + a + float + value if exist, otherwise + + . + + + + + Sets miter limit value, + ML key + . See also line join style. + + + a + float + value. + + object itself. + + + + Gets line dash pattern value, + D + key. + + + a + PdfArray + , that represents line dash pattern. + + + + + Sets line dash pattern value, + D + key. + + + a + PdfArray + , that represents line dash pattern. + + object itself. + + + + Gets rendering intent value, + RI + key. + Valid values are: + AbsoluteColorimetric + , + RelativeColorimetric + , + Saturation + , + Perceptual + . + + + a + PdfName + instance. + + + + + Sets rendering intent value, + RI + key. + + + a + PdfName + instance, Valid values are: + AbsoluteColorimetric + , + RelativeColorimetric + , + Saturation + , + Perceptual + . + + object itself. + + + + Get overprint flag value for stroking operations, + OP + key. + + + a + boolean + value if exist, otherwise + + . + + + + + Set overprint flag value for stroking operations, + OP + key. + + + + + , for applying overprint for stroking operations. + + object itself. + + + + Get overprint flag value for non-stroking operations, + op + key. + + + a + boolean + value if exist, otherwise + + . + + + + + Set overprint flag value for non-stroking operations, + op + key. + + + + + , for applying overprint for non-stroking operations. + + object itself. + + + + Get overprint control mode, + OPM + key. + + + an + int + value if exist, otherwise + + . + + + + + Set overprint control mode, + OPM + key. + + + an + int + value, see ISO-320001, 8.6.7 Overprint Control. + + object itself. + + + + Gets font and size, + Font + key. + + + a + + of the form + [font size] + , where + font + shall be an indirect reference to a font dictionary and + size + shall be a number expressed in text space units. + + + + + Sets font and size, + Font + key. + + + a + + of the form + [font size] + , where + + shall be an indirect reference to a font dictionary and + size + shall be a number expressed in text space units. + + object itself. + + + + Gets the black-generation function value, + BG + . + + + a + + , should be + + . + + + + + Sets the black-generation function value, + BG + . + + + a + + , shall be + + . + + object itself. + + + + Gets the black-generation function value or + Default + , + BG2 + key. + + + a + + value, should be either + + or + + . + + + + + Sets the black-generation function value or + Default + , + BG2 + key. + Note, if both + BG + and + BG2 + are present in the same graphics state parameter dictionary, + BG2 + takes precedence. + + + a + + value, shall be either + + or + Default + . + + object itself. + + + + Gets the undercolor-removal function, + UCR + key. + + + a + + , should be + + . + + + + + Sets the undercolor-removal function, + UCR + key. + + + a + + , shall be + + . + + object itself. + + + + Gets the undercolor-removal function value or + Default + , + UCR2 + key. + + + a + + value, should be either + + or + + . + + + + + Sets the undercolor-removal function value or + Default + , + UCR2 + key. + Note, if both + UCR + and + UCR2 + are present in the same graphics state parameter dictionary, + UCR2 + takes precedence. + + + a + + value, shall be either + + or + Default + . + + object itself. + + + + Gets the transfer function value, + TR + key. + + + a + + , should be either + + , + + or + + . + + + + + Sets the transfer function value, + TR + key. + + + a + + , shall be either + + , + + or + + . + + object itself. + + + + Gets the transfer function value or + Default + , + TR2 + key. + + + a + + , should be either + + , + + or + + . + + + + + Sets the transfer function value or + Default + , + TR2 + key. + Note, if both + TR + and + TR2 + are present in the same graphics state parameter dictionary, + TR2 + takes precedence. + + + a + + , shall be either + + , + + , + + or + Default + . + + object itself. + + + + Gets the halftone dictionary, stream or + Default + , + HT + key. + + + a + + , should be either + + , + + or + + . + + + + + Sets the halftone or + Default + , + HT + key. + + + a + + , shall be either + + , + + or + + . + + object itself. + + + + Gets + HTP + key. + + + + + Sets + HTP + key. + + + a + + . + + object itself. + + + + Gets the flatness tolerance value, + FL + key. + + + a + float + value if exist, otherwise + + . + + + + + Sets the flatness tolerance value, + FL + key. + + + a + float + value. + + object itself. + + + + Gets the smoothness tolerance value, + SM + key. + + + a + float + value if exist, otherwise + + . + + + + + Sets the smoothness tolerance value, + SM + key. + + + a + float + value. + + object itself. + + + + Gets value of an automatic stroke adjustment flag, + SA + key. + + + a + boolean + value if exist, otherwise + + . + + + + + Sets value of an automatic stroke adjustment flag, + SA + key. + + + a + boolean + value. + + object itself. + + + + Gets the current blend mode for the transparent imaging model, + BM + key. + + + a + + , should be either + + or + + . + + + + + Sets the current blend mode for the transparent imaging model, + BM + key. + + + a + + , shall be either + + or + + . + + object itself. + + + + Gets the current soft mask, + SMask + key. + + + a + + , should be either + + or + + . + + + + + Sets the current soft mask, + SMask + key. + + + a + + , shall be either + + or + + . + + object itself. + + + + Gets the current alpha constant, specifying the constant shape or constant opacity value + for stroking operations in the transparent imaging model, + CA + key. + + + a + float + value if exist, otherwise + + . + + + + + Sets the current alpha constant, specifying the constant shape or constant opacity value + for stroking operations in the transparent imaging model, + CA + key. + + + a + float + value. + + object itself. + + + + Gets the current alpha constant, specifying the constant shape or constant opacity value + for non-stroking operations in the transparent imaging model, + ca + key. + + + a + float + value if exist, otherwise + + . + + + + + Sets the current alpha constant, specifying the constant shape or constant opacity value + for non-stroking operations in the transparent imaging model, + ca + key. + + + a + float + value. + + object itself. + + + + Gets the alpha source flag (“alpha is shape”), specifying whether the current soft mask and alpha constant + shall be interpreted as shape values ( + + ) or opacity values ( + + ), + AIS + key. + + + a + boolean + value if exist, otherwise + + . + + + + + Sets the alpha source flag (“alpha is shape”), specifying whether the current soft mask and alpha constant + shall be interpreted as shape values ( + + ) or opacity values ( + + ), + AIS + key. + + + if + + - alpha as shape values, if + + — as opacity values. + + object itself. + + + + Gets the text knockout flag, which determine the behaviour of overlapping glyphs + within a text object in the transparent imaging model, + TK + key. + + + a + boolean + value if exist, otherwise + + . + + + + + Sets the text knockout flag, which determine the behaviour of overlapping glyphs + within a text object in the transparent imaging model, + TK + key. + + + + + if enabled. + + object itself. + + + Puts the value into Graphics state parameter dictionary and associates it with the specified key. + + + Puts the value into Graphics state parameter dictionary and associates it with the specified key. + If the key is already present, it will override the old value with the specified one. + + key to insert or to override + the value to associate with the specified key + object itself. + + + + To manually flush a + PdfObject + behind this wrapper, you have to ensure + that this object is added to the document, i.e. it has an indirect reference. + Basically this means that before flushing you need to explicitly call + + . + For example: wrapperInstance.makeIndirect(document).flush(); + Note that not every wrapper require this, only those that have such warning in documentation. + + + + + + + + + + Handles ASCII85Decode filter + + + + The main interface for creating a new + FilterHandler + + + + Decode the byte[] using the provided filterName. + the bytes that need to be decoded + PdfName of the filter + decode parameters + the dictionary of the stream. Can contain additional information needed to decode the byte[]. + + decoded byte array + + + Decodes the input bytes according to ASCII85. + the byte[] to be decoded + the decoded byte[] + + + Handles ASCIIHexDecode filter + + + Decodes a byte[] according to ASCII Hex encoding. + byte[] to be decoded + decoded byte[] + + + Handles CCITTFaxDecode filter + + + A filter that doesn't modify the stream at all + + + Encapsulates filter behavior for PDF streams. + + Encapsulates filter behavior for PDF streams. Classes generally interace with this + using the static getDefaultFilterHandlers() method, then obtain the desired + + via a lookup. + + + + + The default + + s used by iText + + + + + the default + + s used by iText + + + + Handles FlateDecode filter. + + + A helper to flateDecode. + the input data + + + + to read a correct stream. + + to try to read a corrupted stream. + + the decoded data + + + Input byte array. + PdfDictionary of decodeParams. + a byte array + + + Handles LZWDECODE filter + + + Decodes a byte[] according to the LZW encoding. + byte[] to be decoded + decoded byte[] + + + A class for performing LZW decoding. + + + Creates an LZWDecoder instance. + + + Method to decode LZW compressed data. + The compressed data. + Array to return the uncompressed data in. + + + Initialize the string table. + + + Write out the string just uncompressed. + content to write to the uncompressed data + + + Add a new string to the string table. + stored string + string to be appended to the stored string + + + Add a new string to the string table. + byte[] to store in the string table + + + Append newString to the end of oldString. + string be appended to + string that is to be appended to oldString + combined string + + + Attempt to get the next code. + + Attempt to get the next code. Exceptions are caught to make + this robust to cases wherein the EndOfInformation code has been + omitted from a strip. Examples of such cases have been observed + in practice. + + next code + + + Handles RunLengthDecode filter. + + + + To manually flush a + PdfObject + behind this wrapper, you have to ensure + that this object is added to the document, i.e. it has an indirect reference. + Basically this means that before flushing you need to explicitly call + + . + For example: wrapperInstance.makeIndirect(document).flush(); + Note that not every wrapper require this, only those that have such warning in documentation. + + + + + This interface defines logic which can be used to perform a custom copying + operation of a + + . + + + + Copies a page. + + Copies a page. + The new page must already be created before calling this, either in a new + + or in the same + + as the old page. + + the source page + the target page in a target document + + + Type of object to conform. + + + The interface generalizing the layer types (PdfLayer, PdfLayerMembership). + + + Gets the object representing the layer. + the object representing the layer + + + Gets the PdfIndirectReference that represents this layer. + the PdfIndirectReference that represents this layer + + + + An optional content group is a dictionary representing a collection of graphics + that can be made visible or invisible dynamically by users of viewer applications. + + + An optional content group is a dictionary representing a collection of graphics + that can be made visible or invisible dynamically by users of viewer applications. + In iText they are referenced as layers. +

    + To be able to be wrapped with this + + the + + must be indirect. +
    +
    + + Used for titling group of objects but not actually grouping them. + + + Creates a new layer by existing dictionary, which must be an indirect object. + the layer dictionary, must have an indirect reference. + + + Creates a new layer by its name and document. + the layer name + the PdfDocument which the layer belongs to + + + Creates a title layer. + + Creates a title layer. A title layer is not really a layer but a collection of layers + under the same title heading. + + the title text + the PdfDocument + the title layer + + + + Use this method to set a collection of optional content groups + whose states are intended to follow a "radio button" paradigm. + + + Use this method to set a collection of optional content groups + whose states are intended to follow a "radio button" paradigm. + That is, the state of at most one optional content group + in the array should be ON at a time: if one group is turned + ON, all others must be turned OFF. + + the PdfDocument + the radio group + + + Adds a child layer. + Adds a child layer. Nested layers can only have one parent. + the child layer + + + Gets the parent of this layer, be it a title layer, or a usual one. + the parent of the layer, or null if it has no parent + + + Sets the name of the layer to be displayed in the Layers panel. + the name of the layer. + + + Gets the initial visibility of the layer when the document is opened. + the initial visibility of the layer + + + Sets the initial visibility of the layer when the document is opened. + the initial visibility of the layer + + + Gets whether the layer is currently locked or not. + + Gets whether the layer is currently locked or not. If the layer is locked, + it will not be possible to change its state (on/off) in a viewer. + + true of the layer is currently locked, false otherwise. + + + Use this method to lock an optional content group. + + Use this method to lock an optional content group. + The state of a locked group cannot be changed through the user interface + of a viewer application. Producers can use this entry to prevent the visibility + of content that depends on these groups from being changed by users. + + + + Gets the layer visibility in Acrobat's layer panel + the layer visibility in Acrobat's layer panel + + + Sets the visibility of the layer in Acrobat's layer panel. + + Sets the visibility of the layer in Acrobat's layer panel. If false + the layer cannot be directly manipulated by the user. Note that any children layers will + also be absent from the panel. + + the visibility of the layer in Acrobat's layer panel + + + Gets a collection of current intents specified for this layer. + + Gets a collection of current intents specified for this layer. + The default value is PdfName.View, so it will be the only element of the + resultant colletion if no intents are currently specified. + + the collection of intents. + + + Sets the intents of the layer. + the list of intents. + + + + Used by the creating application to store application-specific + data associated with this optional content group. + + a text string specifying the application that created the group + + a string defining the type of content controlled by the group. Suggested + values include but are not limited to Artwork, for graphic-design or publishing + applications, and Technical, for technical designs such as building plans or + schematics + + + + + Specifies the language of the content controlled by this + optional content group + + + a language string which specifies a language and possibly a locale + (for example, es-MX represents Mexican Spanish) + + + used by viewer applications when there is a partial match but no exact + match between the system language and the language strings in all usage dictionaries + + + + + Specifies the recommended state for content in this + group when the document (or part of it) is saved by a viewer application to a format + that does not support optional content (for example, an earlier version of + PDF or a raster image format). + + the export state + + + + Specifies a range of magnifications at which the content + in this optional content group is best viewed. + + + the minimum recommended magnification factors at which the group + should be ON. A negative value will set the default to 0 + + + the maximum recommended magnification factor at which the group + should be ON. A negative value will set the largest possible magnification supported by the + viewer application + + + + + Specifies that the content in this group is intended for + use in printing + + + a name specifying the kind of content controlled by the group; + for example, Trapping, PrintersMarks and Watermark + + + indicates that the group should be + set to that state when the document is printed from a viewer application + + + + + Indicates that the group should be set to that state when the + document is opened in a viewer application. + + the view state + + + + Specifies one or more users for whom this optional content group + is primarily intended. + + a name that can be Ind (individual), Ttl (title), or Org (organization). + + one or more text strings representing + the name(s) of the individual, position or organization + + + + Indicates that the group contains a pagination artifact. + + one of the following names: "HF" (Header Footer), + "FG" (Foreground), "BG" (Background), or "L" (Logo). + + + + + Gets the indirect reference to the current layer object, + making it indirect first if necessary. + + the indirect reference to the object representing the layer + + + Gets the title of the layer if it is a title layer, or null if it is a usual layer. + + + Gets the list of the current child layers of the layer. + + Gets the list of the current child layers of the layer. + BE CAREFUL! Do not try to add a child layer using the resultant child list, + use #addChild method instead. + + the list of the current child layers, null if the layer has no children. + + + Creates a title layer without registering it in PdfOCProperties. + the title of the layer + the document this title layer belongs to + the created layer + + + Gets the /Usage dictionary, creating a new one if necessary. + the /Usage dictionary + + + + Content typically belongs to a single optional content group, + and is visible when the group is ON and invisible when it is OFF. + + + Content typically belongs to a single optional content group, + and is visible when the group is ON and invisible when it is OFF. To express more + complex visibility policies, content should not declare itself to belong to an optional + content group directly, but rather to an optional content membership dictionary + represented by this class. +

    + To be able to be wrapped with this + + the + + must be indirect. +
    +
    + + Creates a new, empty membership layer. + + + Creates a new PdfLayerMembership instance by its PdfDictionary, which must be an indirect object. + + the membership dictionary, must have an indirect reference. + + + Gets the collection of the layers this layer membership operates with. + + + Adds a new layer to the current layer membership. + the layer to be added + + + + Sets the visibility policy for content belonging to this + membership dictionary. + + + Sets the visibility policy for content belonging to this + membership dictionary. Possible values are AllOn, AnyOn, AnyOff and AllOff. + AllOn - Visible only if all of the entries are ON. + AnyOn - Visible if any of the entries are ON. + AnyOff - Visible if any of the entries are OFF. + AllOff - Visible only if all of the entries are OFF. + The default value is AnyOn. + + the visibility policy + + + + Gets the visibility policy for content belonging to this + optional content membership dictionary. + + + + + Sets the visibility expression for content belonging to this + membership dictionary. + + + A (nested) array of which the first value is /And, /Or, or /Not + followed by a series of indirect references to OCGs or other visibility + expressions. + + + + + Gets the visibility expression for content belonging to this + optional content membership dictionary. + + + + + This class represents /OCProperties entry if pdf catalog and manages + the layers of the pdf document. + + + This class represents /OCProperties entry if pdf catalog and manages + the layers of the pdf document. +

    + To be able to be wrapped with this + + the + + must be indirect. +
    +
    + + Creates a new PdfOCProperties instance. + the document the optional content belongs to + + + + Creates a new PdfOCProperties instance by the dictionary it represents, + the dictionary must be an indirect object. + + the dictionary of optional content properties, must have an indirect reference. + + + + + Use this method to set a collection of optional content groups + whose states are intended to follow a "radio button" paradigm. + + + Use this method to set a collection of optional content groups + whose states are intended to follow a "radio button" paradigm. + That is, the state of at most one optional content group + in the array should be ON at a time: if one group is turned + ON, all others must be turned OFF. + + the radio group + + + Fills the underlying PdfDictionary object with the current layers and their settings. + + Fills the underlying PdfDictionary object with the current layers and their settings. + Note that it completely regenerates the dictionary, so your direct changes to the dictionary + will not take any affect. + + the resultant dictionary + + + Gets the list of all the layers currently registered in the OCProperties. + + Gets the list of all the layers currently registered in the OCProperties. + Note that this is just a new list and modifications to it will not affect anything. + + + + This method registers a new layer in the OCProperties. + the new layer + + + + Gets the order of the layers in which they will be displayed in the layer view panel, + including nesting. + + + + Populates the /AS entry in the /D dictionary. + + + Reads the layers from the document to be able to modify them in the future. + + + Reads the /Order in the /D entry and initialized the parent-child hierarchy. + + + + An array specifying a visibility expression, used to compute visibility + of content based on a set of optional content groups. + + + + Constructs a new PdfVisibilityExpression instance by its raw PdfArray. + the array representing the visibility expression + + + Creates a visibility expression. + should be either PdfName#And, PdfName#Or, or PdfName#Not + + + Adds a new operand to the current visibility expression. + the layer operand to be added. + + + Adds a new opeand to the current visibility expression. + the PdfVisibilityExpression instance operand to be added + + + Enumeration of all the PDF/A conformance levels. + + + A representation of an array as described in the PDF specification. + + A representation of an array as described in the PDF specification. A PdfArray can contain any + subclass of + + . + + + + If object is flushed the indirect reference is kept here. + + + Indicate same special states of PdfIndirectObject or PdfObject like @see Free, @see Reading, @see Modified. + + + + Gets object type. + object type. + + + Flushes the object to the document. + + + Flushes the object to the document. + indicates whether object can be placed into object stream. + + + Gets the indirect reference associated with the object. + + Gets the indirect reference associated with the object. + The indirect reference is used when flushing object to the document. + + indirect reference. + + + Checks if object is indirect. + + Checks if object is indirect. +
    + Note: + Return value + + doesn't necessarily mean that indirect reference of this object + is not null at the moment. Object could be marked as indirect and + be transformed to indirect on flushing. +
    + E.g. all PdfStreams are transformed to indirect objects when they are written, but they don't always + have indirect references at any given moment. +
    + + returns + + if object is indirect or is to be indirect in the resultant document. + +
    + + Marks object to be saved as indirect. + a document the indirect reference will belong to. + object itself. + + + Marks object to be saved as indirect. + a document the indirect reference will belong to. + object itself. + + + Indicates is the object has been flushed or not. + true is object has been flushed, otherwise false. + + + Indicates is the object has been set as modified or not. + Indicates is the object has been set as modified or not. Useful for incremental updates (e.g. appendMode). + + true is object has been set as modified, otherwise false. + + + Creates clone of the object which belongs to the same document as original object. + + Creates clone of the object which belongs to the same document as original object. + New object shall not be used in other documents. + + cloned object. + + + Copies object to a specified document. + + Copies object to a specified document. +

    + NOTE: Works only for objects that are read from document opened in reading mode, otherwise an exception is thrown. +
    + document to copy object to. + copied object. +
    + + Copies object to a specified document. + + Copies object to a specified document. +

    + NOTE: Works only for objects that are read from document opened in reading mode, otherwise an exception is thrown. +
    + document to copy object to. + + indicates if to allow copy objects which already have been copied. + If object is associated with any indirect reference and allowDuplicating is false then already existing reference will be returned instead of copying object. + If allowDuplicating is true then object will be copied and new indirect reference will be assigned. + + copied object. +
    + + + Checks if this PdfObject is of the type + PdfNull. + + true or false + + + + Checks if this PdfObject is of the type + PdfBoolean. + + true or false + + + + Checks if this PdfObject is of the type + PdfNumber. + + true or false + + + + Checks if this PdfObject is of the type + PdfString. + + true or false + + + + Checks if this PdfObject is of the type + PdfName. + + true or false + + + + Checks if this PdfObject is of the type + PdfArray. + + true or false + + + + Checks if this PdfObject is of the type + PdfDictionary. + + true or false + + + + Checks if this PdfObject is of the type + PdfStream. + + true or false + + + + Checks if this PdfObject is of the type + PdfIndirectReference. + + + true if this is an indirect reference, + otherwise false + + + + + Checks if this PdfObject is of the type + PdfLiteral. + + + true if this is a literal, + otherwise false + + + + Creates new instance of object. + new instance of object. + + + Checks state of the flag of current object. + special flag to check + true if the state was set. + + + Sets special states of current object. + special flag of current object + + + Clear state of the flag of current object. + special flag state to clear + + + Copies object content from object 'from'. + object to copy content from. + document to copy object to. + + + + Create a new, empty PdfArray. + + + + Create a new PdfArray with the provided PdfObject as the first item in the + array. + + first item in the array + + + Create a new PdfArray. + Create a new PdfArray. The array is filled with the items of the provided PdfArray. + + PdfArray containing items that will added to this PdfArray + + + Create a new PdfArray. + + Create a new PdfArray. The array is filled with the four values of the Rectangle in the + follozing order: left, bottom, right, top. + + Rectangle whose 4 values will be added to the PdfArray + + + Create a new PdfArray. + Create a new PdfArray. The PdfObjects in the list will be added to the PdfArray. + + List of PdfObjects to be added to this PdfArray + + + + Create a new PdfArray filled with the values in the float[] as + + . + + values to be added to this PdfArray + + + + Create a new PdfArray filled with the values in the double[] as + + . + + values to be added to this PdfArray + + + + Create a new PdfArray filled with the values in the int[] as + + . + + values to be added to this PdfArray + + + + Create a new PdfArray filled with the values in the boolean[] as + + . + + values to be added to this PdfArray + + + Create a new PdfArray filled with a list of Strings. + + Create a new PdfArray filled with a list of Strings. The boolean value decides if the Strings + should be added as + + (true) or as + + (false). + + list of strings to be added to the list + indicates whether the strings should be added as PdfName (true) or as PdfString (false) + + + + + Returns an iterator over an array of PdfObject elements. +
    + NOTE: since 7.0.1 it returns collection of direct objects. + If you want to get {@link PdfIndirectReference} instances for the indirect objects value, + you shall use {@link #get(int, boolean)} method. +
    + an enumerator. +
    + + + Returns an iterator over an array of PdfObject elements. + + + + Adds the Collection of PdfObjects. + the Collection of PdfObjects to be added + + + + + Adds content of the + PdfArray + . + + + the + PdfArray + to be added + + + + + Gets the (direct) PdfObject at the specified index. + index of the PdfObject in the PdfArray + the PdfObject at the position in the PdfArray + + + Sets the PdfObject at the specified index in the PdfArray. + the position to set the PdfObject + PdfObject to be added + true if the operation changed the PdfArray + + + + Adds the specified PdfObject at the specified index. + Adds the specified PdfObject at the specified index. All objects after this index will be shifted by 1. + + position to insert the PdfObject + PdfObject to be added + + + + Removes the PdfObject at the specified index. + position of the PdfObject to be removed + true if the operation changes the PdfArray + + + + Gets the first index of the specified PdfObject. + PdfObject to find the index of + index of the PdfObject + + + + Returns a sublist of this PdfArray, starting at fromIndex (inclusive) and ending at toIndex (exclusive). + + the position of the first element in the sublist (inclusive) + + the position of the last element in the sublist (exclusive) + + List of PdfObjects + + + + Marks object to be saved as indirect. + a document the indirect reference will belong to. + object itself. + + + Marks object to be saved as indirect. + a document the indirect reference will belong to. + object itself. + + + Copies object to a specified document. + + Copies object to a specified document. + Works only for objects that are read from existing document, otherwise an exception is thrown. + + document to copy object to. + copied object. + + + Copies object to a specified document. + + Copies object to a specified document. + Works only for objects that are read from existing document, otherwise an exception is thrown. + + document to copy object to. + + indicates if to allow copy objects which already have been copied. + If object is associated with any indirect reference and allowDuplicating is false then already existing reference will be returned instead of copying object. + If allowDuplicating is true then object will be copied and new indirect reference will be assigned. + + copied object. + + + true is to extract direct object always. + + + Returns the element at the specified index as a PdfArray. + Returns the element at the specified index as a PdfArray. If the element isn't a PdfArray, null is returned. + + position of the element to be returned + the element at the index as a PdfArray + + + Returns the element at the specified index as a PdfDictionary. + Returns the element at the specified index as a PdfDictionary. If the element isn't a PdfDictionary, null is returned. + + position of the element to be returned + the element at the index as a PdfDictionary + + + Returns the element at the specified index as a PdfStream. + Returns the element at the specified index as a PdfStream. If the element isn't a PdfStream, null is returned. + + position of the element to be returned + the element at the index as a PdfStream + + + Returns the element at the specified index as a PdfNumber. + Returns the element at the specified index as a PdfNumber. If the element isn't a PdfNumber, null is returned. + + position of the element to be returned + the element at the index as a PdfNumber + + + Returns the element at the specified index as a PdfName. + Returns the element at the specified index as a PdfName. If the element isn't a PdfName, null is returned. + + position of the element to be returned + the element at the index as a PdfName + + + Returns the element at the specified index as a PdfString. + Returns the element at the specified index as a PdfString. If the element isn't a PdfString, null is returned. + + position of the element to be returned + the element at the index as a PdfString + + + Returns the element at the specified index as a PdfBoolean. + Returns the element at the specified index as a PdfBoolean. If the element isn't a PdfBoolean, null is returned. + + position of the element to be returned + the element at the index as a PdfBoolean + + + Returns the first four elements of this array as a PdfArray. + + Returns the first four elements of this array as a PdfArray. The first four values need to be + PdfNumbers, if not a PdfException will be thrown. + + Rectangle of the first four values + if one of the first values isn't a PdfNumber + + + + Release content of PdfArray. + + + Marks object to be saved as indirect. + a document the indirect reference will belong to. + object itself. + + + Marks object to be saved as indirect. + a document the indirect reference will belong to. + object itself. + + + Copies object to a specified document. + + Copies object to a specified document. + Works only for objects that are read from existing document, otherwise an exception is thrown. + + document to copy object to. + copied object. + + + Copies object to a specified document. + + Copies object to a specified document. + Works only for objects that are read from existing document, otherwise an exception is thrown. + + document to copy object to. + + indicates if to allow copy objects which already have been copied. + If object is associated with any indirect reference and allowDuplicating is false then already existing reference will be returned instead of copying object. + If allowDuplicating is true then object will be copied and new indirect reference will be assigned. + + copied object. + + + Use this method to get the Optional Content Properties Dictionary. + + Use this method to get the Optional Content Properties Dictionary. + Note that if you call this method, then the PdfDictionary with OCProperties will be + generated from PdfOCProperties object right before closing the PdfDocument, + so if you want to make low-level changes in Pdf structures themselves (PdfArray, PdfDictionary, etc), + then you should address directly those objects, e.g.: + + PdfCatalog pdfCatalog = pdfDoc.getCatalog(); + PdfDictionary ocProps = pdfCatalog.getAsDictionary(PdfName.OCProperties); + // manipulate with ocProps. + + Also note that this method is implicitly called when creating a new PdfLayer instance, + so you should either use hi-level logic of operating with layers, + or manipulate low-level Pdf objects by yourself. + + + true to create new /OCProperties entry in catalog if not exists, + false to return null if /OCProperties entry in catalog is not present. + + the Optional Content Properties Dictionary + + + PdfCatalog will be flushed in PdfDocument.close(). + PdfCatalog will be flushed in PdfDocument.close(). User mustn't flush PdfCatalog! + + + + This method sets a page layout of the document + + + + + + This method sets the document viewer preferences, specifying the way the document shall be displayed on the + screen + + + + + + This method gets Names tree from the catalog. + type of the tree (Dests, AP, EmbeddedFiles etc). + + returns + + + + + This method returns the NumberTree of Page Labels + + returns + + + + + An entry specifying the natural language, and optionally locale. + + An entry specifying the natural language, and optionally locale. Use this + to specify the Language attribute on a Tagged Pdf element. + For the content usage dictionary, use PdfName.Language + + + + + Sets collection dictionary that a conforming reader shall use to enhance the presentation of file attachments + stored in the PDF document. + + + + + + + True indicates that getOCProperties() was called, may have been modified, + and thus its dictionary needs to be reconstructed. + + + + this method return map containing all pages of the document with associated outlines. + + + + This methods adds new name to the Dests NameTree. + This methods adds new name to the Dests NameTree. It throws an exception, if the name already exists. + + Name of the destination. + + An object destination refers to. Must be an array or a dictionary with key /D and array. + See ISO 32000-1 12.3.2.3 for more info. + + + + This methods adds a new name to the specified NameTree. + This methods adds a new name to the specified NameTree. It throws an exception, if the name already exists. + + key in the name tree + value in the name tree + type of the tree (Dests, AP, EmbeddedFiles etc). + + + This method returns a complete outline tree of the whole document. + + if the flag is true, the method read the whole document and creates outline tree. + If false the method gets cached outline tree (if it was cached via calling getOutlines method before). + + + fully initialized + + object. + + + + Indicates if the catalog has any outlines + + + + , if there are outlines and + + otherwise. + + + + This flag determines if Outline tree of the document has been built via calling getOutlines method. + + This flag determines if Outline tree of the document has been built via calling getOutlines method. If this flag is false all outline operations will be ignored + + state of outline mode. + + + This method removes all outlines associated with a given page + + + + This method sets the root outline element in the catalog. + + + + This is the length of a dash. + + + This is the length of a gap. + + + This is the phase. + + + + + Constructs a + PdfDate + -object. + + + the date that has to be turned into a + PdfDate + >-object + + + + + Constructs a + PdfDate + -object, representing the current day and time. + + + + Gives the W3C format of the PdfDate. + a formatted date + + + + Gives the W3C format of the + PdfDate + . + + the date in the format D:YYYYMMDDHHmmSSOHH'mm' + a formatted date + + + + Converts a PDF string representing a date into a + DateTime + . + + the PDF string representing a date + + a + DateTime + representing the date + + + + + Beginning with BaseVersion 1.7, the extensions dictionary lets developers + designate that a given document contains extensions to PDF. + + + Beginning with BaseVersion 1.7, the extensions dictionary lets developers + designate that a given document contains extensions to PDF. The presence + of the extension dictionary in a document indicates that it may contain + developer-specific PDF properties that extend a particular base version + of the PDF specification. + The extensions dictionary enables developers to identify their own extensions + relative to a base version of PDF. Additionally, the convention identifies + extension levels relative to that base version. The intent of this dictionary + is to enable developers of PDF-producing applications to identify company-specific + specifications (such as this one) that PDF-consuming applications use to + interpret the extensions. + + + + An instance of this class for Adobe 1.7 Extension level 3. + + + An instance of this class for ETSI 1.7 Extension level 2. + + + An instance of this class for ETSI 1.7 Extension level 5. + + + The prefix used in the Extensions dictionary added to the Catalog. + + + The base version. + + + The extension level within the base version. + + + Creates a PdfDeveloperExtension object. + the prefix referring to the developer + the number of the base version + the extension level within the baseverion. + + + Gets the prefix name. + a PdfName + + + Gets the baseVersion name. + a PdfName + + + Gets the extension level within the baseVersion. + an integer + + + + Generations the developer extension dictionary corresponding + with the prefix. + + a PdfDictionary + + + A representation of a Dictionary as described by the PDF Specification. + + A representation of a Dictionary as described by the PDF Specification. A Dictionary is a mapping between keys + and values. Keys are + PdfNames + and the values are + PdfObjects + . Each key can only be associated with one value and + adding a new value to an existing key will override the previous value. A value of null should be ignored when + the PdfDocument is closed. + + + + Creates a new PdfDictionary instance. + + + Creates a new PdfDictionary instance. + + Creates a new PdfDictionary instance. This constructor inserts the content of the specified Map into this + PdfDictionary instance. + + Map containing values to be inserted into PdfDictionary + + + Creates a new PdfDictionary instance. + + Creates a new PdfDictionary instance. This constructor inserts the content of the specified Set into this + PdfDictionary instance. + + Set containing Map#Entries to be inserted into PdfDictionary + + + Creates a new PdfDictionary instance. + + Creates a new PdfDictionary instance. This constructor inserts the content of the specified PdfDictionary + into this PdfDictionary instance. + + PdfDictionary containing values to be inserted into PdfDictionary + + + Returns the number of key-value pairs in this PdfDictionary. + number of key-value pairs + + + Returns true if there are no key-value pairs in this PdfDictionary. + true if there are no key-value pairs in this PdfDictionary + + + Returns true if this PdfDictionary contains the specified key. + the key to check + true if key is present in the PdfDictionary + + + Returns true if this PdfDictionary contains the specified value. + the value to check + true if value is present in the PdfDictionary + + + Returns the value associated to this key. + the key of which the associated value needs to be returned + the value associated with this key + + + Returns the value associated to this key as a PdfArray. + Returns the value associated to this key as a PdfArray. If the value isn't a PdfArray, null is returned. + + the key of which the associated value needs to be returned + PdfArray associated with this key + + + Returns the value associated to this key as a PdfDictionary. + Returns the value associated to this key as a PdfDictionary. If the value isn't a PdfDictionary, null is returned. + + the key of which the associated value needs to be returned + PdfDictionary associated with this key + + + Returns the value associated to this key as a PdfStream. + Returns the value associated to this key as a PdfStream. If the value isn't a PdfStream, null is returned. + + the key of which the associated value needs to be returned + PdfStream associated with this key + + + Returns the value associated to this key as a PdfNumber. + Returns the value associated to this key as a PdfNumber. If the value isn't a PdfNumber, null is returned. + + the key of which the associated value needs to be returned + PdfNumber associated with this key + + + Returns the value associated to this key as a PdfName. + Returns the value associated to this key as a PdfName. If the value isn't a PdfName, null is returned. + + the key of which the associated value needs to be returned + PdfName associated with this key + + + Returns the value associated to this key as a PdfString. + Returns the value associated to this key as a PdfString. If the value isn't a PdfString, null is returned. + + the key of which the associated value needs to be returned + PdfString associated with this key + + + Returns the value associated to this key as a PdfBoolean. + Returns the value associated to this key as a PdfBoolean. If the value isn't a PdfBoolean, null is returned. + + the key of which the associated value needs to be returned + PdfBoolean associated with this key + + + Returns the value associated to this key as a Rectangle. + + Returns the value associated to this key as a Rectangle. If the value isn't a PdfArray of which the + firt four elements are PdfNumbers, null is returned. + + the key of which the associated value needs to be returned + PdfArray associated with this key + + + + Returns the value associated to this key as a Float. + Returns the value associated to this key as a Float. If the value isn't a Pdfnumber, null is returned. + + the key of which the associated value needs to be returned + Float associated with this key + + + Returns the value associated to this key as an Integer. + Returns the value associated to this key as an Integer. If the value isn't a Pdfnumber, null is returned. + + the key of which the associated value needs to be returned + Integer associated with this key + + + Returns the value associated to this key as a Boolean. + Returns the value associated to this key as a Boolean. If the value isn't a PdfBoolean, null is returned. + + the key of which the associated value needs to be returned + Boolean associated with this key + + + Inserts the value into this PdfDictionary and associates it with the specified key. + + Inserts the value into this PdfDictionary and associates it with the specified key. If the key is already + present in this PdfDictionary, this method will override the old value with the specified one. + + key to insert or to override + the value to associate with the specified key + the previous PdfObject associated with this key + + + Removes the specified key from this PdfDictionary. + key to be removed + the removed value associated with the specified key + + + Inserts all the key-value pairs into this PdfDictionary. + PdfDictionary holding the key-value pairs to be copied + + + Removes all key-value pairs from this PdfDictionary. + + + Returns all the keys of this PdfDictionary as a Set. + Set of keys + + + Returns all the values of this map in a Collection. + + if false, collection will contain + + instances + for the indirect objects in dictionary, otherwise it will contain collection of direct objects. + + a Collection holding all the values + + + Returns all the values of this map in a Collection. + + Returns all the values of this map in a Collection. +
    + NOTE: since 7.0.1 it returns collection of direct objects. + If you want to get + + instances for the indirect objects value, + you shall use + + method. +
    + a Collection holding all the values +
    + + Returns all the values of this map in a Collection. + + Returns all the values of this map in a Collection. In opposite to + + method, + this method will resolve all indirect references in the dictionary and return actual objects in collection. + + a Collection holding all the values + + + Returns a Set holding the key-value pairs as Map#Entry objects. + + Returns a Set holding the key-value pairs as Map#Entry objects. +
    + NOTE: since 7.0.1 it returns collection of direct objects. + If you want to get + + instances for the indirect objects value, + you shall use + + method. +
    + a Set of Map.Entry objects +
    + + Returns a Set holding the key-value pairs as Map#Entry objects. + + Returns a Set holding the key-value pairs as Map#Entry objects. In opposite to + + method, this method will resolve all indirect references in the dictionary and return actual objects as values of + entries in the collection. + + a Set of Map.Entry objects + + + Creates clones of the dictionary in the current document. + + Creates clones of the dictionary in the current document. + It's possible to pass a list of keys to exclude when cloning. + + list of objects to exclude when cloning dictionary. + cloned dictionary. + + + Marks object to be saved as indirect. + a document the indirect reference will belong to. + object itself. + + + Marks object to be saved as indirect. + a document the indirect reference will belong to. + object itself. + + + Copies object to a specified document. + + Copies object to a specified document. + Works only for objects that are read from existing document, otherwise an exception is thrown. + + document to copy object to. + copied object. + + + Copies object to a specified document. + + Copies object to a specified document. + Works only for objects that are read from existing document, otherwise an exception is thrown. + + document to copy object to. + + indicates if to allow copy objects which already have been copied. + If object is associated with any indirect reference and allowDuplicating is false then already existing reference will be returned instead of copying object. + If allowDuplicating is true then object will be copied and new indirect reference will be assigned. + + copied object. + + + Copies dictionary to specified document. + + Copies dictionary to specified document. + It's possible to pass a list of keys to exclude when copying. + + document to copy dictionary to. + list of objects to exclude when copying dictionary. + + + + + copied dictionary. + + + true is to extract direct object always. + + + This method merges different fields from two dictionaries into the current one + a dictionary whose fields should be merged into the current dictionary. + + + Release content of PdfDictionary. + + + Main enter point to work with PDF document. + + + Currently active page. + + + Default page size. + + Default page size. + New page by default will be created with this size. + + + + PdfWriter associated with the document. + + PdfWriter associated with the document. + Not null if document opened either in writing or stamping mode. + + + + PdfReader associated with the document. + + PdfReader associated with the document. + Not null if document is opened either in reading or stamping mode. + + + + XMP Metadata for the document. + + + Document catalog. + + + Document trailed. + + + Document info. + + + Document version. + + + List of indirect objects used in the document. + + + flag determines whether to write unused objects to result document + + + Yet not copied link annotations from the other documents. + + Yet not copied link annotations from the other documents. + Key - page from the source document, which contains this annotation. + Value - link annotation from the source document. + + + + Open PDF document in reading mode. + PDF reader. + + + Open PDF document in writing mode. + + Open PDF document in writing mode. + Document has no pages when initialized. + + PDF writer + + + Opens PDF document in the stamping mode. + + Opens PDF document in the stamping mode. +
    +
    + PDF reader. + PDF writer. +
    + + Open PDF document in stamping mode. + PDF reader. + PDF writer. + properties of the stamping process + + + Use this method to set the XMP Metadata. + The xmpMetadata to set. + + + + + + + + + Gets XMPMetadata. + + + Gets XMPMetadata or create a new one. + if true, create a new empty XMPMetadata if it did not present. + existed or newly created XMPMetadata byte array. + + + Gets PdfObject by object number. + object number. + + + + or + + , if object not found. + + + + Get number of indirect objects in the document. + number of indirect objects. + + + Gets the page by page number. + page number. + page by page number. + + + + Gets the + + instance by + + . + + + + + that present page. + + + page by + + . + + + + Get the first page of the document. + first page of the document. + + + Gets the last page of the document. + last page. + + + Creates and adds new page to the end of document. + added page + + + Creates and adds new page with the specified page size. + page size of the new page + added page + + + Creates and inserts new page to the document. + position to addPage page to + inserted page + + in case + page + is flushed + + + + Creates and inserts new page to the document. + position to addPage page to + page size of the new page + inserted page + + in case + page + is flushed + + + + Adds page to the end of document. + page to add. + added page. + + in case + + is flushed + + + + Inserts page to the document. + position to addPage page to + page to addPage + inserted page + + in case + + is flushed + + + + Gets number of pages of the document. + number of pages. + + + Gets page number by page. + the page. + page number. + + + + Gets page number by + + . + + + + + that present page. + + + page number by + + . + + + + + Removes the first occurrence of the specified page from this document, + if it is present. + + + Removes the first occurrence of the specified page from this document, + if it is present. Returns true if this document + contained the specified element (or equivalently, if this document + changed as a result of the call). + + page to be removed from this document, if present + true if this document contained the specified page + + + Removes page from the document by page number. + the one-based index of the PdfPage to be removed + the page that was removed from the list + + + Gets document information dictionary. + document information dictionary. + + + Gets default page size. + default page size. + + + Sets default page size. + page size to be set as default. + + + + + + + + + + + + + + + + + + + + + + Gets + PdfWriter + associated with the document. + + PdfWriter associated with the document. + + + + Gets + PdfReader + associated with the document. + + PdfReader associated with the document. + + + + Returns + + if the document is opened in append mode, and + + otherwise. + + + + + if the document is opened in append mode, and + + otherwise. + + + + Creates next available indirect reference. + created indirect reference. + + + Gets PDF version. + PDF version. + + + Gets PDF catalog. + PDF catalog. + + + Close PDF document. + + + Gets close status of the document. + true, if the document has already been closed, otherwise false. + + + Gets tagged status of the document. + true, if the document has tag structure, otherwise false. + + + + Gets + + of tagged document. + + + + + in case tagged document, otherwise false. + + + + + + Gets next parent index of tagged document. + -1 if document is not tagged, or >= 0 if tagged. + + + + + + Gets document + TagStructureContext + . + The document must be tagged, otherwise an exception will be thrown. + + + document + TagStructureContext + . + + + + + Copies a range of pages from current document to + + . + Use this method if you want to copy pages across tagged documents. + This will keep resultant PDF structure consistent. + + start of the range of pages to be copied. + end of the range of pages to be copied. + a document to copy pages to. + a position where to insert copied pages. + list of copied pages + + + + Copies a range of pages from current document to + + . + Use this method if you want to copy pages across tagged documents. + This will keep resultant PDF structure consistent. + + 1-based start of the range of pages to be copied. + 1-based end of the range of pages to be copied. + a document to copy pages to. + a position where to insert copied pages. + a copier which bears a special copy logic. May be NULL + list of new copied pages + + + + Copies a range of pages from current document to + + appending copied pages to the end. + Use this method if you want to copy pages across tagged documents. + This will keep resultant PDF structure consistent. + + 1-based start of the range of pages to be copied. + 1-based end of the range of pages to be copied. + a document to copy pages to. + list of new copied pages + + + + Copies a range of pages from current document to + + appending copied pages to the end. + Use this method if you want to copy pages across tagged documents. + This will keep resultant PDF structure consistent. + + 1-based start of the range of pages to be copied. + 1-based end of the range of pages to be copied. + a document to copy pages to. + a copier which bears a special copy logic. May be null. + list of new copied pages. + + + + Copies a range of pages from current document to + + . + Use this method if you want to copy pages across tagged documents. + This will keep resultant PDF structure consistent. + + list of pages to be copied. TreeSet for the order of the pages to be natural. + a document to copy pages to. + a position where to insert copied pages. + list of new copied pages + + + + Copies a range of pages from current document to + + . + Use this method if you want to copy pages across tagged documents. + This will keep resultant PDF structure consistent. + + list of pages to be copied. TreeSet for the order of the pages to be natural. + a document to copy pages to. + a position where to insert copied pages. + a copier which bears a special copy logic. May be NULL + list of new copied pages + + + + Copies a range of pages from current document to + + appending copied pages to the end. + Use this method if you want to copy pages across tagged documents. + This will keep resultant PDF structure consistent. + + list of pages to be copied. TreeSet for the order of the pages to be natural. + a document to copy pages to. + list of copied pages + + + + Copies a range of pages from current document to + + appending copied pages to the end. + Use this method if you want to copy pages across tagged documents. + This will keep resultant PDF structure consistent. + + list of pages to be copied. TreeSet for the order of the pages to be natural. + a document to copy pages to. + a copier which bears a special copy logic + list of copied pages + + + + Checks, whether + + method will close associated PdfReader. + + + true, + + method is going to close associated PdfReader, otherwise false. + + + + + Sets, whether + + method shall close associated PdfReader. + + + true, + + method shall close associated PdfReader, otherwise false. + + + + + Checks, whether + + method will close associated PdfWriter. + + + true, + + method is going to close associated PdfWriter, otherwise false. + + + + + Sets, whether + + method shall close associated PdfWriter. + + + true, + + method shall close associated PdfWriter, otherwise false. + + + + + Checks, whether + + will flush unused objects, + e.g. unreachable from PDF Catalog. By default - false. + + + false, if + + shall not flush unused objects, otherwise true. + + + + + Sets, whether + + shall flush unused objects, + e.g. unreachable from PDF Catalog. + + + false, if + + shall not flush unused objects, otherwise true. + + + + This method returns a complete outline tree of the whole document. + + if the flag is true, the method read the whole document and creates outline tree. + If false the method gets cached outline tree (if it was cached via calling getOutlines method before). + + + fully initialize + + object. + + + + This method initializes an outline tree of the document and sets outline mode to true. + + + This methods adds new name in the Dests NameTree. + This methods adds new name in the Dests NameTree. It throws an exception, if the name already exists. + + Name of the destination. + + An object destination refers to. Must be an array or a dictionary with key /D and array. + See ISO 32000-1 12.3.2.3 for more info. + + + + Gets static copy of cross reference table. + + + Gets document trailer. + document trailer. + + + + Adds + + that shall specify the colour characteristics of output devices + on which the document might be rendered. + + + + + to add. + + + + + Checks whether PDF document conforms a specific standard. + + Checks whether PDF document conforms a specific standard. + Shall be override. + + An object to conform. + type of object to conform. + + + Checks whether PDF document conforms a specific standard. + + Checks whether PDF document conforms a specific standard. + Shall be override. + + an object to conform. + type of object to conform. + + + + associated with an object to check. + + + + Checks whether PDF document conforms a specific standard. + + Checks whether PDF document conforms a specific standard. + Shall be override. + + + a + + object to conform. + + + + + associated with an object to check. + + + + Adds file attachment at document level. + the file description + an array with the file. + the actual file name stored in the pdf + mime type of the file + the optional extra file parameters such as the creation or modification date + + if + + , + + will be added. Shall be one of: + + , + + , + + , + + or + + . + + + + Adds file attachment at document level. + the file description + the path to the file. + the actual file name stored in the pdf + mime type of the file + + if + + , + + will be added. Shall be one of: + + , + + , + + , + + or + + . + + + + + Adds file attachment at document level. + the file description + + + + object. + + + + This method retrieves the page labels from a document as an array of String objects. + + + + list of page labels if they were found, or + + otherwise + + + + Indicates if the document has any outlines + + + + , if there are outlines and + + otherwise. + + + + Sets the flag indicating the presence of structure elements that contain user properties attributes. + + the user properties flag + + + Gets list of indirect references. + list of indirect references. + + + + Initialize + + . + + + + Save the link annotation in a temporary storage for further copying. + + just copied + + link annotation belongs to. + + + + + itself. + + + + Checks whether PDF document conforms a specific standard. + + Checks whether PDF document conforms a specific standard. + Shall be override. + + + + + Mark an object with + + . + + an object to mark. + + + Flush an object. + object to flush. + indicates whether object can be placed into object stream. + on error. + + + Initializes document. + + new pdf version of the resultant file if stamper is used and the version needs to be changed, + or + + otherwise + + + + Adds custom XMP metadata extension. + Adds custom XMP metadata extension. Useful for PDF/UA, ZUGFeRD, etc. + + + + to add custom metadata to. + + + + Updates XMP metadata. + + Updates XMP metadata. + Shall be override. + + + + + Update XMP metadata values from + + . + + + + + List all newly added or loaded fonts + + List of + PdfFonts + . + + + + Checks page before adding and add. + one-base index of the page. + + + + to add. + + + + Checks page before adding. + + + + to add. + + + + checks whether a method is invoked at the closed document + + + + Gets + + instance. + + + + + instance. + + + + This method removes all annotation entries from form fields associated with a given page. + to remove from. + + + This method copies all given outlines + outlines to be copied + document where outlines should be copied + + + This method gets all outlines to be copied including parent outlines + current outline + a Set of outlines to be copied + + + This method copies create new outlines in the Document to copy. + - Set of outlines to be copied + - new parent outline + - old parent outline + + + + + + Paulo Soares + Kazuya Ujihara + + + Creates the encryption. + + Creates the encryption. The userPassword and the + ownerPassword can be null or have zero length. In this case the ownerPassword + is replaced by a random string. The open permissions for the document can be + AllowPrinting, AllowModifyContents, AllowCopy, AllowModifyAnnotations, + AllowFillIn, AllowScreenReaders, AllowAssembly and AllowDegradedPrinting. + The permissions can be combined by ORing them. + + the user password. Can be null or empty + the owner password. Can be null or empty + the user permissions + + the type of encryption. It can be one of STANDARD_ENCRYPTION_40, STANDARD_ENCRYPTION_128 or ENCRYPTION_AES128. + Optionally DO_NOT_ENCRYPT_METADATA can be ored to output the metadata in cleartext + + if the document is already open + + + Creates the certificate encryption. + + Creates the certificate encryption. An array of one or more public certificates + must be provided together with an array of the same size for the permissions for each certificate. + The open permissions for the document can be + AllowPrinting, AllowModifyContents, AllowCopy, AllowModifyAnnotations, + AllowFillIn, AllowScreenReaders, AllowAssembly and AllowDegradedPrinting. + The permissions can be combined by ORing them. + Optionally DO_NOT_ENCRYPT_METADATA can be ored to output the metadata in cleartext + + the public certificates to be used for the encryption + the user permissions for each of the certificates + the type of encryption. It can be one of STANDARD_ENCRYPTION_40, STANDARD_ENCRYPTION_128 or ENCRYPTION_AES128. + + if the document is already open + + + document id which was used for encryption. Could be null, if encryption doesn't rely on document id. + + + + Computes user password if standard encryption handler is used with Standard40, Standard128 or AES128 algorithm. + + owner password of the encrypted document. + user password, or null if not a standard encryption handler was used. + + + + To manually flush a + PdfObject + behind this wrapper, you have to ensure + that this object is added to the document, i.e. it has an indirect reference. + Basically this means that before flushing you need to explicitly call + + . + For example: wrapperInstance.makeIndirect(document).flush(); + Note that not every wrapper require this, only those that have such warning in documentation. + + + + + This class takes any PDF and returns exactly the same but + encrypted. + + + This class takes any PDF and returns exactly the same but + encrypted. All the content, links, outlines, etc, are kept. + It is also possible to change the info dictionary. + + + + Entry point to encrypt a PDF document. + the read PDF + the output destination + + encryption properties. See + + . + + + an optional + String + map to add or change + the info dictionary. Entries with + + values delete the key in the original info dictionary + + + + Entry point to encrypt a PDF document. + the read PDF + the output destination + + encryption properties. See + + . + + + + Give you a verbose analysis of the permissions. + the permissions value of a PDF file + a String that explains the meaning of the permissions value + + + Tells you if printing is allowed. + the permissions value of a PDF file + true if printing is allowed + + + Tells you if modifying content is allowed. + the permissions value of a PDF file + true if modifying content is allowed + + + Tells you if copying is allowed. + the permissions value of a PDF file + true if copying is allowed + + + Tells you if modifying annotations is allowed. + the permissions value of a PDF file + true if modifying annotations is allowed + + + Tells you if filling in fields is allowed. + the permissions value of a PDF file + true if filling in fields is allowed + + + Tells you if repurposing for screenreaders is allowed. + the permissions value of a PDF file + true if repurposing for screenreaders is allowed + + + Tells you if document assembly is allowed. + the permissions value of a PDF file + true if document assembly is allowed + + + Tells you if degraded printing is allowed. + the permissions value of a PDF file + true if degraded printing is allowed + + + Object number. + + + Object generation. + + + PdfObject that current PdfIndirectReference instance refers to. + + + Indirect reference number of object stream containing refersTo object. + + Indirect reference number of object stream containing refersTo object. + If refersTo is not placed into object stream - objectStreamNumber = 0. + + + + + Offset in a document of the + refersTo + object. + If the object placed into object stream then it is an object index inside object stream. + + + + PdfDocument object belongs to. + PdfDocument object belongs to. For direct objects it is null. + + + Gets direct object and try to resolve indirects chain. + + Gets direct object and try to resolve indirects chain. +

    + Note: If chain of references has length of more than 32, + this method return 31st reference in chain. +

    +
    +
    + + Gets refersTo object offset in a document. + object offset in a document. If refersTo object is in object stream then -1. + + + Gets refersTo object index in the object stream. + object index in a document. If refersTo object is not in object stream then -1. + + + Releases indirect reference from the document. + + Releases indirect reference from the document. Remove link to the referenced indirect object. +

    + Note: Be careful when using this method. Do not use this method for wrapper objects, + it can be cause of errors. + Free indirect reference could be reused for a new indirect object. +

    +
    +
    + + Gets a PdfWriter associated with the document object belongs to. + PdfWriter. + + + Gets a PdfReader associated with the document object belongs to. + PdfReader. + + + Sets special states of current object. + special flag of current object + + + PdfName for the abbreviation of FlateDecode. + + PdfName for the abbreviation of FlateDecode. For the Flatness Tolerance PdfName use + + (Uppercase 'L') + + + + PdfName for Flatness Tolerance. + + PdfName for Flatness Tolerance. For the PdfName with the FlateDecode abbreviation use + + (Lowercase 'L') + + + + map strings to all known static names + + + Marks object to be saved as indirect. + a document the indirect reference will belong to. + object itself. + + + Marks object to be saved as indirect. + a document the indirect reference will belong to. + object itself. + + + Copies object to a specified document. + + Copies object to a specified document. + Works only for objects that are read from existing document, otherwise an exception is thrown. + + document to copy object to. + copied object. + + + Copies object to a specified document. + + Copies object to a specified document. + Works only for objects that are read from existing document, otherwise an exception is thrown. + + document to copy object to. + + indicates if to allow copy objects which already have been copied. + If object is associated with any indirect reference and allowDuplicating is false then already existing reference will be returned instead of copying object. + If allowDuplicating is true then object will be copied and new indirect reference will be assigned. + + copied object. + + + Creates the NameTree of current Document + Document catalog + the type of tree. Dests Tree, AP Tree etc. + + + Representation of the null object in the PDF specification. + + + Creates a PdfNull instance. + + + Marks object to be saved as indirect. + a document the indirect reference will belong to. + object itself. + + + Marks object to be saved as indirect. + a document the indirect reference will belong to. + object itself. + + + Copies object to a specified document. + + Copies object to a specified document. + Works only for objects that are read from existing document, otherwise an exception is thrown. + + document to copy object to. + copied object. + + + Copies object to a specified document. + + Copies object to a specified document. + Works only for objects that are read from existing document, otherwise an exception is thrown. + + document to copy object to. + + indicates if to allow copy objects which already have been copied. + If object is associated with any indirect reference and allowDuplicating is false then already existing reference will be returned instead of copying object. + If allowDuplicating is true then object will be copied and new indirect reference will be assigned. + + copied object. + + + Marks object to be saved as indirect. + a document the indirect reference will belong to. + object itself. + + + Marks object to be saved as indirect. + a document the indirect reference will belong to. + object itself. + + + Copies object to a specified document. + + Copies object to a specified document. + Works only for objects that are read from existing document, otherwise an exception is thrown. + + document to copy object to. + copied object. + + + Copies object to a specified document. + + Copies object to a specified document. + Works only for objects that are read from existing document, otherwise an exception is thrown. + + document to copy object to. + + indicates if to allow copy objects which already have been copied. + If object is associated with any indirect reference and allowDuplicating is false then already existing reference will be returned instead of copying object. + If allowDuplicating is true then object will be copied and new indirect reference will be assigned. + + copied object. + + + Creates the NumberTree of current Document + Document catalog + the type of tree. ParentTree or PageLabels. + + + Representation of a stream as described in the PDF Specification. + + + + Constructs a + PdfStream + -object. + + + initial content of + PdfOutputStream + . + + the compression level (0 = best speed, 9 = best compression, -1 is default) + + + + Creates a PdfStream instance. + bytes to write to the PdfStream + + + Creates an efficient stream. + + Creates an efficient stream. No temporary array is ever created. The + InputStream + is totally consumed but is not closed. The general usage is: +

    +

    +            PdfDocument document = ?;
    +            InputStream in = ?;
    +            PdfStream stream = new PdfStream(document, in, PdfOutputStream.DEFAULT_COMPRESSION);
    +            ?
    +            stream.flush();
    +            in.close();
    +            
    +
    + the data to write to this stream + the compression level (0 = best speed, 9 = best compression, -1 is default) + +
    + + Creates an efficient stream. + + Creates an efficient stream. No temporary array is ever created. The + InputStream + is totally consumed but is not closed. The general usage is: +

    +

    +            PdfDocument document = ?;
    +            InputStream in = ?;
    +            PdfStream stream = new PdfStream(document, in);
    +            stream.flush();
    +            in.close();
    +            
    +
    + the data to write to this stream +
    + + + Constructs a + PdfStream + -object. + + the compression level (0 = best speed, 9 = best compression, -1 is default) + + + + Creates an empty PdfStream instance. + + + Gets output stream. + output stream + + + Gets compression level of this PdfStream. + + Gets compression level of this PdfStream. + For more details @see + + . + + compression level. + + + Sets compression level of this PdfStream. + + Sets compression level of this PdfStream. + For more details @see + + . + + the compression level (0 = best speed, 9 = best compression, -1 is default) + + + + Gets decoded stream bytes. + byte[] + + + Gets stream bytes. + true if to get decoded stream bytes, otherwise false. + + byte content of the + PdfStream + . Byte content will be + + , + if the + PdfStream + was created by + InputStream + . + + + + Sets bytes as stream's content. + + Sets bytes as stream's content. + Could not be used with streams which were created by InputStream. + + new content for stream; if null then stream's content will be discarded + + + Sets or appends bytes to stream content. + + Sets or appends bytes to stream content. + Could not be used with streams which were created by InputStream. + + + new content for stream; if null and append is false then + stream's content will be discarded + + + if set to true then bytes will be appended to the end, + rather then replace original content + + + + Marks object to be saved as indirect. + a document the indirect reference will belong to. + object itself. + + + Marks object to be saved as indirect. + a document the indirect reference will belong to. + object itself. + + + Copies object to a specified document. + + Copies object to a specified document. + Works only for objects that are read from existing document, otherwise an exception is thrown. + + document to copy object to. + copied object. + + + Copies object to a specified document. + + Copies object to a specified document. + Works only for objects that are read from existing document, otherwise an exception is thrown. + + document to copy object to. + + indicates if to allow copy objects which already have been copied. + If object is associated with any indirect reference and allowDuplicating is false then already existing reference will be returned instead of copying object. + If allowDuplicating is true then object will be copied and new indirect reference will be assigned. + + copied object. + + + Update length manually in case its correction. + + Update length manually in case its correction. + PdfReader.checkPdfStreamLength() + method. + + + + Release content of PdfStream. + + + Max number of objects in object stream. + + + Current object stream size (number of objects inside). + + + Stream containing object indices, a heading part of object stream. + + + This constructor is for reusing ByteArrayOutputStreams of indexStream and outputStream. + + This constructor is for reusing ByteArrayOutputStreams of indexStream and outputStream. + NOTE Only for internal use in PdfWriter! + + previous PdfObjectStream. + + + Adds object to the object stream. + object to add. + + + Gets object stream size (number of objects inside). + object stream size. + + + + Document outline object + See ISO-320001, 12.3.3 Document Outline. + + + + A flag for displaying the outline item’s text with italic font. + + + A flag for displaying the outline item’s text with bold font. + + + Create instance of document outline. + the text that shall be displayed on the screen for this item. + Outline dictionary + + + + the outline belongs to. + + + + Create instance of document outline. + the text that shall be displayed on the screen for this item. + Outline dictionary + parent outline. + + + This constructor creates root outline in the document. + + + + + + + Gets title of the outline. + String value. + + + + Sets title of the outline with + + encoding, + Title + key. + + String value. + + + + Sets color for the outline entry’s text, + C + key. + + + + + + + + + Sets text style for the outline entry’s text, + F + key. + + + Could be either + + or + + . Default value is + 0 + . + + + + Gets content dictionary. + + + + . + + + + Gets list of children outlines. + + List of + + . + + + + Gets parent outline. + + + + . + + + + + Gets + + . + + + + + . + + + + + Adds + + for the outline, + Dest + key. + + + instance of + + . + + + + + Adds + + for the outline, + A + key. + + + instance of + + . + + + + Defines if the outline needs to be closed or not. + + Defines if the outline needs to be closed or not. + By default, outlines are open. + + if false, the outline will be closed by default + + + + Adds a new + PdfOutline + with specified parameters as a child to existing + PdfOutline + and put it to specified position in the existing + PdfOutline + children list. + + an outline title + + a position in the current outline child List where a new outline should be added. + If the position equals -1, then the outline will be put in the end of children list. + + just created outline + + + + Adds an + PdfOutline + as a child to existing + PdfOutline + and put it in the end of the existing + PdfOutline + children list. + + an outline title + just created outline + + + + Adds an + PdfOutline + as a child to existing + PdfOutline + and put it to the end of the existing + PdfOutline + children list. + + an outline to add. + just created outline + + + Clear list of children. + + + + Sets + + . + + + instance of + + . + + + + Remove this outline from the document. + + + + Specify the colour characteristics of output devices on which the document might be rendered + See ISO 32000-1 14.11.5: Output Intents. + + + + Creates output intent dictionary. + + Creates output intent dictionary. Null values are allowed to + suppress any key. + By default output intent subtype is GTS_PDFA1, use setter to change it. + + + + Document associated with PdfOutputStream. + + + Contains the business logic for cryptography. + + + Automatically rotate new content if the page has a rotation ( is disabled by default ) + + + + See + + . + + + + Gets page size, defined by media box object. + Gets page size, defined by media box object. This method doesn't take page rotation into account. + + + + + that specify page size. + + + + Gets page size, considering page rotation. + + + + that specify size of rotated page. + + + + Gets the number of degrees by which the page shall be rotated clockwise when displayed or printed. + + + Gets the number of degrees by which the page shall be rotated clockwise when displayed or printed. + Shall be a multiple of 90. + + + + int + number of degrees. Default value: 0 + + + + Sets the page rotation. + + the + int + number of degrees by which the page shall be rotated clockwise + when displayed or printed. Shall be a multiple of 90. + + + this + + instance. + + + + + Gets the content stream at specified 0-based index in the Contents object + + . + The situation when Contents object is a + + is treated like a one element array. + + + the + int + index of returned + + . + + + + + object at specified index. + + if the index is out of range + + + + Gets the size of Contents object + + . + The situation when Contents object is a + + is treated like a one element array. + + + the + int + size of Contents object, or 1 if Contents object is a + + . + + + + + Returns the Contents object if it is + + , or first stream in the array if it is + + . + + + first + + in Contents object, or + + if Contents is empty. + + + + + Returns the Contents object if it is + + , or last stream in the array if it is + + . + + + first + + in Contents object, or + + if Contents is empty. + + + + + Creates new + + object and puts it at the beginning of Contents array + (if Contents object is + + it will be replaced with one-element array). + + + Created + + object. + + + + + Creates new + + object and puts it at the end of Contents array + (if Contents object is + + it will be replaced with one-element array). + + + Created + + object. + + + + + Gets the + + wrapper object for this page resources. + If page doesn't have resource object, then it will be inherited from page's parents. + If neither parents nor page has the resource object, then the new one is created and added to page dictionary. +

    + NOTE: If you'll try to modify the inherited resources, then the new resources object will be created, + so you won't change the parent's resources. + This new object under the wrapper will be added to page dictionary on + + , + or you can add it manually with this line, if needed:
    + getPdfObject().put(PdfName.Resources, getResources().getPdfObject()); +
    + + + + wrapper of the page. + +
    + + + Sets + + object. + + + + + to set. + + + this + + instance. + + + + Sets the XMP Metadata. + + the + byte[] + of XMP Metadata to set. + + in case of writing error. + + + Serializes XMP Metadata to byte array and sets it. + + the + + object to set. + + + the + + used while serialization. + + in case of XMP Metadata serialization error. + in case of writing error. + + + Serializes XMP Metadata to byte array and sets it. + Serializes XMP Metadata to byte array and sets it. Uses padding equals to 2000. + + the + + object to set. + + in case of XMP Metadata serialization error. + in case of writing error. + + + Gets the XMP Metadata object. + + + + object, that represent XMP Metadata. + + + + + Copies page to the specified document. + + Copies page to the specified document. +

    + NOTE: Works only for pages from the document opened in reading mode, otherwise an exception is thrown. +
    + a document to copy page to. + + copied + + . + +
    + + Copies page to the specified document. + + Copies page to the specified document. +

    + NOTE: Works only for pages from the document opened in reading mode, otherwise an exception is thrown. +
    + a document to copy page to. + + a copier which bears a specific copy logic. May be + + + + copied + + . + +
    + + Copies page as FormXObject to the specified document. + a document to copy to. + + copied + + object. + + + + + + Gets the + + that owns that page, or + + if such document isn't exist. + + + + + that owns that page, or + + if such document isn't exist. + + + + Flushes page and it's content stream. + + Flushes page and it's content stream. +
    +
    + If the page belongs to the document which is tagged, page flushing also triggers flushing of the tags, + which are considered to belong to the page. The logic that defines if the given tag (structure element) belongs + to the page is the following: if all the marked content references (dictionary or number references), that are the + descenders of the given structure element, belong to the current page - the tag is considered + to belong to the page. If tag has descenders from several pages - it is flushed, if all other pages except the + current one are flushed. +
    +
    + + Flushes page and its content stream. + + Flushes page and its content stream. If flushContentStreams is true, all content streams that are + rendered on this page (like FormXObjects, annotation appearance streams, patterns) and also all images associated + with this page will also be flushed. +
    + For notes about tag structure flushing see + PdfPage#flush() method + . +
    +
    + If PdfADocument is used, flushing will be applied only if flushContentStreams is true. +
    + + if true all content streams that are rendered on this page (like form xObjects, + annotation appearance streams, patterns) and also all images associated with this page + will be flushed. + +
    + + + Gets + + object specified by page's Media Box, that defines the boundaries of the physical medium + on which the page shall be displayed or printed + + + + + object specified by page Media Box, expressed in default user space units. + + in case of any error while reading MediaBox object. + + + + Sets the Media Box object, that defines the boundaries of the physical medium + on which the page shall be displayed or printed. + + + the + + object to set, expressed in default user space units. + + + this + + instance. + + + + + Gets the + + specified by page's CropBox, that defines the visible region of default user space. + When the page is displayed or printed, its contents shall be clipped (cropped) to this rectangle + and then shall be imposed on the output medium in some implementation-defined manner. + + + the + + object specified by pages's CropBox, expressed in default user space units. + MediaBox by default. + + + + Sets the CropBox object, that defines the visible region of default user space. + + Sets the CropBox object, that defines the visible region of default user space. + When the page is displayed or printed, its contents shall be clipped (cropped) to this rectangle + and then shall be imposed on the output medium in some implementation-defined manner. + + + the + + object to set, expressed in default user space units. + + + this + + instance. + + + + + Sets the ArtBox object, that define the extent of the page’s meaningful content + (including potential white space) as intended by the page’s creator. + + + the + + object to set, expressed in default user space units. + + + this + + instance. + + + + + Gets the + + object specified by page's ArtBox, that define the extent of the page’s + meaningful content (including potential white space) as intended by the page’s creator. + + + the + + object specified by page's ArtBox, expressed in default user space units. + CropBox by default. + + + + Sets the TrimBox object, that define the intended dimensions of the finished page after trimming. + + + the + + object to set, expressed in default user space units. + + + this + + instance. + + + + + Gets the + + object specified by page's TrimBox object, + that define the intended dimensions of the finished page after trimming. + + + the + + object specified by page's TrimBox, expressed in default user space units. + CropBox by default. + + + + Get decoded bytes for the whole page content. + byte array. + + in case of any + IOException). + + + + Gets decoded bytes of a certain stream of a page content. + index of stream inside Content. + byte array. + + in case of any + IOException). + + + + Calculates and returns next available MCID reference. + calculated MCID reference. + in case of not tagged document. + + + + Gets + + key of the page’s entry in the structural parent tree. + + + + + key of the page’s entry in the structural parent tree. + + + + Helper method to add an additional action to this page. + + Helper method to add an additional action to this page. + May be used in chain. + + + a + + specifying the name of an additional action + + + the + + to add as an additional action + + + this + + instance. + + + + + Checks if page contains the specified annotation. + + the + + to check. + + + + + if page contains specified annotation and + + otherwise. + + + + Adds specified annotation to the end of annotations array and tagged it. + + Adds specified annotation to the end of annotations array and tagged it. + May be used in chain. + + + the + + to add. + + + this + + instance. + + + + + Adds specified + + to specified index in annotations array with or without autotagging. + May be used in chain. + + + the index at which specified annotation will be added. If + -1 + then annotation will be added + to the end of array. + + + the + + to add. + + + if + + the added annotation will be autotagged.
    + (see + + ) + + + this + + instance. + +
    + + Removes an annotation from the page. + + Removes an annotation from the page. +

    + NOTE: If document is tagged, PdfDocument's PdfTagStructure instance will point at annotation tag parent after method call. +
    + an annotation to be removed. + + this + + instance. + +
    + + + Gets the number of + + associated with this page. + + + the + int + number of + + associated with this page. + + + + This method gets outlines of a current page + + return all outlines of a current page + + + + true - if in case the page has a rotation, then new content will be automatically rotated in the + opposite direction. On the rotated page this would look like if new content ignores page rotation. + + + + + If true - defines that in case the page has a rotation, then new content will be automatically rotated in the + opposite direction. + + + If true - defines that in case the page has a rotation, then new content will be automatically rotated in the + opposite direction. On the rotated page this would look like if new content ignores page rotation. + Default value - + + . + + - true to ignore rotation of the new content on the rotated page. + + + + This method adds or replaces a page label. + + The numbering style that shall be used for the numeric portion of each page label. + May be NULL + + The label prefix for page labels in this range. May be NULL + + this + + instance. + + + + This method adds or replaces a page label. + + The numbering style that shall be used for the numeric portion of each page label. + May be NULL + + The label prefix for page labels in this range. May be NULL + + The value of the numeric portion for the first page label in the range. Must be greater or + equal 1. + + + this + + instance. + + + + + Helper method that associate specified value with specified key in the underlined + + . + May be used in chain. + + + the + + key with which the specified value is to be associated. + + + the + + value to be associated with the specified key. + + + this + + object. + + + + + This flag is meaningful for the case, when page rotation is applied and ignorePageRotationForContent + is set to true. + + + This flag is meaningful for the case, when page rotation is applied and ignorePageRotationForContent + is set to true. NOTE: It is needed for the internal usage. +

    + This flag defines if inverse matrix (which rotates content into the opposite direction from page rotation + direction in order to give the impression of the not rotated text) is already applied to the page content stream. + See + +
    + true, if inverse matrix is already applied, false otherwise. +
    + + NOTE: For internal usage! Use this method only if you know what you are doing. + + NOTE: For internal usage! Use this method only if you know what you are doing. +

    + This method is called when inverse matrix (which rotates content into the opposite direction from page rotation + direction in order to give the impression of the not rotated text) is applied to the page content stream. + See + +
    +
    + + + Algorithm for construction + + tree + + + + Create PdfPages tree. + + + PdfCatalog + + + + + Returns the + PdfPage + at the specified position in this list. + + one-based index of the element to return + + the + PdfPage + at the specified position in this list + + + + + Returns the + PdfPage + by page's PdfDictionary. + + page's PdfDictionary + + the + PdfPage + object, that wraps + + . + + + + Gets total number of @see PdfPages. + total number of pages + + + + Returns the index of the first occurrence of the specified page + in this tree, or 0 if this tree does not contain the page. + + + + + Returns the index of the first occurrence of the page in this tree + specified by it's PdfDictionary, or 0 if this tree does not contain the page. + + + + + Appends the specified + PdfPage + to the end of this tree. + + + + PdfPage + + + + + Insert + PdfPage + into specific one-based position. + + one-base index of the page + + + + to insert. + + + + Removes the page at the specified position in this tree. + + Removes the page at the specified position in this tree. + Shifts any subsequent elements to the left (subtracts one from their + indices). + + the one-based index of the PdfPage to be removed + the page that was removed from the list + + + Generate PdfPages tree. + + root + + + in case empty document + + + Constructs a new PdfReader. + source of bytes for the reader + properties of the created reader + + + + Reads and parses a PDF document. + + the + InputStream + containing the document. The stream is read to the + end but is not closed + + properties of the created reader + on error + + + Reads and parses a PDF document. + + the + InputStream + containing the document. Stream is closed automatically, when document is closed, + if user doesn't want to close stream, he should set closeStream=false; + + on error + + + Reads and parses a PDF document. + the file name of the document + properties of the created reader + on error + + + Reads and parses a PDF document. + the file name of the document + on error + + + + + + If any exception generated while reading XRef section, PdfReader will try to rebuild it. + true, if PdfReader rebuilt Cross-Reference section. + + + + Some documents contain hybrid XRef, for more information see "7.5.8.4 Compatibility with Applications + That Do Not Support Compressed Reference Streams" in PDF 32000-1:2008 spec. + + true, if the document has hybrid Cross-Reference section. + + + Indicates whether the document has Cross-Reference Streams. + true, if the document has Cross-Reference Streams. + + + If any exception generated while reading PdfObject, PdfReader will try to fix offsets of all objects. + + true, if PdfReader fixed offsets of PdfObjects. + + + Gets position of the last Cross-Reference table. + -1 if Cross-Reference table has rebuilt, otherwise position of the last Cross-Reference table. + + + Reads and gets stream bytes. + true if to get decoded stream bytes, false if to leave it originally encoded. + byte[] + + + + + + + Gets the input stream associated with PdfStream. + + Gets the input stream associated with PdfStream. + User is responsible for closing returned stream. + + true if to get decoded stream, false if to leave it originally encoded. + InputStream + + + + Decode a byte[] applying the filters specified in the provided dictionary using default filter handlers. + + the bytes to decode + the dictionary that contains filter information + the decoded bytes + if there are any problems decoding the bytes + + + Decode a byte[] applying the filters specified in the provided dictionary using the provided filter handlers. + + the bytes to decode + the dictionary that contains filter information + the map used to look up a handler for each type of filter + the decoded bytes + if there are any problems decoding the bytes + + + + Gets a new file instance of the original PDF + document. + + a new file instance of the original PDF document + + + Provides the size of the opened file. + The size of the opened file. + + + + Gets the declared Pdf/A conformance level of the source document that is being read. + + Gets the declared Pdf/A conformance level of the source document that is being read. + Note that this information is provided via XMP metadata and is not verified by iText. + + + conformance level of the source document, or + + if no Pdf/A + conformance level information is specified. + + + + Computes user password if standard encryption handler is used with Standard40, Standard128 or AES128 encryption algorithm. + + user password, or null if not a standard encryption handler was used or if ownerPasswordUsed wasn't use to open the document. + + + + Parses the entire PDF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Utility method that checks the provided byte source to see if it has junk bytes at the beginning. + + + Utility method that checks the provided byte source to see if it has junk bytes at the beginning. If junk bytes + are found, construct a tokeniser that ignores the junk. Otherwise, construct a tokeniser for the byte source as it is + + the source to check + a tokeniser that is guaranteed to start at the PDF header + if there is a problem reading the byte source + + + + + + + + + + Wrapper class that represent resource dictionary - that define named resources + used by content streams operators. + + + Wrapper class that represent resource dictionary - that define named resources + used by content streams operators. (ISO 32000-1, 7.8.3 Resource Dictionaries) + + + + Creates new instance from given dictionary. + + the + + object from which the resource object will be created. + + + + Creates new instance from empty dictionary. + + + Adds font to resources and register PdfFont in the document for further flushing. + added font resource name. + + + + Adds + + object to the resources. + + + the + + to add. + + added image resource name. + + + + Adds + + to the resources as image. + + + the + + to add. + + added image resources name. + + + + Adds + + to the resources as image. + + + the + + to add. Should be + + . + + added image resources name. + + + + Adds + + object to the resources. + + + the + + to add. + + added form resource name. + + + + Adds + + to the resources as form. + + + the + + to add. + + added form resources name. + + + + Adds + + to the resources as form. + + + the + + to add. Should be + + . + + added form resources name. + + + + Adds the given Form XObject to the current instance of + + . + + Form XObject. + Preferred name for the given Form XObject. + + the + + of the newly added resource + + + + + Adds + + object to the resources. + + + the + + to add. + + added graphics state parameter dictionary resource name. + + + + Adds + + to the resources as graphics state parameter dictionary. + + + the + + to add. + + added graphics state parameter dictionary resources name. + + + + Adds + + to the resources as graphics state parameter dictionary. + + + the + + to add. Should be + + . + + added graphics state parameter dictionary resources name. + + + + Adds + + to the resources as properties list. + + + the + + to add. + + added properties list resources name. + + + + Adds + + to the resources as properties list. + + + the + + to add. Should be + + . + + added properties list resources name. + + + + Adds + + object to the resources. + + + the + + to add. + + added color space resource name. + + + + Adds + + to the resources as color space. + + + the + + to add. + + added color space resources name. + + + + Adds + + object to the resources. + + + the + + to add. + + added pattern resource name. + + + + Adds + + to the resources as pattern. + + + the + + to add. + + added pattern resources name. + + + + Adds + + to the resources as pattern. + + + the + + to add. Should be + + or + + . + + added pattern resources name. + + + + Adds + + object to the resources. + + + the + + to add. + + added shading resource name. + + + + Adds + + to the resources as shading dictionary. + + + the + + to add. + + added shading dictionary resources name. + + + + Adds + + to the resources as shading dictionary. + + + the + + to add. Should be + + or + + . + + added shading dictionary resources name. + + + Sets the default color space (see ISO-320001 Paragraph 8.6.5.6). + + the name of Default Color Space. Should be + + , + + , or + + . + + the value of the default color space to be set. + + + Sets the value of default Gray Color Space (see ISO-320001 Paragraph 8.6.5.6). + the color space to set. + + + Sets the value of default RGB Color Space (see ISO-320001 Paragraph 8.6.5.6). + the color space to set. + + + Sets the value of default CMYK Color Space (see ISO-320001 Paragraph 8.6.5.6). + the color space to set. + + + + + Gets the names of all the added resources. + the name of all the added resources. + + + Gets the array of predefined procedure set names (see ISO-320001 Paragraph 14.2). + the array of predefined procedure set names. + + + Sets the array of predefined procedure set names (see ISO-320001 Paragraph 14.2). + the array of predefined procedure set names to be set. + + + Gets the names of all resources of specified type. + + the resource type. Should be + + , + + , + + , + + , + + , + + . + + + set of resources name of corresponding type. May be empty. + Will be empty in case of incorrect resource type. + + + + + Get the + + object that that contain resources of specified type. + + + the resource type. Should be + + , + + , + + , + + , + + , + + . + + + the + + object containing all resources of specified type, + or + + in case of incorrect resource type. + + + + Represents a resource name generator. + + Represents a resource name generator. The generator takes into account + the names of already existing resources thus providing us a unique name. + The name consists of the following parts: prefix (literal) and number. + + + + + Constructs an instance of + + class. + + + Type of resource. Should be + + , + + , + + , + + , + + , + + . + + Prefix used for generating names. + + Seed for the value which is appended to the number each time + new name is generated. + + + + + Constructs an instance of + + class. + + + Type of resource. Should be + + , + + , + + , + + , + + , + + . + + Prefix used for generating names. + + + Gets the resource type of generator. + + Type of resource. May be + + , + + , + + , + + , + + , + + . + + + + Generates new (unique) resource name. + + the + + object for which name will be generated. + + new (unique) resource name. + + + + A + PdfString + -class is the PDF-equivalent of a + JAVA- + String + -object. +

    + A string is a sequence of characters delimited by parenthesis. + If a string is too long to be conveniently placed on a single line, it may + be split across multiple lines by using the backslash character (\) at the + end of a line to indicate that the string continues on the following line. + Within a string, the backslash character is used as an escape to specify + unbalanced parenthesis, non-printing ASCII characters, and the backslash + character itself. Use of the \ddd escape sequence is the preferred + way to represent characters outside the printable ASCII character set.
    + This object is described in the 'Portable Document Format Reference Manual + version 1.7' section 3.2.3 (page 53-56). +

    + +

    +
    + + Only PdfReader can use this method! + + + Gets the encoding of this string. + + + Sets the encoding of this string. + + Sets the encoding of this string. + NOTE. Byte content will be removed. + + + + + Returns the Unicode + String + value of this + PdfString + -object. + + + + Gets bytes of String-value considering encoding. + byte array + + + Marks object to be saved as indirect. + a document the indirect reference will belong to. + object itself. + + + Marks object to be saved as indirect. + a document the indirect reference will belong to. + object itself. + + + Copies object to a specified document. + + Copies object to a specified document. + Works only for objects that are read from existing document, otherwise an exception is thrown. + + document to copy object to. + copied object. + + + Copies object to a specified document. + + Copies object to a specified document. + Works only for objects that are read from existing document, otherwise an exception is thrown. + + document to copy object to. + + indicates if to allow copy objects which already have been copied. + If object is associated with any indirect reference and allowDuplicating is false then already existing reference will be returned instead of copying object. + If allowDuplicating is true then object will be copied and new indirect reference will be assigned. + + copied object. + + + + Decrypt content of an encrypted + PdfString + . + + + + + Encrypt content of + value + and set as content. + generateContent() + won't be called. + + @see PdfEncryption + true if value was encrypted, otherwise false. + + + Escape special symbols or convert to hexadecimal string. + + Escape special symbols or convert to hexadecimal string. + This method don't change either + value + or + content + ot the + PdfString + . + + byte array to manipulate with. + Hexadecimal string or string with escaped symbols in byte array view. + + + + + Adds content of the + PdfArray + . + + + the + PdfArray + to be added + + + + + Adds the Collection of PdfObjects. + the Collection of PdfObjects to be added + + + + This class represents all official PDF versions. + + + Creates a PdfVersion class. + major version number + minor version number + + + + Creates a PdfVersion class from a String object if the specified version + can be found. + + version number + PdfVersion of the specified version + + + + Creates a PdfVersion class from a + + object if the specified version + can be found. + + version number + PdfVersion of the specified version + + + This method sets HideToolBar flag to true or false + + + + + This method sets HideMenuBar flag to true or false + + + + + This method sets HideWindowUI flag to true or false + + + + + This method sets FitWindow flag to true or false + + + + + This method sets CenterWindow flag to true or false + + + + + This method sets DisplayDocTitle flag to true or false + + + + + This method sets NonFullScreenPageMode property. + + This method sets NonFullScreenPageMode property. Allowed values are UseNone, UseOutlines, useThumbs, UseOC. + This entry is meaningful only if the value of the PageMode entry in the Catalog dictionary is FullScreen + + + + + + This method sets predominant reading order of text. + + + + + + This method sets the name of the page boundary representing the area of a page that shall be displayed when + viewing the document on the screen. + + + + + + + This method sets the name of the page boundary to which the contents of a page shall be clipped when + viewing the document on the screen. + + + + + + + This method sets the name of the page boundary representing the area of a page that shall be + rendered when printing the document. + + + + + + + This method sets the name of the page boundary to which the contents of a page shall be clipped when + printing the document. + + + + + + + This method sets the page scaling option that shall be selected when a print dialog is displayed for this + document. + + + This method sets the page scaling option that shall be selected when a print dialog is displayed for this + document. Valid values are None and AppDefault. + + + + + + This method sets the paper handling option that shall be used when printing the file from the print dialog. + + + This method sets the paper handling option that shall be used when printing the file from the print dialog. + The following values are valid: Simplex, DuplexFlipShortEdge, DuplexFlipLongEdge. + + + + + + This method sets PickTrayByPDFSize flag to true or false. + + + + + This method sets the page numbers used to initialize the print dialog box when the file is printed. + + + + + + This method sets the number of copies that shall be printed when the print dialog is opened for this file. + + + + + + Currently active object stream. + + Currently active object stream. + Objects are written to the object stream if fullCompression set to true. + + + + Is used to avoid duplications on object copying. + + Is used to avoid duplications on object copying. + It stores hashes of the indirect reference from the source document and the corresponding + indirect references of the copied objects from the new document. + + + + Is used in smart mode to store serialized objects content. + + + + + + + + + Indicates if to use full compression mode. + true if to use full compression, false otherwise. + + + Gets default compression level for @see PdfStream. + + Gets default compression level for @see PdfStream. + For more details @see + + . + + compression level. + + + Sets default compression level for @see PdfStream. + + Sets default compression level for @see PdfStream. + For more details @see + + . + + compression level. + + + Sets the smart mode. + + Sets the smart mode. +

    + In smart mode when resources (such as fonts, images,...) are + encountered, a reference to these resources is saved + in a cache, so that they can be reused. + This requires more memory, but reduces the file size + of the resulting PDF document. + + + + + + + + + + + + + + + +

    Gets the current object stream. + object stream. + +
    + + Flushes the object. + Flushes the object. Override this method if you want to define custom behaviour for object flushing. + + object to flush. + indicates whether object can be placed into object stream. + on error. + + + Writes object to body of PDF document. + object to write. + + + + Writes PDF header. + + + Flushes all objects which have not been flushed yet. + + + Flushes all modified objects which have not been flushed yet. + Flushes all modified objects which have not been flushed yet. Used in case incremental updates. + + + Calculates hash code for the indirect reference taking into account the document it belongs to. + object to be hashed. + calculated hash code. + + + Calculates hash code for object to be copied. + + Calculates hash code for object to be copied. + The hash code and the copied object is the stored in @{link copiedObjects} hash map to avoid duplications. + + object to be copied. + calculated hash code. + + + Used in the smart mode. + + Used in the smart mode. + It serializes given object content and tries to find previously copied object with the same content. + If already copied object is not found, it saves current object serialized content into the map. + + an object to check if some other object with the same content was already copied. + indirect reference of the object with the same content, which already has a copy in the new document. + + + + + + + Adds indirect reference to list of indirect objects. + indirect reference to add. + + + Creates next available indirect reference. + created indirect reference. + + + Writes cross reference table and trailer to PDF. + + + + + + + Defines the password which will be used if the document is encrypted with standard encryption. + + Defines the password which will be used if the document is encrypted with standard encryption. + This could be either user or owner password. + + the password to use in order to open the document. + + + Defines the certificate which will be used if the document is encrypted with public key encryption. + + + + Defines the certificate which will be used if the document is encrypted with public key encryption. + + + + Defines if the document will be edited in append mode. + + this + + instance + + + + Defines if the encryption of the original document (if it was encrypted) will be preserved. + + Defines if the encryption of the original document (if it was encrypted) will be preserved. + By default, the resultant document doesn't preserve the original encryption. + + + this + + instance + + + + + Internal helper class which is used to effectively build parent tree and also find marked content references: + for specified page, by MCID or by struct parent index. + + + + Represents parentTree in structTreeRoot. + Represents parentTree in structTreeRoot. It contains only those entries that belong to the already flushed pages. + + + + Contains marked content references for every page. + + Contains marked content references for every page. + If new mcrs are added to the tag structure, these new mcrs are also added to this map. So for each adding or + removing mcr, register/unregister calls must be made (this is done automatically if addKid or removeKid methods + of PdfStructElem are used). + Keys in this map are page references, values - a map which contains all mcrs that belong to the given page. + This inner map of mcrs is of following structure: + * for McrDictionary and McrNumber values the keys are their MCIDs; + * for ObjRef values the keys are struct parent indexes, but with one trick. Struct parent indexes and MCIDs have the + same value domains: the increasing numbers starting from zero. So, in order to store them in one map, for + struct parent indexes simple transformation is applied via + #structParentIndexIntoKey + and + #keyIntoStructParentIndex + . With this we simply store struct parent indexes as negative numbers. + + + + Init ParentTreeHandler. + Init ParentTreeHandler. On init the parent tree is read and stored in this instance. + + + Gets a list of marked content references on page. + + + Creates and flushes parent tree entry for the page. + + Creates and flushes parent tree entry for the page. + Effectively this means that new content mustn't be added to the page. + + + + + for which to create parent tree entry. Typically this page is flushed after this call. + + + + Represents Marked Content Reference (MCR) object wrapper. + + + + To be able to be wrapped with this + + the + + must be indirect. + + + + must be an indirect object. + + + Method to to distinguish struct elements from other elements of the logical tree (like mcr or struct tree root). + + + + Gets attributes object. + + sometimes attributes object may not exist. + Pass + + if you want to create empty dictionary in such case. + The attributes dictionary will be stored inside element. + + attributes dictionary. + + + parent of the current structure element. If parent is already flushed it returns null. + + + Gets list of the direct kids of structure element. + + Gets list of the direct kids of structure element. + If certain kid is flushed, there will be a + + in the list on it's place. + + list of the direct kids of structure element. + + + + To be able to be wrapped with this + + the + + must be indirect. + + + + must be an indirect object. + + + Gets list of the direct kids of StructTreeRoot. + + Gets list of the direct kids of StructTreeRoot. + If certain kid is flushed, there will be a + + in the list on it's place. + + list of the direct kids of StructTreeRoot. + + + Creates and flushes parent tree entry for the page. + + Creates and flushes parent tree entry for the page. + Effectively this means that new content mustn't be added to the page. + + + + + for which to create parent tree entry. Typically this page is flushed after this call. + + + + Gets an unmodifiable collection of marked content references on page. + + Gets an unmodifiable collection of marked content references on page. +

    + NOTE: Do not remove tags when iterating over returned collection, this could + lead to the ConcurrentModificationException, because returned collection is backed by the internal list of the + actual page tags. +
    +
    + + + Copies structure to a + + . +

    + NOTE: Works only for + PdfStructTreeRoot + that is read from the document opened in reading mode, + otherwise an exception is thrown. +
    + document to copy structure to. Shall not be current document. + association between original page and copied page. +
    + + + Copies structure to a + + and insert it in a specified position in the document. +

    + NOTE: Works only for + PdfStructTreeRoot + that is read from the document opened in reading mode, + otherwise an exception is thrown. +
    + document to copy structure to. + indicates where the structure to be inserted. + association between original page and copied page. +
    + + Internal helper class which is used to copy tag structure across documents. + + + + Copies structure to a + + . +

    + NOTE: Works only for + PdfStructTreeRoot + that is read from the document opened in reading mode, + otherwise an exception is thrown. +
    + document to copy structure to. Shall not be current document. + association between original page and copied page. +
    + + + Copies structure to a + + and insert it in a specified position in the document. +

    + NOTE: Works only for + PdfStructTreeRoot + that is read from the document opened in reading mode, + otherwise an exception is thrown. +
    + Also, to insert a tagged page into existing tag structure, existing tag structure shouldn't be flushed, otherwise + an exception may be raised. +
    + document to copy structure to. + indicates where the structure to be inserted. + association between original page and copied page. +
    + + + Copies structure to a + + . + + document to cpt structure to. + association between original page and copied page. + + indicates if page2page keys and values represent pages from + + . + + + + the topmost parent added to set. If encountered flushed element - stops and returns this flushed element. + + + + A layout element which can have a role. + + A layout element which can have a role. The name of the role will be + used to tag the element if it is added to a Tagged PDF document. It can also + have + + , metadata for the tag dictionary. + + + + Gets the element's role. + + a + + containing the name of the role + + + + Sets the element's role. + + the new role which the + + should take + + + + + Gets the + accessibility properties + . + + a properties wrapper object specific to a tagged element in Tagged PDF + + + + TagStructureContext + class is used to track necessary information of document's tag structure. + It is also used to make some global modifications of the tag tree like removing or flushing page tags, however + these two methods and also others are called automatically and are for the most part for internal usage. +

    + There shall be only one instance of this class per + PdfDocument + . To obtain instance of this class use + + . +
    +
    + + + These two fields define the connections between tags ( + PdfStructElem + ) and + layout model elements ( + IAccessibleElement + ). This connection is used as + a sign that tag is not yet finished and therefore should not be flushed or removed + if page tags are flushed or removed. Also, any + TagTreePointer + could be + immediately moved to the tag with connection via it's connected element + + . + When connection is removed, accessible element role and properties are set to the structure element. + + + + + Do not use this constructor, instead use + + method. +

    + Creates + TagStructureContext + for document. There shall be only one instance of this + class per + PdfDocument + . +
    + the document which tag structure will be manipulated with this class. +
    + + + If forbidUnknownRoles is set to true, then if you would try to add new tag which has not a standard role and + it's role is not mapped through RoleMap, an exception will be raised. + + + If forbidUnknownRoles is set to true, then if you would try to add new tag which has not a standard role and + it's role is not mapped through RoleMap, an exception will be raised. + Default value - true. + + new value of the flag + + current + + instance. + + + + + All document auto tagging logic uses + + returned by this method to manipulate tag structure. + Typically it points at the root tag. This pointer also could be used to tweak auto tagging process + (e.g. move this pointer to the Sect tag, which would result in placing all automatically tagged content + under Sect tag). + + + the + TagTreePointer + which is used for all auto tagging of the document. + + + + + Checks if given + IAccessibleElement + is connected to some tag. + + element to check if it has a connected tag. + true, if there is a tag which retains the connection to the given accessible element. + + + Destroys the connection between the given accessible element and the tag to which this element is connected to. + + + + IAccessibleElement + which connection to the tag (if there is one) will be removed. + + + current + + instance. + + + + Removes annotation content item from the tag structure. + + Removes annotation content item from the tag structure. + If annotation is not added to the document or is not tagged, nothing will happen. + + + + + instance which points at annotation tag parent if annotation was removed, + otherwise returns null. + + + + Removes content item from the tag structure. + + Removes content item from the tag structure. +
    + Nothing happens if there is no such mcid on given page. +
    + page, which contains this content item + marked content id of this content item + + + TagTreePointer + which points at the parent of the removed content item, or null if there is no + such mcid on given page. + +
    + + Removes all tags that belong only to this page. + + Removes all tags that belong only to this page. The logic which defines if tag belongs to the page is described + at + + . + + page that defines which tags are to be removed + + current + + instance. + + + + + Sets the tag, which is connected with the given accessible element, as a current tag for the given + + . An exception will be thrown, if given accessible element is not connected to any tag. + + an element which has a connection with some tag. + + + + which will be moved to the tag connected to the given accessible element. + + + current + + instance. + + + + Destroys all the retained connections. + + current + + instance. + + + + Flushes the tags which are considered to belong to the given page. + + Flushes the tags which are considered to belong to the given page. + The logic that defines if the given tag (structure element) belongs to the page is the following: + if all the marked content references (dictionary or number references), that are the + descenders of the given structure element, belong to the current page - the tag is considered + to belong to the page. If tag has descenders from several pages - it is flushed, if all other pages except the + current one are flushed. +

    + If some of the page's tags are still connected to the accessible elements, in this case these tags are considered + as not yet finished ones, and they won't be flushed. +
    + a page which tags will be flushed. +
    + + Transforms root tags in a way that complies with the PDF References. + + Transforms root tags in a way that complies with the PDF References. +

    + PDF Reference + 10.7.3 Grouping Elements: +

    + For most content extraction formats, the document must be a tree with a single top-level element; + the structure tree root (identified by the StructTreeRoot entry in the document catalog) must have + only one child in its K (kids) array. If the PDF file contains a complete document, the structure + type Document is recommended for this top-level element in the logical structure hierarchy. If the + file contains a well-formed document fragment, one of the structure types Part, Art, Sect, or Div + may be used instead. +
    +
    + + Method for internal usages. + + Method for internal usages. + Essentially, all it does is just making sure that for connected tags properties are + up to date with connected accessible elements properties. + + + + parent of the flushed tag + + + + + Creates + TagTreePointer + instance. After creation + TagTreePointer + points at the root tag. + + the document, at which tag structure this instance will point. + + + A copy constructor. + + the + TagTreePointer + from which current position and page are copied. + + + + + Sets a page which content will be tagged with this instance of + TagTreePointer + . + To tag page content: +
      +
    1. Set pointer position to the tag which will be the parent of the page content item;
    2. +
    3. Call + + to obtain the reference to the current tag;
    4. +
    5. Pass + PdfTagReference + to the + + method of the page's + + to start marked content item;
    6. +
    7. Draw content on + PdfCanvas + ;
    8. +
    9. Use + + to finish marked content item.
    10. +
    +
    + + the page which content will be tagged with this instance of + TagTreePointer + . + + + this + + instance. + +
    + + + a page which content will be tagged with this instance of + TagTreePointer + . + + + + + Sometimes, tags are desired to be connected with the content that resides not in the page's content stream, + but rather in the some appearance stream or in the form xObject stream. + + + Sometimes, tags are desired to be connected with the content that resides not in the page's content stream, + but rather in the some appearance stream or in the form xObject stream. In that case, to have a valid tag structure, + one shall set not only the page, on which the content will be rendered, but also the content stream in which + the tagged content will reside. +

    + NOTE: It's important to set a + + for this value, when tagging of this stream content is finished. +
    + + the content stream which content will be tagged with this instance of + TagTreePointer + or + + if content stream tagging is finished. + +
    + + + the content stream which content will be tagged with this instance of + TagTreePointer + . + + + + the document, at which tag structure this instance points. + + + Adds a new tag with given role to the tag structure. + + Adds a new tag with given role to the tag structure. + This method call moves this + TagTreePointer + to the added kid. + + role of the new tag. + + this + + instance. + + + + Adds a new tag with given role to the tag structure. + + Adds a new tag with given role to the tag structure. + This method call moves this + TagTreePointer + to the added kid. +
    + This call is equivalent of calling sequentially + + and + + . +
    + zero-based index in kids array of parent tag at which new tag will be added. + role of the new tag. + + this + + instance. + +
    + + Adds a new tag to the tag structure. + + Adds a new tag to the tag structure. + This method call moves this + TagTreePointer + to the added kid. +
    + New tag will have a role and attributes defined by the given IAccessibleElement. +
    + accessible element which represents a new tag. + + this + + instance. + +
    + + Adds a new tag to the tag structure. + + Adds a new tag to the tag structure. + This method call moves this + TagTreePointer + to the added kid. +
    + New tag will have a role and attributes defined by the given IAccessibleElement. +

    + If keepConnectedToTag is true then a newly created tag will retain the connection with given + accessible element. See + + for more explanations about tag connections concept. +

    + If the same accessible element is connected to the tag and is added twice to the same parent - + this + TagTreePointer + instance would move to connected kid instead of creating tag twice. + But if it is added to some other parent, then connection will be removed. +
    + accessible element which represents a new tag. + defines if to retain the connection between accessible element and the tag. + + + this + + instance. + +
    + + Adds a new tag to the tag structure. + + Adds a new tag to the tag structure. + This method call moves this + TagTreePointer + to the added kid. +
    + New tag will have a role and attributes defined by the given IAccessibleElement. + This call is equivalent of calling sequentially + + and + + . +
    + zero-based index in kids array of parent tag at which new tag will be added. + accessible element which represents a new tag. + + this + + instance. + +
    + + + + Adds a new content item for the given + PdfAnnotation + under the current tag. +

    + By default, when annotation is added to the page it is automatically tagged with auto tagging pointer + (see + + ). If you want to add annotation tag manually, be sure to use + + method with false for boolean flag. +
    + + + PdfAnnotation + to be tagged. + + + this + + instance. + +
    + + Sets index of the next added to the current tag kid, which could be another tag or content item. + + Sets index of the next added to the current tag kid, which could be another tag or content item. + By default, new tag is added at the end of the parent kids array. This property affects only the next added tag, + all tags added after will be added with the default behaviour. +

    + This method could be used with any overload of + + method, + with + + and + + . +
    + Keep in mind, that this method set property to the + TagTreePointer + and not to the tag itself, which means + that if you would move the pointer, this property would be applied to the new current tag. +
    + index of the next added kid. + + this + + instance. + +
    + + + Checks if given + IAccessibleElement + is connected to some tag. + See + + for more explanations about tag connections concept. + + element to check if it has a connected tag. + true, if there is a tag which retains the connection to the given accessible element. + + + Destroys the connection between the given accessible element and the tag to which this element is connected to. + + + Destroys the connection between the given accessible element and the tag to which this element is connected to. + See + + for more explanations about tag connections concept. + + + + IAccessibleElement + which connection to the tag (if there is one) will be removed. + + + this + + instance. + + + + Removes the current tag. + + Removes the current tag. If it has kids, they will become kids of the current tag parent. + This method call moves this + TagTreePointer + to the current tag parent. +

    + You cannot remove root tag, and also you cannot remove any tag if document's tag structure was partially flushed; + in this two cases an exception will be thrown. +
    + + this + + instance. + +
    + + + Moves kid of the current tag to the tag at which given + TagTreePointer + points. + This method doesn't change pointerToNewParent position. + + zero-based index of the current tag's kid to be relocated. + + the + TagTreePointer + which is positioned at the tag which will become kid's new parent. + + + this + + instance. + + + + Creates a reference to the current tag, which could be used to associate a content on the PdfCanvas with current tag. + + + Creates a reference to the current tag, which could be used to associate a content on the PdfCanvas with current tag. + See + + and + + . + + the reference to the current tag. + + + Creates a reference to the current tag, which could be used to associate a content on the PdfCanvas with current tag. + + + Creates a reference to the current tag, which could be used to associate a content on the PdfCanvas with current tag. + See + + and + + . + + zero-based index in kids array of tag. These indexes define the logical order of the content on the page. + + the reference to the current tag. + + + + Moves this + TagTreePointer + instance to the document root tag. + + + this + + instance. + + + + + Moves this + TagTreePointer + instance to the parent of the current tag. + + + this + + instance. + + + + + Moves this + TagTreePointer + instance to the kid of the current tag. + + zero-based index of the current tag kid to which pointer will be moved. + + this + + instance. + + + + + Moves this + TagTreePointer + instance to the kid of the current tag. + + + role of the current tag kid to which pointer will be moved. + If there is several kids with this role, pointer will be moved to the first kid with such role. + + + this + + instance. + + + + + Moves this + TagTreePointer + instance to the kid of the current tag. + + + if there is several kids with the given role, pointer will be moved to the kid + which is the n'th if you count kids with such role. + + role of the current tag kid to which pointer will be moved. + + this + + instance. + + + + + Gets current element kids roles. + + Gets current element kids roles. + If certain kid is already flushed, at its position there will be a + + . + If kid is content item, at its position there will be "MCR" (Marked Content Reference). + + current element kids roles + + + + Gets connected accessible element for the current tag. + + Gets connected accessible element for the current tag. If tag is not connected to element, behaviour is defined + by the createIfNotExist flag. + See + + for more explanations about tag connections concept. + + + if true, creates an + IAccessibleElement + and connects it to the tag. + + + connected + IAccessibleElement + if there is one (or if it is created), otherwise null. + + + + Gets accessibility properties of the current tag. + accessibility properties of the current tag. + + + Gets current tag role. + current tag role. + + + Sets new role to the current tag. + new role to be set. + + this + + instance. + + + + Indicates if to use full compression (using object streams). + + + Indicates if the writer copy objects in a smart mode. + + Indicates if the writer copy objects in a smart mode. If so PdfDictionary and PdfStream will be hashed + and reused if there's an object with the same content later. + + + + Defines pdf version for the created document. + Defines pdf version for the created document. Default value is PDF_1_7. + version for the document. + + this + WriterProperties + instance + + + + Enables smart mode. + + Enables smart mode. +

    + In smart mode when resources (such as fonts, images,...) are + encountered, a reference to these resources is saved + in a cache, so that they can be reused. + This requires more memory, but reduces the file size + of the resulting PDF document. + + + this + WriterProperties + instance + + + +

    + If true, default XMPMetadata based on + + will be added. + + + this + WriterProperties + instance + +
    + + Defines the level of compression for the document. + + Defines the level of compression for the document. + See + + + + + this + WriterProperties + instance + + + + Defines if full compression mode is enabled. + + Defines if full compression mode is enabled. If enabled, not only the content of the pdf document will be + compressed, but also the pdf document inner structure. + + true - to enable full compression mode, false to disable it + + this + WriterProperties + instance + + + + Sets the encryption options for the document. + + Sets the encryption options for the document. The userPassword and the + ownerPassword can be null or have zero length. In this case the ownerPassword + is replaced by a random string. The open permissions for the document can be + AllowPrinting, AllowModifyContents, AllowCopy, AllowModifyAnnotations, + AllowFillIn, AllowScreenReaders, AllowAssembly and AllowDegradedPrinting. + The permissions can be combined by ORing them. + See + + . + + the user password. Can be null or empty + the owner password. Can be null or empty + the user permissions + + the type of encryption. It can be one of STANDARD_ENCRYPTION_40, STANDARD_ENCRYPTION_128, + ENCRYPTION_AES128 or ENCRYPTION_AES256 + Optionally DO_NOT_ENCRYPT_METADATA can be ored to output the metadata in cleartext + + + this + WriterProperties + instance + + + + Sets the certificate encryption options for the document. + + Sets the certificate encryption options for the document. An array of one or more public certificates + must be provided together with an array of the same size for the permissions for each certificate. + The open permissions for the document can be + AllowPrinting, AllowModifyContents, AllowCopy, AllowModifyAnnotations, + AllowFillIn, AllowScreenReaders, AllowAssembly and AllowDegradedPrinting. + The permissions can be combined by ORing them. + Optionally DO_NOT_ENCRYPT_METADATA can be ored to output the metadata in cleartext + See + + . + + the public certificates to be used for the encryption + the user permissions for each of the certificates + + the type of encryption. It can be one of STANDARD_ENCRYPTION_40, STANDARD_ENCRYPTION_128, + ENCRYPTION_AES128 or ENCRYPTION_AES256. + + + this + WriterProperties + instance + + + + This activates debug mode with pdfDebug tool. + + This activates debug mode with pdfDebug tool. + It causes additional overhead of duplicating document bytes into memory, so use it careful. + NEVER use it in production or in any other cases except pdfDebug. + + + this + WriterProperties + instance + + + + A wrapper for Form XObject. + A wrapper for Form XObject. ISO 32000-1, 8.10 FormXObjects. + + + An abstract wrapper for supported types of XObject. + + + + + + Create + + or + + by + + . + + + + + with either + + or + + + + + + either + + or + + . + + + + Sets the layer this XObject belongs to. + the layer this XObject belongs to. + + + Gets width of XObject. + float value. + + + Gets height of XObject. + float value. + + + + + + Creates a new instance of Form XObject. + the form XObject’s bounding box. + + + + Create + + instance by + + . + Note, this constructor doesn't perform any additional checks + + + + + with Form XObject. + + + + + Creates form XObject from page content. + + Creates form XObject from page content. + The page shall be from the document, to which FormXObject will be added. + + + an instance of + + + + + + Creates a form XObject from + + . + Unlike other images, + + images are represented as + + , not as + + . + + image to create form object from + document instance which is needed for writing form stream contents + + + + Gets + + of the Form XObject. + Note, if there is no resources, a new instance will be created. + + + not null instance of + + . + + + + + Gets Form XObject's BBox, + + key. + + + a + + , that represents + + . + + + + + Sets Form XObject's BBox, + + key. + + + a + + , that represents + + . + + object itself. + + + + Sets a group attributes dictionary indicating that the contents of the form XObject + shall be treated as a group and specifying the attributes of that group. + + + Sets a group attributes dictionary indicating that the contents of the form XObject + shall be treated as a group and specifying the attributes of that group. + + key. + + + instance of + + . + + object itself. + + + + Gets width based on XObject's BBox. + float value. + + + Gets height based on XObject's BBox. + float value. + + + + To manually flush a + PdfObject + behind this wrapper, you have to ensure + that this object is added to the document, i.e. it has an indirect reference. + Basically this means that before flushing you need to explicitly call + + . + For example: wrapperInstance.makeIndirect(document).flush(); + Note that not every wrapper require this, only those that have such warning in documentation. + + + + + Sets process color model for trap network appearance, + + key. + + + shall be one of the valid values: + + , + + , + + , + + , + + , and + + . + + object itself. + + + + Gets process color model of trap network appearance, + + key. + + + a + + instance, possible values: + + , + + , + + , + + , + + , and + + . + + + + + Sets separation color names for the trap network appearance, + + key. + + + an array of names identifying the colorants that were assumed + when the trap network appearance was created. + + object itself. + + + + Gets separation color names of trap network appearance, + + key. + + + an + + of names identifying the colorants. + + + + + Sets an array of TrapRegion objects defining the page’s trapping zones + and the associated trapping parameters, as described in Adobe Technical Note #5620, + Portable Job Ticket Format. + + + Sets an array of TrapRegion objects defining the page’s trapping zones + and the associated trapping parameters, as described in Adobe Technical Note #5620, + Portable Job Ticket Format. + + key. + + + A + + of indirect references to TrapRegion objects. + + object itself. + + + + Gets an array of TrapRegion objects defining the page’s trapping zones + and the associated trapping parameters, as described in Adobe Technical Note #5620, + Portable Job Ticket Format. + + + Gets an array of TrapRegion objects defining the page’s trapping zones + and the associated trapping parameters, as described in Adobe Technical Note #5620, + Portable Job Ticket Format. + + key. + + + A + + of indirect references to TrapRegion objects. + + + + Sets a human-readable text string that described this trap network to the user. + + Sets a human-readable text string that described this trap network to the user. + + key. + + + a + + value. + + object itself. + + + Gets a human-readable text string that described this trap network to the user. + + Gets a human-readable text string that described this trap network to the user. + + key. + + + a + + value. + + + + Sets a text string representing the printer’s mark in human-readable form. + a string value. + object itself. + + + Gets a text string representing the printer’s mark in human-readable form. + a string value. + + + Puts the value into Image XObject dictionary and associates it with the specified key. + + Puts the value into Image XObject dictionary and associates it with the specified key. + If the key is already present, it will override the old value with the specified one. + + key to insert or to override + the value to associate with the specified key + object itself. + + + A wrapper for Image XObject. + A wrapper for Image XObject. ISO 32000-1, 8.9 Images. + + + Creates Image XObject by image. + + + + with actual image data. + + + + Creates Image XObject by image. + + + + with actual image data. + + + + + with image mask. + + + + + Create + + instance by + + . + Note, this constructor doesn't perform any additional checks + + + + + with Image XObject. + + + + + + Gets width of image, + Width + key. + + float value. + + + + Gets height of image, + Height + key. + + float value. + + + + To manually flush a + PdfObject + behind this wrapper, you have to ensure + that this object is added to the document, i.e. it has an indirect reference. + Basically this means that before flushing you need to explicitly call + + . + For example: wrapperInstance.makeIndirect(document).flush(); + Note, that not every wrapper require this, only those that have such warning in documentation. + + + + Copy Image XObject to the specified document. + target document + + just created instance of + + . + + + + Gets decoded image bytes. + byte array. + + + Gets image bytes. + + Gets image bytes. + Note, + + , + + and + + filters will be ignored. + + + if + + , decodes stream bytes. + + byte array. + + + + Identifies the type of the image that is stored in the bytes of this + + . + Note that this has nothing to do with the original type of the image. For instance, the return value + of this method will never be + + as we loose this information when converting a + PNG image into something that can be put into a PDF file. + The possible values are: + + , + + , + + , + + , + + + the identified type of image + + + + Identifies recommended file extension to store the bytes of this + + . + Possible values are: 'png', 'jpg', 'jp2', 'tif', 'jbig2'. + This extension can later be used together with the result of + + . + + + a + + with recommended file extension + + + + + Puts the value into Image XObject dictionary and associates it with the specified key. + + Puts the value into Image XObject dictionary and associates it with the specified key. + If the key is already present, it will override the old value with the specified one. + + key to insert or to override + the value to associate with the specified key + object itself. + + + + + + Sets state of this object according to the color space + the colorspace to use + whether indexed color spaces will be resolved (used for recursive call) + if there is a problem with reading from the underlying stream + + + Determining the initial backdrop against which its stack is composited. + + + + Determining whether the objects within the stack are composited with one another or only with the group's backdrop. + + + + + + This class provides means to compare two PDF files both by content and visually + and gives the report of their differences. + + + This class provides means to compare two PDF files both by content and visually + and gives the report of their differences. +

    + For visual comparison it uses external tools: Ghostscript and ImageMagick, which + should be installed on your machine. To allow CompareTool to use them, you need + to pass either java properties or environment variables with names "gsExec" and + "compareExec", which would contain the paths to the executables of correspondingly + Ghostscript and ImageMagick tools. +

    + CompareTool class was mainly designed for the testing purposes of iText in order to + ensure that the same code produces the same PDF document. For this reason you will + often encounter such parameter names as "outDoc" and "cmpDoc" which stand for output + document and document-for-comparison. The first one is viewed as the current result, + and the second one is referred as normal or ideal result. OutDoc is compared to the + ideal cmpDoc. Therefore all reports of the comparison are in the form: "Expected ..., + but was ...". This should be interpreted in the following way: "expected" part stands + for the content of the cmpDoc and "but was" part stands for the content of the outDoc. +
    +
    + + Creates an instance of the CompareTool. + + + + Compares two PDF documents by content starting from Catalog dictionary and then recursively comparing + corresponding objects which are referenced from it. + + + Compares two PDF documents by content starting from Catalog dictionary and then recursively comparing + corresponding objects which are referenced from it. You can roughly imagine it as depth-first traversal + of the two trees that represent pdf objects structure of the documents. +

    + The main difference between this method and the + + methods is the return value. This method returns a + + class instance, which could be used + in code, however compareByContent methods in case of the differences simply return String value, which could + only be printed. Also, keep in mind that this method doesn't perform visual comparison of the documents. +

    + For more explanations about what is outDoc and cmpDoc see last paragraph of the + + class description. +
    + the absolute path to the output file, which is to be compared to cmp-file. + the absolute path to the cmp-file, which is to be compared to output file. + + the report of comparison of two files in the form of the custom class instance. + See + + for more info. + + +
    + + + Sets the maximum errors count which will be returned as the result of the comparison. + the errors count. + this CompareTool instance. + + + Enables or disables the generation of the comparison report in the form of the xml document. + + Enables or disables the generation of the comparison report in the form of the xml document. +
    + IMPORTANT NOTE: this flag affect only the comparison made by compareByContent methods! +
    + true to enable xml report generation, false - to disable. + this CompareTool instance. +
    + + Enables the comparison of the encryption properties of the documents. + + Enables the comparison of the encryption properties of the documents. Encryption properties comparison + results are returned along with all other comparison results. +
    + IMPORTANT NOTE: this flag affect only the comparison made by compareByContent methods! +
    + this CompareTool instance. +
    + + + + Compares two documents visually. + + Compares two documents visually. For the comparison two external tools are used: Ghostscript and ImageMagick. + For more info about needed configuration for visual comparison process see + + class description. +
    + During comparison for every page of two documents an image file will be created in the folder specified by + outPath absolute path. Then those page images will be compared and if there are any differences for some pages, + another image file will be created with marked differences on it. +
    + the absolute path to the output file, which is to be compared to cmp-file. + the absolute path to the cmp-file, which is to be compared to output file. + the absolute path to the folder, which will be used to store image files for visual comparison. + + file name prefix for image files with marked differences if there is any. + + string containing list of the pages that are visually different, or null if there are no visual differences. + + + +
    + + Compares two documents visually. + + Compares two documents visually. For the comparison two external tools are used: Ghostscript and ImageMagick. + For more info about needed configuration for visual comparison process see + + class description. +
    + During comparison for every page of two documents an image file will be created in the folder specified by + outPath absolute path. Then those page images will be compared and if there are any differences for some pages, + another image file will be created with marked differences on it. +
    + It is possible to ignore certain areas of the document pages during visual comparison. This is useful for example + in case if documents should be the same except certain page area with date on it. In this case, in the folder + specified by the outPath, new pdf documents will be created with the black rectangles at the specified ignored + areas, and visual comparison will be performed on these new documents. +
    + the absolute path to the output file, which is to be compared to cmp-file. + the absolute path to the cmp-file, which is to be compared to output file. + the absolute path to the folder, which will be used to store image files for visual comparison. + + file name prefix for image files with marked differences if there is any. + + a map with one-based page numbers as keys and lists of ignored rectangles as values. + + string containing list of the pages that are visually different, or null if there are no visual differences. + + + +
    + + + Compares two PDF documents by content starting from page dictionaries and then recursively comparing + corresponding objects which are referenced from them. + + + Compares two PDF documents by content starting from page dictionaries and then recursively comparing + corresponding objects which are referenced from them. You can roughly imagine it as depth-first traversal + of the two trees that represent pdf objects structure of the documents. +

    + Unlike + + this method performs content comparison page by page + and doesn't compare the tag structure, acroforms and all other things that doesn't belong to specific pages. +
    + When comparison by content is finished, if any differences were found, visual comparison is automatically started. + For more info see + + . +

    + For more explanations about what is outPdf and cmpPdf see last paragraph of the + + class description. +
    + the absolute path to the output file, which is to be compared to cmp-file. + the absolute path to the cmp-file, which is to be compared to output file. + the absolute path to the folder, which will be used to store image files for visual comparison. + + file name prefix for image files with marked visual differences if there is any. + + + string containing text report of the encountered content differences and also list of the pages that are + visually different, or null if there are no content and therefore no visual differences. + + + +
    + + This method overload is used to compare two encrypted PDF documents. + + This method overload is used to compare two encrypted PDF documents. Document passwords are passed with + outPass and cmpPass parameters. +

    + Compares two PDF documents by content starting from page dictionaries and then recursively comparing + corresponding objects which are referenced from them. You can roughly imagine it as depth-first traversal + of the two trees that represent pdf objects structure of the documents. +

    + Unlike + + this method performs content comparison page by page + and doesn't compare the tag structure, acroforms and all other things that doesn't belong to specific pages. +
    + When comparison by content is finished, if any differences were found, visual comparison is automatically started. + For more info see + + . +

    + For more explanations about what is outPdf and cmpPdf see last paragraph of the + + class description. +
    + the absolute path to the output file, which is to be compared to cmp-file. + the absolute path to the cmp-file, which is to be compared to output file. + the absolute path to the folder, which will be used to store image files for visual comparison. + + file name prefix for image files with marked visual differences if there is any. + + password for the encrypted document specified by the outPdf absolute path. + password for the encrypted document specified by the cmpPdf absolute path. + + string containing text report of the encountered content differences and also list of the pages that are + visually different, or null if there are no content and therefore no visual differences. + + + +
    + + + Compares two PDF documents by content starting from page dictionaries and then recursively comparing + corresponding objects which are referenced from them. + + + Compares two PDF documents by content starting from page dictionaries and then recursively comparing + corresponding objects which are referenced from them. You can roughly imagine it as depth-first traversal + of the two trees that represent pdf objects structure of the documents. +

    + Unlike + + this method performs content comparison page by page + and doesn't compare the tag structure, acroforms and all other things that doesn't belong to specific pages. +
    + When comparison by content is finished, if any differences were found, visual comparison is automatically started. + For more info see + + . +

    + For more explanations about what is outPdf and cmpPdf see last paragraph of the + + class description. +
    + the absolute path to the output file, which is to be compared to cmp-file. + the absolute path to the cmp-file, which is to be compared to output file. + the absolute path to the folder, which will be used to store image files for visual comparison. + + file name prefix for image files with marked visual differences if there is any. + + a map with one-based page numbers as keys and lists of ignored rectangles as values. + + + string containing text report of the encountered content differences and also list of the pages that are + visually different, or null if there are no content and therefore no visual differences. + + + +
    + + This method overload is used to compare two encrypted PDF documents. + + This method overload is used to compare two encrypted PDF documents. Document passwords are passed with + outPass and cmpPass parameters. +

    + Compares two PDF documents by content starting from page dictionaries and then recursively comparing + corresponding objects which are referenced from them. You can roughly imagine it as depth-first traversal + of the two trees that represent pdf objects structure of the documents. +

    + Unlike + + this method performs content comparison page by page + and doesn't compare the tag structure, acroforms and all other things that doesn't belong to specific pages. +
    + When comparison by content is finished, if any differences were found, visual comparison is automatically started. + For more info see + + . +

    + For more explanations about what is outPdf and cmpPdf see last paragraph of the + + class description. +
    + the absolute path to the output file, which is to be compared to cmp-file. + the absolute path to the cmp-file, which is to be compared to output file. + the absolute path to the folder, which will be used to store image files for visual comparison. + + file name prefix for image files with marked visual differences if there is any. + + a map with one-based page numbers as keys and lists of ignored rectangles as values. + + password for the encrypted document specified by the outPdf absolute path. + password for the encrypted document specified by the cmpPdf absolute path. + + string containing text report of the encountered content differences and also list of the pages that are + visually different, or null if there are no content and therefore no visual differences. + + + +
    + + Simple method that compares two given PdfDictionaries by content. + + Simple method that compares two given PdfDictionaries by content. This is "deep" comparing, which means that all + nested objects are also compared by content. + + dictionary to compare. + dictionary to compare. + true if dictionaries are equal by content, otherwise false. + + + + Simple method that compares two given PdfStreams by content. + + Simple method that compares two given PdfStreams by content. This is "deep" comparing, which means that all + nested objects are also compared by content. + + stream to compare. + stream to compare. + true if stream are equal by content, otherwise false. + + + + Simple method that compares two given PdfArrays by content. + + Simple method that compares two given PdfArrays by content. This is "deep" comparing, which means that all + nested objects are also compared by content. + + array to compare. + array to compare. + true if arrays are equal by content, otherwise false. + + + + Simple method that compares two given PdfNames. + name to compare. + name to compare. + true if names are equal, otherwise false. + + + Simple method that compares two given PdfNumbers. + number to compare. + number to compare. + true if numbers are equal, otherwise false. + + + Simple method that compares two given PdfStrings. + string to compare. + string to compare. + true if strings are equal, otherwise false. + + + Simple method that compares two given PdfBooleans. + boolean to compare. + boolean to compare. + true if booleans are equal, otherwise false. + + + Compares xmp metadata of the two given PDF documents. + the absolute path to the output file, which xmp is to be compared to cmp-file. + the absolute path to the cmp-file, which xmp is to be compared to output file. + text report of the xmp differences, or null if there are no differences. + + + Compares xmp metadata of the two given PDF documents. + the absolute path to the output file, which xmp is to be compared to cmp-file. + the absolute path to the cmp-file, which xmp is to be compared to output file. + + true, if to ignore differences in date or producer xmp metadata + properties. + + text report of the xmp differences, or null if there are no differences. + + + Utility method that provides simple comparison of the two xml files stored in byte arrays. + first xml file data to compare. + second xml file data to compare. + true if xml structures are identical, false otherwise. + + + + + + Utility method that provides simple comparison of the two xml files. + absolute path to the first xml file to compare. + absolute path to the second xml file to compare. + true if xml structures are identical, false otherwise. + + + + + + This method overload is used to compare two encrypted PDF documents. + + This method overload is used to compare two encrypted PDF documents. Document passwords are passed with + outPass and cmpPass parameters. +

    + Compares document info dictionaries of two pdf documents. +
    + the absolute path to the output file, which info is to be compared to cmp-file info. + the absolute path to the cmp-file, which info is to be compared to output file info. + password for the encrypted document specified by the outPdf absolute path. + password for the encrypted document specified by the cmpPdf absolute path. + text report of the differences in documents infos. + +
    + + Compares document info dictionaries of two pdf documents. + the absolute path to the output file, which info is to be compared to cmp-file info. + the absolute path to the cmp-file, which info is to be compared to output file info. + text report of the differences in documents infos. + + + + Compares if two documents has identical link annotations on corresponding pages. + the absolute path to the output file, which links are to be compared to cmp-file links. + + the absolute path to the cmp-file, which links are to be compared to output file links. + + text report of the differences in documents links. + + + + Compares tag structures of the two PDF documents. + + Compares tag structures of the two PDF documents. +
    + This method creates xml files in the same folder with outPdf file. These xml files contain documents tag structures + converted into the xml structure. These xml files are compared if they are equal. +
    + the absolute path to the output file, which tags are to be compared to cmp-file tags. + + the absolute path to the cmp-file, which tags are to be compared to output file tags. + + text report of the differences in documents tags. + + + +
    + + + + + + + + + + + + + + + + + Runs ghostscript to create images of pdfs. + Path to the output folder. + Returns null if result is successful, else returns error message. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Class containing results of the comparison of two documents. + + + Creates new empty instance of CompareResult with given limit of difference messages. + maximum number of difference messages handled by this CompareResult. + + + Is used to define if documents are considered equal after comparison. + true if documents are equal, false otherwise. + + + Returns number of differences between two documents met during comparison. + number of differences. + + + Converts this CompareResult into text form. + text report of the differences between two documents. + + + + Returns map with + + as keys and difference descriptions as values. + + differences map which could be used to find in the document objects that are different. + + + Converts this CompareResult into xml form. + output stream to which xml report will be written. + + + + + + Creates empty ObjectPath. + + + Creates ObjectPath with corresponding base objects in two documents. + base object in cmp document. + base object in out document. + + + + Creates a new ObjectPath instance with two new given base objects, which are supposed to be nested in the base + objects of the current instance of the ObjectPath. + + + Creates a new ObjectPath instance with two new given base objects, which are supposed to be nested in the base + objects of the current instance of the ObjectPath. This method is used to avoid infinite loop in case of + circular references in pdf documents objects structure. +
    + Basically, this method creates copy of the current ObjectPath instance, but resets information of the direct + paths, and also adds current ObjectPath instance base objects to the indirect references chain that denotes + a path to the new base objects. +
    + new base object in cmp document. + new base object in out document. + + new ObjectPath instance, which stores chain of the indirect references which were already met to get + to the new base objects. + +
    + + This method is used to define if given objects were already met in the path to the current base objects. + + + This method is used to define if given objects were already met in the path to the current base objects. + If this method returns true it basically means that we found a loop in the objects structure and that we + already compared these objects. + + cmp object to check if it was already met in base objects path. + out object to check if it was already met in base objects path. + true if given objects are contained in the path and therefore were already compared. + + + Adds array item to the direct path. + + Adds array item to the direct path. See + + . + + index in the array of the direct object to be compared. + + + Adds dictionary item to the direct path. + + Adds dictionary item to the direct path. See + + . + + key in the dictionary to which corresponds direct object to be compared. + + + Adds offset item to the direct path. + + Adds offset item to the direct path. See + + . + + offset to the specific byte in the stream that is compared. + + + Removes the last path item from the direct path. + + + + Gets local (or direct) path that denotes sequence of the path items from base object to the comparing + direct object. + + direct path to the comparing object. + + + + Gets indirect path which denotes sequence of the indirect references that were passed in comparing process + to get to the current base objects. + + indirect path to the current base objects. + + + current base object in the cmp document. + + + current base object in the out document. + + + Creates an xml node that describes a direct path stored in this ObjectPath instance. + xml document, to which this xml node will be added. + an xml node describing direct path. + + + string representation of the direct path stored in this ObjectPath instance. + + + + An item in the indirect path (see + + . It encapsulates two corresponding objects from the two + comparing documents that were met to get to the path base objects during comparing process. + + + + Creates IndirectPathItem instance for two corresponding objects from two comparing documents. + an object from the cmp document. + an object from the out document. + + + an object from the cmp object that was met to get to the path base objects during comparing process. + + + + an object from the out object that was met to get to the path base objects during comparing process. + + + + + An abstract class for the items in the direct path (see + + . + + + + Creates an xml node that describes this direct path item. + xml document, to which this xml node will be added. + an xml node describing direct path item. + + + + Direct path item (see + + , which describes transition to the + + entry which value is now a currently comparing direct object. + + + + + Creates an instance of the + + . + + + the key which defines to which entry of the + + the transition was performed. + + + + + The key which defines to which entry of the + + the transition was performed. + See + + for more info. + + + a + + which is the key which defines to which entry of the dictionary + the transition was performed. + + + + + Direct path item (see + + , which describes transition to the + + element which is now a currently comparing direct object. + + + + + Creates an instance of the + + . + + + the index which defines element of the + + to which + the transition was performed. + + + + + The index which defines element of the + + to which the transition was performed. + See + + for more info. + + the index which defines element of the array to which the transition was performed + + + + Direct path item (see + + , which describes transition to the + specific position in + + . + + + + + Creates an instance of the + + . + + + bytes offset to the specific position in + + . + + + + + The bytes offset of the stream which defines specific position in the + + , to which transition + was performed. + + an integer defining bytes offset to the specific position in stream. + + + + Class representing a page range, for instance a page range can contain + pages 5, then pages 10 through 15, then page 18, then page 21 and so on. + + + + + Constructs an empty + + instance. + + + + + Constructs a + + instance from a range in a string form, for example: "1-12, 15, 45-66". + + the page range + + + Adds a page sequence to the range. + the starting page number of the sequence + the finishing page number of the sequnce + this range, already modified + + + Adds a single page to the range. + the page number to add + this range, already modified + + + Gets the list if pages that have been added to the range so far. + the list containing page numbers added to the range + + + Checks if a given page is present in the range built so far. + the page number to check + true if the page is present in this range, false otherwise + + + + + + + + + This class is used to merge a number of existing documents into one. + + This class is used to merge a number of existing documents into one. By default, if source document + contains tags and outlines, they will be also copied to the destination document. + + the document into which source documents will be merged. + + + This class is used to merge a number of existing documents into one. + the document into which source documents will be merged. + + if true, then tags from the source document are copied even if destination document is not set as + tagged. Note, that if false, tag structure is still could be copied if the destination document + is explicitly marked as tagged with + + . + + + if true, then outlines from the source document are copied even if in destination document + outlines are not initialized. Note, that if false, outlines are still could be copied if the + destination document outlines were explicitly initialized with + + . + + + + + If set to true then passed to the + PdfMerger#merge + method source documents will be closed + immediately after merging specified pages into current document. If false - PdfDocuments are left open. + Default value - false. + + should be true to close pdf documents in merge method. + + this + PdfMerger + instance. + + + + This method merges pages from the source document to the current one. + + This method merges pages from the source document to the current one. +

    + If closeSourceDocuments flag is set to true (see + + ), + passed + PdfDocument + will be closed after pages are merged. +
    + - document, from which pages will be copied. + - start page in the range of pages to be copied. + - end page in the range to be copied. + + this + PdfMerger + instance. + +
    + + This method merges pages from the source document to the current one. + + This method merges pages from the source document to the current one. +

    + If closeSourceDocuments flag is set to true (see + + ), + passed + PdfDocument + will be closed after pages are merged. +
    + - document, from which pages will be copied. + - List of numbers of pages which will be copied. + + this + PdfMerger + instance. + +
    + + Closes the current document. + + Closes the current document. It is a complete equivalent of calling + PdfDocument#close + on the PdfDocument + passed to the constructor of this PdfMerger instance. This means that it is enough to call close either on + passed PdfDocument or on this PdfMerger instance, but there is no need to call them both. + + + + + This class can be used to count the number of bytes needed when copying + pages from an existing PDF into a newly created PDF. + + + + A map of the resources that are already taken into account + + + + Creates a PdfResourceCounter instance to be used to count the resources + needed for either a page (in this case pass a page dictionary) or the + trailer (root and info dictionary) of a PDF file. + + the object we want to examine + + + Processes an object. + + Processes an object. If the object is indirect, it is added to the + list of resources. If not, it is just processed. + + the object to process + + + + In case an object is an array, a dictionary or a stream, + we need to loop over the entries and process them one by one. + + the object to examine + + + Returns a map with the resources. + the resources + + + + Returns the resources needed for the object that was used to create + this PdfResourceCounter. + + + Returns the resources needed for the object that was used to create + this PdfResourceCounter. If you pass a Map with resources that were + already used by other objects, these objects will not be taken into + account. + + The resources that can be excluded when counting the bytes. + The number of bytes needed for an object. + + + Creates a new instance of PdfSplitter class. + the document to be split. + + + If original document is tagged, then by default all resultant document will also be tagged. + + If original document is tagged, then by default all resultant document will also be tagged. + This could be changed with this flag - if set to false, resultant documents will be not tagged, even if + original document is tagged. + + + + If original document has outlines, then by default all resultant document will also have outlines. + + + If original document has outlines, then by default all resultant document will also have outlines. + This could be changed with this flag - if set to false, resultant documents won't contain outlines, even if + original document had them. + + + + Splits the document basing on the given size. + Preferred size for splitting. + + The documents which the source document was split into. + Be warned that these documents are not closed. + + + + Splits the document by page numbers. + + the numbers of pages from which another document is to be started. + If the first element is not 1, then 1 is implied (i.e. the first split document will start from page 1 in any case). + + + the event listener which is called when another document is ready. + You can close this document in this listener, for instance. + + + + Splits the document by page numbers. + + the numbers of pages from which another document is to be started. + If the first element is not 1, then 1 is implied (i.e. the first split document will start from page 1 in any case). + + the list of resultant documents. By warned that they are not closed. + + + Splits a document into smaller documents with no more than @pageCount pages each. + the biggest possible number of pages in a split document. + + the event listener which is called when another document is ready. + You can close this document in this listener, for instance. + + + + Splits a document into smaller documents with no more than @pageCount pages each. + the biggest possible number of pages in a split document. + the list of resultant documents. By warned that they are not closed. + + + Extracts the specified page ranges from a document. + the list of page ranges for each of the resultant document. + + the list of the resultant documents for each of the specified page range. + Be warned that these documents are not closed. + + + + Extracts the specified page ranges from a document. + the page range to be extracted from the document. + + the resultant document containing the pages specified by the provided page range. + Be warned that this document is not closed. + + + + This method is called when another split document is to be created. + + This method is called when another split document is to be created. + You can override this method and return your own + PdfWriter + depending on your needs. + + the page range of the original document to be included in the document being created now. + + the PdfWriter instance for the document which is being created. + + + + Split a document by outline title (bookmark name), find outline by name + and places the entire hierarchy in a separate document ( outlines and pages ) . + + list of outline titles . + + + the next element in the entire hierarchy + + + + Converts a tagged PDF document into an XML file. + + + + Constructs a + + via a given + + . + + the document to read tag structure from + + + Checks if a character value should be escaped/unescaped. + a character value + true if it's OK to escape or unescape this value + + + Converts the current tag structure into an XML file with default encoding (UTF-8). + the output stream to save XML file to + + + + Converts the current tag structure into an XML file with provided encoding. + the output stream to save XML file to + the charset of the resultant XML file + + + + Sets the name of the root tag of the resultant XML file + the name of the root tag + this object + + + + NOTE: copied from itext5 XMLUtils class + Escapes a string with the appropriated XML codes. + + the string to be escaped + codes above 127 will always be escaped with &#nn; if true + the escaped string + + + This class contains version information about iText. + + This class contains version information about iText. + DO NOT CHANGE THE VERSION INFORMATION WITHOUT PERMISSION OF THE COPYRIGHT HOLDERS OF ITEXT. + Changing the version makes it extremely difficult to debug an application. + Also, the nature of open source software is that you honor the copyright of the original creators of the software. + + + + String that will indicate if the AGPL version is used. + + + The iText version instance. + + + This String contains the name of the product. + + This String contains the name of the product. + iText is a registered trademark by iText Group NV. + Please don't change this constant. + + + + This String contains the version number of this iText release. + + This String contains the version number of this iText release. + For debugging purposes, we request you NOT to change this constant. + + + + This String contains the iText version as shown in the producer line. + + This String contains the iText version as shown in the producer line. + iText is a product developed by iText Group NV. + iText Group requests that you retain the iText producer line + in every PDF that is created or manipulated using iText. + + + + The license key. + + + Gets an instance of the iText version that is currently used. + + Gets an instance of the iText version that is currently used. + Note that iText Group requests that you retain the iText producer line + in every PDF that is created or manipulated using iText. + + + + Checks if the AGPL version is used. + returns true if the AGPL version is used. + + + Is the license expired? + true if expired + + + Gets the product name. + + Gets the product name. + iText Group NV requests that you retain the iText producer line + in every PDF that is created or manipulated using iText. + + the product name + + + Gets the release number. + + Gets the release number. + iText Group NV requests that you retain the iText producer line + in every PDF that is created or manipulated using iText. + + the release number + + + Returns the iText version as shown in the producer line. + + Returns the iText version as shown in the producer line. + iText is a product developed by iText Group NV. + iText Group requests that you retain the iText producer line + in every PDF that is created or manipulated using iText. + + iText version + + + Returns a license key if one was provided, or null if not. + a license key. + + + + A wrapper for an Encoding to suppress the preamble. + + + + Translates a IANA encoding name to a Java encoding. + + + The object that maps IANA to Java encodings. + + + + A utility class to perform base64 encoding and decoding as specified + in RFC-1521. + + + A utility class to perform base64 encoding and decoding as specified + in RFC-1521. See also RFC 1421. + + $Revision: 1.4 $ + + + marker for invalid bytes + + + marker for accepted whitespace bytes + + + marker for an equal symbol + + + Encode the given byte[]. + the source string. + the base64-encoded data. + + + Encode the given byte[]. + the source string. + + a linefeed is added after linefeed characters; + must be dividable by four; 0 means no linefeeds + + the base64-encoded data. + + + Encode the given string. + the source string. + the base64-encoded string. + + + Decode the given byte[]. + the base64-encoded data. + the decoded data. + + Thrown if the base 64 strings contains non-valid characters, + beside the bas64 chars, LF, CR, tab and space are accepted. + + + + Decode the given string. + the base64-encoded string. + the decoded string. + + + Byte buffer container including length of valid data. + 11.10.2006 + + + the initial capacity for this buffer + + + a byte array that will be wrapped with ByteBuffer. + + + a byte array that will be wrapped with ByteBuffer. + the length of valid bytes in the array + + + Loads the stream into a buffer. + an InputStream + If the stream cannot be read. + + + a byte array that will be wrapped with ByteBuffer. + the offset of the provided buffer. + the length of valid bytes in the array + + + Returns a byte stream that is limited to the valid amount of bytes. + + + + Returns the length, that means the number of valid bytes, of the buffer; + the inner byte array might be bigger than that. + + + + the index to retrieve the byte from + Returns a byte from the buffer + + + the index to retrieve a byte as int or char. + Returns a byte from the buffer + + + Appends a byte to the buffer. + a byte + + + Appends a byte array or part of to the buffer. + a byte array + an offset with + + + + Append a byte array to the buffer + a byte array + + + Append another buffer to this buffer. + another ByteBuffer + + + Detects the encoding of the byte buffer, stores and returns it. + + Detects the encoding of the byte buffer, stores and returns it. + Only UTF-8, UTF-16LE/BE and UTF-32LE/BE are recognized. + Note: UTF-32 flavors are not supported by Java, the XML-parser will complain. + + Returns the encoding string. + + + + Ensures the requested capacity by increasing the buffer size when the + current length is exceeded. + + requested new buffer length + + + + An OutputStream that counts the written bytes. + + @since 08.11.2006 + + + + + the decorated output stream + + + + the byte counter + + + + Constructor with providing the output stream to decorate. + an OutputStream + + + the bytesWritten + + + + + + 22.08.2006 + + + the state of the automaton + + + the result of the escaping sequence + + + count the digits of the sequence + + + The look-ahead size is 6 at maximum (&#xAB;) + + a Reader + + + + + + + Processes numeric escaped chars to find out if they are a control character. + a char + Returns the char directly or as replacement for the escaped sequence. + + + Converts between ISO 8601 Strings and Calendar with millisecond resolution. + + 16.02.2006 + + + Hides public constructor + + + + a date string that is ISO 8601 conform. + an existing XMPDateTime to set with the parsed date + Returns an XMPDateTime-object containing the ISO8601-date. + Is thrown when the string is non-conform. + + + + + 22.08.2006 + + + initializes the parser container + + + Returns the length of the input. + + + Returns whether there are more chars to come. + + + index of char + Returns char at a certain index. + + + Returns the current char or 0x0000 if there are no more chars. + + + Skips the next char. + + + Returns the current position. + + + Parses a integer from the source and sets the pointer after it. + Error message to put in the exception if no number can be found + + the max value of the number to return + Returns the parsed integer. + Thrown if no integer can be found. + + + + 12.10.2006 + + + Private constructor + + + + + Converts a Cp1252 char (contains all Latin-1 chars above 0x80) into a + UTF-8 byte sequence. + + + Converts a Cp1252 char (contains all Latin-1 chars above 0x80) into a + UTF-8 byte sequence. The bytes 0x81, 0x8D, 0x8F, 0x90, and 0x9D are + formally undefined by Windows 1252 and therefore replaced by a space + (0x20). + + an Cp1252 / Latin-1 byte + Returns a byte array containing a UTF-8 byte sequence. + + + 11.08.2006 + + + Common constants for the XMP Toolkit. + 20.01.2006 + + + The XML namespace for XML. + + + The XML namespace for RDF. + + + The XML namespace for the Dublin Core schema. + + + The XML namespace for the IPTC Core schema. + + + The XML namespace for the IPTC Extension schema. + + + The XML namespace for the DICOM medical schema. + + + The XML namespace for the PLUS (Picture Licensing Universal System, http://www.useplus.org) + + + The XML namespace Adobe XMP Metadata. + + + The XML namespace for the XMP "basic" schema. + + + The XML namespace for the XMP copyright schema. + + + The XML namespace for the XMP digital asset management schema. + + + The XML namespace for the job management schema. + + + The XML namespace for the job management schema. + + + The XML namespace for the PDF schema. + + + The XML namespace for the PDF schema. + + + The XML namespace for the Photoshop custom schema. + + + The XML namespace for the Photoshop Album schema. + + + The XML namespace for Adobe's EXIF schema. + + + NS for the CIPA XMP for Exif document v1.1 + + + The XML namespace for Adobe's TIFF schema. + + + BExt Schema + + + RIFF Info Schema + + + Transform XMP + + + Adobe Flash SWF + + + legacy Dublin Core NS, will be converted to NS_DC + + + The XML namespace for qualifiers of the xmp:Identifier property. + + + The XML namespace for fields of the Dimensions type. + + + The XML namespace for fields of a graphical image. + The XML namespace for fields of a graphical image. Used for the Thumbnail type. + + + The XML namespace for fields of the ResourceEvent type. + + + The XML namespace for fields of the ResourceRef type. + + + The XML namespace for fields of the Version type. + + + The XML namespace for fields of the JobRef type. + + + The canonical true string value for Booleans in serialized XMP. + + The canonical true string value for Booleans in serialized XMP. Code that converts from the + string to a bool should be case insensitive, and even allow "1". + + + + The canonical false string value for Booleans in serialized XMP. + + The canonical false string value for Booleans in serialized XMP. Code that converts from the + string to a bool should be case insensitive, and even allow "0". + + + + Index that has the meaning to be always the last item in an array. + + + Node name of an array item. + + + The x-default string for localized properties + + + xml:lang qualfifier + + + rdf:type qualfifier + + + Processing Instruction (PI) for xmp packet + + + XMP meta tag version new + + + XMP meta tag version old + + + Part, 1, 2, or 3 + + + Conformance, A, B, or U. + + + private constructor + + + Asserts that an array name is set. + an array name + Array name is null or empty + + + Asserts that a property name is set. + a property name or path + Property name is null or empty + + + Asserts that a schema namespace is set. + a schema namespace + Schema is null or empty + + + Asserts that a prefix is set. + a prefix + Prefix is null or empty + + + Asserts that a specific language is set. + a specific lang + Specific language is null or empty + + + Asserts that a struct name is set. + a struct name + Struct name is null or empty + + + Asserts that any string parameter is set. + any string parameter + Thrown if the parameter is null or has length 0. + + + + Asserts that the xmp object is of this implemention + ( + + ). + + the XMP object + A wrong implentaion is used. + + + Parser for "normal" XML serialisation of RDF. + 14.07.2006 + + + Start of coreSyntaxTerms. + + + End of coreSyntaxTerms + + + Start of additions for syntax Terms. + + + End of of additions for syntaxTerms. + + + Start of oldTerms. + + + End of oldTerms. + + + ! Yes, the syntax terms include the core terms. + + + this prefix is used for default namespaces + + + The main parsing method. + + The main parsing method. The XML tree is walked through from the root node and and XMP tree + is created. This is a raw parse, the normalisation of the XMP tree happens outside. + + the XML root node + Returns an XMP metadata object (not normalized) + Occurs if the parsing fails for any reason. + + + + + Each of these parsing methods is responsible for recognizing an RDF + syntax production and adding the appropriate structure to the XMP tree. + + + Each of these parsing methods is responsible for recognizing an RDF + syntax production and adding the appropriate structure to the XMP tree. + They simply return for success, failures will throw an exception. + + the xmp metadata object that is generated + the top-level xml node + thown on parsing errors + + + + 7.2.10 nodeElementList
    + ws* ( nodeElement ws* ) + Note: this method is only called from the rdf:RDF-node (top level) +
    + the xmp metadata object that is generated + the parent xmp node + the top-level xml node + thown on parsing errors +
    + + + 7.2.5 nodeElementURIs + anyURI - ( coreSyntaxTerms | rdf:li | oldTerms ) + 7.2.11 nodeElement + start-element ( URI == nodeElementURIs, + attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) ) + propertyEltList + end-element() + A node element URI is rdf:Description or anything else that is not an RDF + term. + + the xmp metadata object that is generated + the parent xmp node + the currently processed XML node + Flag if the node is a top-level node + thown on parsing errors + + + + 7.2.7 propertyAttributeURIs + anyURI - ( coreSyntaxTerms | rdf:Description | rdf:li | oldTerms ) + 7.2.11 nodeElement + start-element ( URI == nodeElementURIs, + attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) ) + propertyEltList + end-element() + Process the attribute list for an RDF node element. + + + 7.2.7 propertyAttributeURIs + anyURI - ( coreSyntaxTerms | rdf:Description | rdf:li | oldTerms ) + 7.2.11 nodeElement + start-element ( URI == nodeElementURIs, + attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) ) + propertyEltList + end-element() + Process the attribute list for an RDF node element. A property attribute URI is + anything other than an RDF term. The rdf:ID and rdf:nodeID attributes are simply ignored, + as are rdf:about attributes on inner nodes. + + the xmp metadata object that is generated + the parent xmp node + the currently processed XML node + Flag if the node is a top-level node + thown on parsing errors + + + + 7.2.13 propertyEltList + ws* ( propertyElt ws* ) + + the xmp metadata object that is generated + the parent xmp node + the currently processed XML node + Flag if the node is a top-level node + thown on parsing errors + + + + 7.2.14 propertyElt + resourcePropertyElt | literalPropertyElt | parseTypeLiteralPropertyElt | + parseTypeResourcePropertyElt | parseTypeCollectionPropertyElt | + parseTypeOtherPropertyElt | emptyPropertyElt + 7.2.15 resourcePropertyElt + start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) ) + ws* nodeElement ws + end-element() + 7.2.16 literalPropertyElt + start-element ( + URI == propertyElementURIs, attributes == set ( idAttr?, datatypeAttr?) ) + text() + end-element() + 7.2.17 parseTypeLiteralPropertyElt + start-element ( + URI == propertyElementURIs, attributes == set ( idAttr?, parseLiteral ) ) + literal + end-element() + 7.2.18 parseTypeResourcePropertyElt + start-element ( + URI == propertyElementURIs, attributes == set ( idAttr?, parseResource ) ) + propertyEltList + end-element() + 7.2.19 parseTypeCollectionPropertyElt + start-element ( + URI == propertyElementURIs, attributes == set ( idAttr?, parseCollection ) ) + nodeElementList + end-element() + 7.2.20 parseTypeOtherPropertyElt + start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseOther ) ) + propertyEltList + end-element() + 7.2.21 emptyPropertyElt + start-element ( URI == propertyElementURIs, + attributes == set ( idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) ) + end-element() + The various property element forms are not distinguished by the XML element name, + but by their attributes for the most part. + + + 7.2.14 propertyElt + resourcePropertyElt | literalPropertyElt | parseTypeLiteralPropertyElt | + parseTypeResourcePropertyElt | parseTypeCollectionPropertyElt | + parseTypeOtherPropertyElt | emptyPropertyElt + 7.2.15 resourcePropertyElt + start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) ) + ws* nodeElement ws + end-element() + 7.2.16 literalPropertyElt + start-element ( + URI == propertyElementURIs, attributes == set ( idAttr?, datatypeAttr?) ) + text() + end-element() + 7.2.17 parseTypeLiteralPropertyElt + start-element ( + URI == propertyElementURIs, attributes == set ( idAttr?, parseLiteral ) ) + literal + end-element() + 7.2.18 parseTypeResourcePropertyElt + start-element ( + URI == propertyElementURIs, attributes == set ( idAttr?, parseResource ) ) + propertyEltList + end-element() + 7.2.19 parseTypeCollectionPropertyElt + start-element ( + URI == propertyElementURIs, attributes == set ( idAttr?, parseCollection ) ) + nodeElementList + end-element() + 7.2.20 parseTypeOtherPropertyElt + start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseOther ) ) + propertyEltList + end-element() + 7.2.21 emptyPropertyElt + start-element ( URI == propertyElementURIs, + attributes == set ( idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) ) + end-element() + The various property element forms are not distinguished by the XML element name, + but by their attributes for the most part. The exceptions are resourcePropertyElt and + literalPropertyElt. They are distinguished by their XML element content. + NOTE: The RDF syntax does not explicitly include the xml:lang attribute although it can + appear in many of these. We have to allow for it in the attibute counts below. + + the xmp metadata object that is generated + the parent xmp node + the currently processed XML node + Flag if the node is a top-level node + thown on parsing errors + + + + 7.2.15 resourcePropertyElt + start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) ) + ws* nodeElement ws + end-element() + This handles structs using an rdf:Description node, + arrays using rdf:Bag/Seq/Alt, and typedNodes. + + + 7.2.15 resourcePropertyElt + start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) ) + ws* nodeElement ws + end-element() + This handles structs using an rdf:Description node, + arrays using rdf:Bag/Seq/Alt, and typedNodes. It also catches and cleans up qualified + properties written with rdf:Description and rdf:value. + + the xmp metadata object that is generated + the parent xmp node + the currently processed XML node + Flag if the node is a top-level node + thown on parsing errors + + + + 7.2.16 literalPropertyElt + start-element ( URI == propertyElementURIs, + attributes == set ( idAttr?, datatypeAttr?) ) + text() + end-element() + Add a leaf node with the text value and qualifiers for the attributes. + + the xmp metadata object that is generated + the parent xmp node + the currently processed XML node + Flag if the node is a top-level node + thown on parsing errors + + + + 7.2.17 parseTypeLiteralPropertyElt + start-element ( URI == propertyElementURIs, + attributes == set ( idAttr?, parseLiteral ) ) + literal + end-element() + + thown on parsing errors + + + + 7.2.18 parseTypeResourcePropertyElt + start-element ( URI == propertyElementURIs, + attributes == set ( idAttr?, parseResource ) ) + propertyEltList + end-element() + Add a new struct node with a qualifier for the possible rdf:ID attribute. + + + 7.2.18 parseTypeResourcePropertyElt + start-element ( URI == propertyElementURIs, + attributes == set ( idAttr?, parseResource ) ) + propertyEltList + end-element() + Add a new struct node with a qualifier for the possible rdf:ID attribute. + Then process the XML child nodes to get the struct fields. + + the xmp metadata object that is generated + the parent xmp node + the currently processed XML node + Flag if the node is a top-level node + thown on parsing errors + + + + 7.2.19 parseTypeCollectionPropertyElt + start-element ( URI == propertyElementURIs, + attributes == set ( idAttr?, parseCollection ) ) + nodeElementList + end-element() + + thown on parsing errors + + + + 7.2.20 parseTypeOtherPropertyElt + start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseOther ) ) + propertyEltList + end-element() + + thown on parsing errors + + + + Adds a child node. + the xmp metadata object that is generated + the parent xmp node + the currently processed XML node + Node value + Flag if the node is a top-level node + Returns the newly created child node. + thown on parsing errors + + + Adds a qualifier node. + the parent xmp node + + the name of the qualifier which has to be + QName including the default prefix + + the value of the qualifier + Returns the newly created child node. + thown on parsing errors + + + The parent is an RDF pseudo-struct containing an rdf:value field. + + The parent is an RDF pseudo-struct containing an rdf:value field. Fix the + XMP data model. The rdf:value node must be the first child, the other + children are qualifiers. The form, value, and children of the rdf:value + node are the real ones. The rdf:value node's qualifiers must be added to + the others. + + the parent xmp node + thown on parsing errors + + + Checks if the node is a white space. + an XML-node + + Returns whether the node is a whitespace node, + i.e. a text node that contains only whitespaces. + + + + + 7.2.6 propertyElementURIs + anyURI - ( coreSyntaxTerms | rdf:Description | oldTerms ) + + the term id + Return true if the term is a property element name. + + + + 7.2.4 oldTerms
    + rdf:aboutEach | rdf:aboutEachPrefix | rdf:bagID +
    + the term id + Returns true if the term is an old term. +
    + + + 7.2.2 coreSyntaxTerms
    + rdf:RDF | rdf:ID | rdf:about | rdf:parseType | rdf:resource | rdf:nodeID | + rdf:datatype +
    + the term id + Return true if the term is a core syntax term +
    + + Determines the ID for a certain RDF Term. + + Determines the ID for a certain RDF Term. + Arranged to hopefully minimize the parse time for large XMP. + + an XML node + Returns the term ID. + + + 09.11.2006 + + + XML namespace prefix + + + XML localname + + + Splits a qname into prefix and localname. + a QName + + + Constructor that initializes the fields + the prefix + the name + + + Returns whether the QName has a prefix. + + + the localName + + + the prefix + + + Utility functions for the XMPToolkit implementation. + 06.06.2006 + + + segments of a UUID + + + length of a UUID + + + table of XML name start chars (<= 0xFF) + + + table of XML name chars (<= 0xFF) + + + Private constructor + + + + + a schema namespace + an XMP Property + + Returns true if the property is defined as "Internal + Property", see XMP Specification. + + + + + Check some requirements for an UUID: +
      +
    • Length of the UUID is 32
    • +
    • The Delimiter count is 4 and all the 4 delimiter are on their right + position (8,13,18,23)
    • +
    +
    + uuid to test + true - this is a well formed UUID, false - UUID has not the expected format +
    + + Simple check for valid XMLNames. + + Simple check for valid XMLNames. Within ASCII range
    + ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6]
    + are accepted, above all characters (which is not entirely + correct according to the XML Spec. +
    + an XML Name + Return true if the name is correct. +
    + + + Checks if the value is a legal "unqualified" XML name, as + defined in the XML Namespaces proposed recommendation. + + + Checks if the value is a legal "unqualified" XML name, as + defined in the XML Namespaces proposed recommendation. + These are XML names, except that they must not contain a colon. + + the value to check + Returns true if the name is a valid "unqualified" XML name. + + + a char + Returns true if the char is an ASCII control char. + + + Serializes the node value in XML encoding. + + Serializes the node value in XML encoding. Its used for tag bodies and + attributes.
    + Note: The attribute is always limited by quotes, + thats why &apos; is never serialized.
    + Note: Control chars are written unescaped, but if the user uses others than tab, LF + and CR the resulting XML will become invalid. +
    + a string + flag if string is attribute value (need to additional escape quotes) + Decides if LF, CR and TAB are escaped. + Returns the value ready for XML output. +
    + + Replaces the ASCII control chars with a space. + a node value + Returns the cleaned up value + + + Simple check if a character is a valid XML start name char. + + Simple check if a character is a valid XML start name char. + All characters according to the XML Spec 1.1 are accepted: + http://www.w3.org/TR/xml11/#NT-NameStartChar + + a character + Returns true if the character is a valid first char of an XML name. + + + + Simple check if a character is a valid XML name char + (every char except the first one), according to the XML Spec 1.1: + http://www.w3.org/TR/xml11/#NT-NameChar + + a character + Returns true if the character is a valid char of an XML name. + + + + Initializes the char tables for the chars 0x00-0xFF for later use, + according to the XML 1.1 specification + http://www.w3.org/TR/xml11 + + + + The implementation of XMPDateTime. + + The implementation of XMPDateTime. Internally a calendar is used + plus an additional nano seconds field, because Calendar supports only milli + seconds. The nanoSeconds convers only the resolution beyond a milli second. + + 16.02.2006 + + + + Returns the year, can be negative. + + + Sets the year + + + Returns The month in the range 1..12. + + + Sets the month 1..12 + + + Returns the day of the month in the range 1..31. + + + Sets the day 1..31 + + + Returns hour - The hour in the range 0..23. + + + Sets the hour in the range 0..23. + + + Returns the minute in the range 0..59. + + + Sets the minute in the range 0..59. + + + Returns the second in the range 0..59. + + + Sets the second in the range 0..59. + + + + Returns milli-, micro- and nano seconds. + Nanoseconds within a second, often left as zero? + + + + + Sets the milli-, micro- and nano seconds. + Granularity goes down to milli seconds. + + + + Returns the time zone. + + + a time zone to set + + + This flag is set either by parsing or by setting year, month or day. + Returns true if the XMPDateTime object has a date portion. + + + This flag is set either by parsing or by setting hours, minutes, seconds or milliseconds. + + Returns true if the XMPDateTime object has a time portion. + + + This flag is set either by parsing or by setting hours, minutes, seconds or milliseconds. + + Returns true if the XMPDateTime object has a defined timezone. + + + + Returns a Calendar (only with milli second precision).
    + Note: the dates before Oct 15th 1585 (which normally fall into validity of + the Julian calendar) are also rendered internally as Gregorian dates. +
    +
    + + Returns the ISO 8601 string representation of the date and time. + + + Use NO time zone as default + + + The nano seconds take micro and nano seconds, while the milli seconds are in the calendar. + + + + + Creates an XMPDateTime-instance with the current time in the default time + zone. + + + + + Creates an XMPDateTime-instance from a calendar. + + a Calendar + + + + Creates an XMPDateTime-instance from + a Date and a TimeZone. + + a date describing an absolute point in time + a TimeZone how to interpret the date + + + Creates an XMPDateTime-instance from an ISO 8601 string. + an ISO 8601 string + If the string is a non-conform ISO 8601 string, an exception is thrown + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Returns the ISO string representation. + + + The XMPIterator implementation. + + The XMPIterator implementation. + Iterates the XMP Tree according to a set of options. + During the iteration the XMPMeta-object must not be changed. + Calls to skipSubtree() / skipSiblings() will affect the iteration. + + 29.06.2006 + + + + + Skip the subtree below the current node when next() is + called. + + + + + Skip the subtree below and remaining siblings of the current node when + next() is called. + + + + stores the iterator options + + + the base namespace of the property path, will be changed during the iteration + + + + flag to indicate that skipSiblings() has been called. + + + flag to indicate that skipSiblings() has been called. + + + the node iterator doing the work + + + Constructor with optionsl initial values. + + Constructor with optionsl initial values. If propName is provided, + schemaNS has also be provided. + + the iterated metadata object. + the iteration is reduced to this schema (optional) + the iteration is redurce to this property within the schemaNS + + + advanced iteration options, see + + + If the node defined by the paramters is not existing. + + + + + + + + + + + + + Exposes the options for inner class. + + + Exposes the options for inner class. + + + sets the baseNS from the inner class. + + + + + + The XMPIterator implementation. + + The XMPIterator implementation. + It first returns the node itself, then recursivly the children and qualifier of the node. + + 29.06.2006 + + + + iteration state + + + + iteration state + + + + iteration state + + + + the recursively accumulated path + + + + the currently visited node + + + + the iterator that goes through the children and qualifier list + + + + index of node with parent, only interesting for arrays + + + + the cached PropertyInfo to return + + + + the state of the iteration + + + + the iterator for each child + + + + Constructor for the node iterator. + the currently visited node + the accumulated path of the node + the index within the parent node (only for arrays) + + + the childrenIterator + + + Returns the returnProperty. + + + + + Sets the returnProperty as next item or recurses into hasNext(). + Returns if there is a next item to return. + + + + Handles the iteration of the children or qualfier + an iterator + Returns if there are more elements available. + + + the node that will be added to the path. + the path up to this node. + the current array index if an arrey is traversed + Returns the updated path. + + + + Creates a property info object from an XMPNode. + an XMPNode + the base namespace to report + the full property path + Returns a XMPProperty-object that serves representation of the node. + + + This interface is used to return a property together with its path and namespace. + + This interface is used to return a property together with its path and namespace. + It is returned when properties are iterated with the XMPIterator. + + 06.07.2006 + + + This interface is used to return a text property together with its and options. + 23.01.2006 + + + Returns the value of the property. + + + Returns the options of the property. + + + + Only set by + + . + + Returns the language of the alt-text item. + + + Returns the namespace of the property + + + Returns the path of the property, but only if returned by the iterator. + + + + This iterator is derived from the default NodeIterator, + and is only used for the option . + + @since 02.10.2006 + + + + + Constructor + the node which children shall be iterated. + the full path of the former node without the leaf node. + + + + + Implementation for + + . + + 17.02.2006 + + + This class represents the set of XMP metadata as a DOM representation. + + This class represents the set of XMP metadata as a DOM representation. It has methods to read and + modify all kinds of properties, create an iterator over all properties and serialize the metadata + to a String, byte-array or OutputStream. + + 20.01.2006 + + + + Provides access to items within an array. + + Provides access to items within an array. The index is passed as an integer, you need not + worry about the path string syntax for array items, convert a loop index to a string, etc. + + The namespace URI for the array. Has the same usage as in getProperty. + + The name of the array. May be a general path expression, must not be + null or the empty string. Has the same namespace prefix usage as + propName in getProperty(). + + + The index of the desired item. Arrays in XMP are indexed from 1. The + constant + + always refers to the last existing array + item. + + + Returns a XMPProperty containing the value and the options or + null if the property does not exist. + + Wraps all errors and exceptions that may occur. + + + + Returns the number of items in the array. + The namespace URI for the array. Has the same usage as in getProperty. + + The name of the array. May be a general path expression, must not be + null or the empty string. Has the same namespace prefix usage as + propName in getProperty(). + + Returns the number of items in the array. + Wraps all errors and exceptions that may occur. + + + + + + + + The namespace URI + The name of the property + the value for the property + Wraps all errors and exceptions + + + + Replaces an item within an array. + + Replaces an item within an array. The index is passed as an integer, you need not worry about + the path string syntax for array items, convert a loop index to a string, etc. The array + passed must already exist. In normal usage the selected array item is modified. A new item is + automatically appended if the index is the array size plus 1. + + The namespace URI for the array. Has the same usage as in getProperty. + + The name of the array. May be a general path expression, must not be + null or the empty string. Has the same namespace prefix usage as + propName in getProperty. + + + The index of the desired item. Arrays in XMP are indexed from 1. To address + the last existing item, use + + to find + out the length of the array. + + + the new value of the array item. Has the same usage as propValue in + setProperty(). + + the set options for the item. + Wraps all errors and exceptions that may occur. + + + + + The namespace URI + The name of the array + The index to insert the new item + the new value of the array item + Wraps all errors and exceptions + + + + Inserts an item into an array previous to the given index. + + Inserts an item into an array previous to the given index. The index is passed as an integer, + you need not worry about the path string syntax for array items, convert a loop index to a + string, etc. The array passed must already exist. In normal usage the selected array item is + modified. A new item is automatically appended if the index is the array size plus 1. + + The namespace URI for the array. Has the same usage as in getProperty. + + The name of the array. May be a general path expression, must not be + null or the empty string. Has the same namespace prefix usage as + propName in getProperty. + + + The index to insert the new item. Arrays in XMP are indexed from 1. Use + XMPConst.ARRAY_LAST_ITEM to append items. + + + the new value of the array item. Has the same usage as + propValue in setProperty(). + + the set options that decide about the kind of the node. + Wraps all errors and exceptions that may occur. + + + + + The namespace URI for the array + The name of the array + The index to insert the new item + the value of the array item + Wraps all errors and exceptions + + + + + + The namespace URI for the array + The name of the array + the value of the array item + Wraps all errors and exceptions + + + + Provides access to fields within a nested structure. + + Provides access to fields within a nested structure. The namespace for the field is passed as + a URI, you need not worry about the path string syntax. The names of fields should be XML + qualified names, that is within an XML namespace. The path syntax for a qualified name uses + the namespace prefix, which is unreliable because the prefix is never guaranteed. The URI is + the formal name, the prefix is just a local shorthand in a given sequence of XML text. + + The namespace URI for the struct. Has the same usage as in getProperty. + + The name of the struct. May be a general path expression, must not be null + or the empty string. Has the same namespace prefix usage as propName in getProperty. + + + The namespace URI for the field. Has the same URI and prefix usage as the + schemaNS parameter. + + + The name of the field. Must be a single XML name, must not be null or the + empty string. Has the same namespace prefix usage as the structName parameter. + + + the value of thefield, if the field has a value. + Has the same usage as propValue in getProperty. + + Option flags describing the field. See the earlier description. + Wraps all errors and exceptions that may occur. + + + + + The namespace URI for the struct + The name of the struct + The namespace URI for the field + The name of the field + the value of the field + Wraps all errors and exceptions + + + + Provides access to a qualifier attached to a property. + + Provides access to a qualifier attached to a property. The namespace for the qualifier is + passed as a URI, you need not worry about the path string syntax. In many regards qualifiers + are like struct fields. See the introductory discussion of qualified properties for more + information. The names of qualifiers should be XML qualified names, that is within an XML + namespace. The path syntax for a qualified name uses the namespace prefix, which is + unreliable because the prefix is never guaranteed. The URI is the formal name, the prefix is + just a local shorthand in a given sequence of XML text. The property the qualifier + will be attached has to exist. + + The namespace URI for the struct. Has the same usage as in getProperty. + + The name of the property to which the qualifier is attached. Has the same + usage as in getProperty. + + + The namespace URI for the qualifier. Has the same URI and prefix usage as the + schemaNS parameter. + + + The name of the qualifier. Must be a single XML name, must not be + null or the empty string. Has the same namespace prefix usage as the + propName parameter. + + + A pointer to the null terminated UTF-8 string that is the + value of the qualifier, if the qualifier has a value. Has the same usage as propValue + in getProperty. + + Option flags describing the qualifier. See the earlier description. + Wraps all errors and exceptions that may occur. + + + + + The namespace URI for the struct + The name of the property to which the qualifier is attached + The namespace URI for the qualifier + The name of the qualifier + the value of the qualifier + Wraps all errors and exceptions + + + + Deletes the given XMP subtree rooted at the given property. + + Deletes the given XMP subtree rooted at the given property. It is not an error if the + property does not exist. + + + The namespace URI for the property. Has the same usage as in + getProperty(). + + The name of the property. Has the same usage as in getProperty. + + + Deletes the given XMP subtree rooted at the given array item. + + Deletes the given XMP subtree rooted at the given array item. It is not an error if the array + item does not exist. + + The namespace URI for the array. Has the same usage as in getProperty. + + The name of the array. May be a general path expression, must not be + null or the empty string. Has the same namespace prefix usage as + propName in getProperty(). + + + The index of the desired item. Arrays in XMP are indexed from 1. The + constant XMPConst.ARRAY_LAST_ITEM always refers to the last + existing array item. + + + + Deletes the given XMP subtree rooted at the given struct field. + + Deletes the given XMP subtree rooted at the given struct field. It is not an error if the + field does not exist. + + + The namespace URI for the struct. Has the same usage as in + getProperty(). + + + The name of the struct. May be a general path expression, must not be + null or the empty string. Has the same namespace prefix usage as + propName in getProperty. + + + The namespace URI for the field. Has the same URI and prefix usage as the + schemaNS parameter. + + + The name of the field. Must be a single XML name, must not be + null or the empty string. Has the same namespace prefix usage as the + structName parameter. + + + + Deletes the given XMP subtree rooted at the given qualifier. + + Deletes the given XMP subtree rooted at the given qualifier. It is not an error if the + qualifier does not exist. + + + The namespace URI for the struct. Has the same usage as in + getProperty(). + + + The name of the property to which the qualifier is attached. Has the same + usage as in getProperty. + + + The namespace URI for the qualifier. Has the same URI and prefix usage as the + schemaNS parameter. + + + The name of the qualifier. Must be a single XML name, must not be + null or the empty string. Has the same namespace prefix usage as the + propName parameter. + + + + Returns whether the property exists. + + The namespace URI for the property. Has the same usage as in + getProperty(). + + + The name of the property. + Has the same usage as in getProperty(). + + Returns true if the property exists. + + + Tells if the array item exists. + + The namespace URI for the array. Has the same usage as in + getProperty(). + + + The name of the array. May be a general path expression, must not be + null or the empty string. Has the same namespace prefix usage as + propName in getProperty(). + + + The index of the desired item. Arrays in XMP are indexed from 1. The + constant XMPConst.ARRAY_LAST_ITEM always refers to the last + existing array item. + + Returns true if the array exists, false otherwise. + + + DoesStructFieldExist tells if the struct field exists. + + The namespace URI for the struct. Has the same usage as in + getProperty(). + + + The name of the struct. May be a general path expression, must not be + null or the empty string. Has the same namespace prefix usage as + propName in getProperty(). + + + The namespace URI for the field. Has the same URI and prefix usage as the + schemaNS parameter. + + + The name of the field. Must be a single XML name, must not be + null or the empty string. Has the same namespace prefix usage as the + structName parameter. + + Returns true if the field exists. + + + DoesQualifierExist tells if the qualifier exists. + + The namespace URI for the struct. Has the same usage as in + getProperty(). + + + The name of the property to which the qualifier is attached. Has the same + usage as in getProperty(). + + + The namespace URI for the qualifier. Has the same URI and prefix usage as the + schemaNS parameter. + + + The name of the qualifier. Must be a single XML name, must not be + null or the empty string. Has the same namespace prefix usage as the + propName parameter. + + Returns true if the qualifier exists. + + + + Modifies the value of a selected item in an alt-text array. + + Modifies the value of a selected item in an alt-text array. Creates an appropriate array item + if necessary, and handles special cases for the x-default item. If the selected item is from + a match with the specific language, the value of that item is modified. If the existing value + of that item matches the existing value of the x-default item, the x-default item is also + modified. If the array only has 1 existing item (which is not x-default), an x-default item + is added with the given value. If the selected item is from a match with the generic language + and there are no other generic matches, the value of that item is modified. If the existing + value of that item matches the existing value of the x-default item, the x-default item is + also modified. If the array only has 1 existing item (which is not x-default), an x-default + item is added with the given value. If the selected item is from a partial match with the + generic language and there are other partial matches, a new item is created for the specific + language. The x-default item is not modified. If the selected item is from the last 2 rules + then a new item is created for the specific language. If the array only had an x-default + item, the x-default item is also modified. If the array was empty, items are created for the + specific language and x-default. + Note: In a future version of this API a method + using Java java.lang.Locale will be added. + + + The namespace URI for the alt-text array. Has the same usage as in + getProperty(). + + + The name of the alt-text array. May be a general path expression, must not + be null or the empty string. Has the same namespace prefix usage as + propName in getProperty(). + + + The name of the generic language as an RFC 3066 primary subtag. May be + null or the empty string if no generic language is wanted. + + + The name of the specific language as an RFC 3066 tag. Must not be + null or the empty string. + + + A pointer to the null terminated UTF-8 string that is the new + value for the appropriate array item. + + Option flags, none are defined at present. + Wraps all errors and exceptions that may occur. + + + + + The namespace URI for the alt-text array + The name of the alt-text array + The name of the generic language + The name of the specific language + the new value for the appropriate array item + Wraps all errors and exceptions + + + + + These are very similar to getProperty() and SetProperty() above, + but the value is returned or provided in a literal form instead of as a UTF-8 string. + + + These are very similar to getProperty() and SetProperty() above, + but the value is returned or provided in a literal form instead of as a UTF-8 string. + The path composition functions in XMPPathFactory may be used to compose an path + expression for fields in nested structures, items in arrays, or qualifiers. + + + The namespace URI for the property. Has the same usage as in + getProperty(). + + + The name of the property. + Has the same usage as in getProperty(). + + + Returns a Boolean value or null + if the property does not exist. + + + Wraps all exceptions that may occur, + especially conversion errors. + + + + + Convenience method to retrieve the literal value of a property. + + The namespace URI for the property. Has the same usage as in + getProperty(). + + + The name of the property. + Has the same usage as in getProperty(). + + + Returns an Integer value or null + if the property does not exist. + + + Wraps all exceptions that may occur, + especially conversion errors. + + + + + Convenience method to retrieve the literal value of a property. + + The namespace URI for the property. Has the same usage as in + getProperty(). + + + The name of the property. + Has the same usage as in getProperty(). + + + Returns a Long value or null + if the property does not exist. + + + Wraps all exceptions that may occur, + especially conversion errors. + + + + + Convenience method to retrieve the literal value of a property. + + The namespace URI for the property. Has the same usage as in + getProperty(). + + + The name of the property. + Has the same usage as in getProperty(). + + + Returns a Double value or null + if the property does not exist. + + + Wraps all exceptions that may occur, + especially conversion errors. + + + + + Convenience method to retrieve the literal value of a property. + + The namespace URI for the property. Has the same usage as in + getProperty(). + + + The name of the property. + Has the same usage as in getProperty(). + + + Returns a XMPDateTime-object or null + if the property does not exist. + + + Wraps all exceptions that may occur, + especially conversion errors. + + + + + Convenience method to retrieve the literal value of a property. + + The namespace URI for the property. Has the same usage as in + getProperty(). + + + The name of the property. + Has the same usage as in getProperty(). + + + Returns a Java Calendar-object or null + if the property does not exist. + + + Wraps all exceptions that may occur, + especially conversion errors. + + + + + Convenience method to retrieve the literal value of a property. + + The namespace URI for the property. Has the same usage as in + getProperty(). + + + The name of the property. + Has the same usage as in getProperty(). + + + Returns a byte[]-array contained the decoded base64 value + or null if the property does not exist. + + + Wraps all exceptions that may occur, + especially conversion errors. + + + + + Convenience method to retrieve the literal value of a property. + + Convenience method to retrieve the literal value of a property. + Note: There is no setPropertyString(), + because setProperty() sets a string value. + + + The namespace URI for the property. Has the same usage as in + getProperty(). + + + The name of the property. + Has the same usage as in getProperty(). + + + Returns a String value or null + if the property does not exist. + + + Wraps all exceptions that may occur, + especially conversion errors. + + + + + Convenience method to set a property to a literal boolean value. + + The namespace URI for the property. Has the same usage as in + setProperty(). + + + The name of the property. + Has the same usage as in getProperty(). + + the literal property value as boolean. + options of the property to set (optional). + Wraps all exceptions that may occur. + + + + + The namespace URI for the property + The name of the property + the literal property value as boolean + Wraps all exceptions + + + + Convenience method to set a property to a literal int value. + + The namespace URI for the property. Has the same usage as in + setProperty(). + + + The name of the property. + Has the same usage as in getProperty(). + + the literal property value as int. + options of the property to set (optional). + Wraps all exceptions that may occur. + + + + + The namespace URI for the property + The name of the property + the literal property value as int + Wraps all exceptions + + + + Convenience method to set a property to a literal long value. + + The namespace URI for the property. Has the same usage as in + setProperty(). + + + The name of the property. + Has the same usage as in getProperty(). + + the literal property value as long. + options of the property to set (optional). + Wraps all exceptions that may occur. + + + + + The namespace URI for the property + The name of the property + the literal property value as long + Wraps all exceptions + + + + Convenience method to set a property to a literal double value. + + The namespace URI for the property. Has the same usage as in + setProperty(). + + + The name of the property. + Has the same usage as in getProperty(). + + the literal property value as double. + options of the property to set (optional). + Wraps all exceptions that may occur. + + + + + The namespace URI for the property + The name of the property + the literal property value as double + Wraps all exceptions + + + + + Convenience method to set a property with an XMPDateTime-object, + which is serialized to an ISO8601 date. + + + The namespace URI for the property. Has the same usage as in + setProperty(). + + + The name of the property. + Has the same usage as in getProperty(). + + the property value as XMPDateTime. + options of the property to set (optional). + Wraps all exceptions that may occur. + + + + + The namespace URI for the property + The name of the property + the property value as XMPDateTime + Wraps all exceptions + + + + + Convenience method to set a property with a Java Calendar-object, + which is serialized to an ISO8601 date. + + + The namespace URI for the property. Has the same usage as in + setProperty(). + + + The name of the property. + Has the same usage as in getProperty(). + + the property value as Java Calendar. + options of the property to set (optional). + Wraps all exceptions that may occur. + + + + + The namespace URI for the property + The name of the property + the property value as Calendar + Wraps all exceptions + + + + + Convenience method to set a property from a binary byte[]-array, + which is serialized as base64-string. + + + The namespace URI for the property. Has the same usage as in + setProperty(). + + + The name of the property. + Has the same usage as in getProperty(). + + the literal property value as byte array. + options of the property to set (optional). + Wraps all exceptions that may occur. + + + + + The namespace URI for the property + The name of the property + the literal property value as byte array + Wraps all exceptions + + + + Constructs an iterator for the properties within this XMP object. + Returns an XMPIterator. + + Wraps all errors and exceptions that may occur. + + + + Constructs an iterator for the properties within this XMP object using some options. + Option flags to control the iteration. + Returns an XMPIterator. + + Wraps all errors and exceptions that may occur. + + + + Construct an iterator for the properties within an XMP object. + + Construct an iterator for the properties within an XMP object. According to the parameters it iterates the entire data tree, + properties within a specific schema, or a subtree rooted at a specific node. + + + Optional schema namespace URI to restrict the iteration. Omitted (visit all + schema) by passing null or empty String. + + + Optional property name to restrict the iteration. May be an arbitrary path + expression. Omitted (visit all properties) by passing null or empty + String. If no schema URI is given, it is ignored. + + + Option flags to control the iteration. See + + for + details. + + + Returns an XMPIterator for this XMPMeta-object + considering the given options. + + Wraps all errors and exceptions that may occur. + + + + + This correlates to the about-attribute, + returns the empty String if no name is set. + + Returns the name of the XMP object. + + + Sets the name of the XMP object. + + + + Returns the unparsed content of the <?xpacket> processing instruction. + This contains normally the attribute-like elements 'begin="<BOM>" + id="W5M0MpCehiHzreSzNTczkc9d"' and possibly the deprecated elements 'bytes="1234"' or + 'encoding="XXX"'. If the parsed packet has not been wrapped into an xpacket, + null is returned. + + + + Clones the complete metadata tree. + Returns a deep copy of this instance. + + + + Perform the normalization as a separate parsing step. + + Perform the normalization as a separate parsing step. + Normally it is done during parsing, unless the parsing option + + is set to true. + Note: It does no harm to call this method to an already normalized xmp object. + It was a PDF/A requirement to get hand on the unnormalized XMPMeta object. + + optional parsing options. + Wraps all errors and exceptions that may occur. + + + + Renders this node and the tree unter this node in a human readable form. + Returns a multiline string containing the dump. + + + Property values are Strings by default + + + root of the metadata tree + + + the xpacket processing instructions content + + + Constructor for an empty metadata object. + + + Constructor for a cloned metadata tree. + + an prefilled metadata tree which fulfills all + XMPNode contracts. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Returns a property, but the result value can be requested. + + Returns a property, but the result value can be requested. It can be one + of + + , + + , + + , + + , + + , + + , + + , + + . + + + a schema namespace + a property name or path + the type of the value, see VALUE_... + Returns an XMPProperty + Collects any exception that occurs. + + + + Returns a property, but the result value can be requested. + + a schema namespace + a property name or path + the type of the value, see VALUE_... + + Returns the node value as an object according to the + valueType. + + Collects any exception that occurs. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Sets the packetHeader attributes, only used by the parser. + the processing instruction content + + + Performs a deep clone of the XMPMeta-object + + + + + + + + + + + + + + Returns the root node of the XMP tree. + + + Locate or create the item node and set the value. + + Locate or create the item node and set the value. Note the index + parameter is one-based! The index can be in the range [1..size + 1] or + "last()", normalize it and check the insert flags. The order of the + normalization checks is important. If the array is empty we end up with + an index and location to set item size + 1. + + an array node + the index where to insert the item + the item value + the options for the new item + insert oder overwrite at index position? + + + + + The internals for setProperty() and related calls, used after the node is + found or created. + + the newly created node + the node value, can be null + options for the new node, must not be null. + + flag if the existing value is to be overwritten + thrown if options and value do not correspond + + + + + Evaluates a raw node value to the given value type, apply special + conversions for defined types in XMP. + + an int indicating the value type + the node containing the value + Returns a literal value for the node. + + + + + This class replaces the ExpatAdapter.cpp and does the + XML-parsing and fixes the prefix. + + + This class replaces the ExpatAdapter.cpp and does the + XML-parsing and fixes the prefix. After the parsing several normalisations + are applied to the XMPTree. + + 01.02.2006 + + + Hidden constructor, initialises the SAX parser handler. + + + + Parses the input source into an XMP metadata object, including + de-aliasing and normalisation. + + + the input can be an InputStream, a String or + a byte buffer containing the XMP packet. + + the parse options + Returns the resulting XMP metadata object + Thrown if parsing or normalisation fails. + + + + + + Parses XML from an , + fixing the encoding (Latin-1 to UTF-8) and illegal control character optionally. + + an InputStream + the parsing options + Returns an XML DOM-Document. + Thrown when the parsing fails. + + + + Parses XML from a byte buffer, + fixing the encoding (Latin-1 to UTF-8) and illegal control character optionally. + + a byte buffer containing the XMP packet + the parsing options + Returns an XML DOM-Document. + Thrown when the parsing fails. + + + + + Parses XML from a + + , + fixing the illegal control character optionally. + + a String containing the XMP packet + the parsing options + Returns an XML DOM-Document. + Thrown when the parsing fails. + + + + + + A node in the internally XMP tree, which can be a schema node, a property node, an array node, + an array item, a struct node or a qualifier node (without '?'). + + + A node in the internally XMP tree, which can be a schema node, a property node, an array node, + an array item, a struct node or a qualifier node (without '?'). + Possible improvements: + 1. The kind Node of node might be better represented by a class-hierarchy of different nodes. + 2. The array type should be an enum + 3. isImplicitNode should be removed completely and replaced by return values of fi. + 4. hasLanguage, hasType should be automatically maintained by XMPNode + + 21.02.2006 + + + name of the node, contains different information depending of the node kind + + + + value of the node, contains different information depending of the node kind + + + + link to the parent node + + + list of child nodes, lazy initialized + + + list of qualifier of the node, lazy initialized + + + options describing the kind of the node + + + flag if the node is implicitly created + + + flag if the node has aliases + + + flag if the node is an alias + + + flag if the node has an "rdf:value" child node. + + + Creates an XMPNode with initial values. + the name of the node + the value of the node + the options of the node + + + Constructor for the node without value. + the name of the node + the options of the node + + + Resets the node. + + + Returns the parent node. + + + an index [1..size] + Returns the child with the requested index. + + + Adds a node as child to this node. + an XMPNode + + + + Adds a node as child to this node. + + the index of the node before which the new one is inserted. + Note: The node children are indexed from [1..size]! + An index of size + 1 appends a node. + + an XMPNode + + + + Replaces a node with another one. + + the index of the node that will be replaced. + Note: The node children are indexed from [1..size]! + + the replacement XMPNode + + + Removes a child at the requested index. + the index to remove [1..size] + + + Removes a child node. + + Removes a child node. + If its a schema node and doesn't have any children anymore, its deleted. + + the child node to delete. + + + + Removes the children list if this node has no children anymore; + checks if the provided node is a schema node and doesn't have any children anymore, + its deleted. + + + + Removes all children from the node. + + + Returns the number of children without neccessarily creating a list. + + + child node name to look for + Returns an XMPNode if node has been found, null otherwise. + + + + an index [1..size] + Returns the qualifier with the requested index. + + + Returns the number of qualifier without neccessarily creating a list. + + + Appends a qualifier to the qualifier list and sets respective options. + a qualifier node. + + + + Removes one qualifier node and fixes the options. + qualifier to remove + + + Removes all qualifiers from the node and sets the options appropriate. + + + qualifier node name to look for + + Returns a qualifier XMPNode if node has been found, + null otherwise. + + + + Returns whether the node has children. + + + + Returns an iterator for the children. + Note: take care to use it.remove(), as the flag are not adjusted in that case. + + + + Returns whether the node has qualifier attached. + + + + Returns an iterator for the qualifier. + Note: take care to use it.remove(), as the flag are not adjusted in that case. + + + + Performs a deep clone of the node and the complete subtree. + + + + + Performs a deep clone of the complete subtree (children and + qualifier )into and add it to the destination node. + + the node to add the cloned subtree + + + Renders this node and the tree unter this node in a human readable form. + + Flag is qualifier and child nodes shall be rendered too + Returns a multiline string containing the dump. + + + + + + Returns the name. + + + The name to set. + + + Returns the value. + + + The value to set. + + + Returns the options. + + + Updates the options of the node. + the options to set. + + + Returns the implicit flag + + + Sets the implicit node flag + + + Returns if the node contains aliases (applies only to schema nodes) + + + sets the flag that the node contains aliases + + + Returns if the node contains aliases (applies only to schema nodes) + + + sets the flag that the node is an alias + + + the hasValueChild + + + the hasValueChild to set + + + + Dumps this node and its qualifier and children recursively. + + Dumps this node and its qualifier and children recursively. + Note: It creats empty options on every node. + + the buffer to append the dump. + Flag is qualifier and child nodes shall be rendered too + the current indent level. + the index within the parent node (important for arrays) + + + Returns whether this node is a language qualifier. + + + Returns whether this node is a type qualifier. + + + + Note: This method should always be called when accessing 'children' to be sure + that its initialized. + + Returns list of children that is lazy initialized. + + + Returns a read-only copy of child nodes list. + + + Returns list of qualifier that is lazy initialized. + + + + Sets the parent node, this is solely done by addChild(...) + and addQualifier(). + + Sets the parent node. + + + Internal find. + the list to search in + the search expression + Returns the found node or nulls. + + + Checks that a node name is not existing on the same level, except for array items. + + the node name to check + Thrown if a node with the same name is existing. + + + + Checks that a qualifier name is not existing on the same level. + the new qualifier name + Thrown if a node with the same name is existing. + + + + Utilities for XMPNode. + Aug 28, 2006 + + + Private Constructor + + + Find or create a schema node if createNodes is false and + the root of the xmp tree. + a namespace + + a flag indicating if the node shall be created if not found. + Note: The namespace must be registered prior to this call. + + + Returns the schema node if found, null otherwise. + Note: If createNodes is true, it is always + returned a valid node. + + + An exception is only thrown if an error occurred, not if a + node was not found. + + + + Find or create a schema node if createNodes is true. + the root of the xmp tree. + a namespace + If a prefix is suggested, the namespace is allowed to be registered. + + + a flag indicating if the node shall be created if not found. + Note: The namespace must be registered prior to this call. + + + Returns the schema node if found, null otherwise. + Note: If createNodes is true, it is always + returned a valid node. + + + An exception is only thrown if an error occurred, not if a + node was not found. + + + + Find or create a child node under a given parent node. + + Find or create a child node under a given parent node. If the parent node is no + Returns the found or created child node. + + the parent node + the node name to find + flag, if new nodes shall be created. + Returns the found or created node or null. + Thrown if + + + Follow an expanded path expression to find or create a node. + the node to begin the search. + the complete xpath + + flag if nodes shall be created + (when called by setProperty()) + + + the options for the created leaf nodes (only when + createNodes == true). + + Returns the node if found or created or null. + + An exception is only thrown if an error occurred, + not if a node was not found. + + + + Deletes the the given node and its children from its parent. + + Deletes the the given node and its children from its parent. + Takes care about adjusting the flags. + + the top-most node to delete. + + + This is setting the value of a leaf node. + an XMPNode + a value + + + Verifies the PropertyOptions for consistancy and updates them as needed. + + + Verifies the PropertyOptions for consistancy and updates them as needed. + If options are null they are created with default values. + + the PropertyOptions + the node value to set + Returns the updated options. + If the options are not consistant. + + + + + Converts the node value to String, apply special conversions for defined + types in XMP. + + the node value to set + Returns the String representation of the node value. + + + + Find or create a qualifier node under a given parent node. + + Find or create a qualifier node under a given parent node. Returns a pointer to the + qualifier node, and optionally an iterator for the node's position in + the parent's vector of qualifiers. The iterator is unchanged if no qualifier node (null) + is returned. + Note: On entry, the qualName parameter must not have the leading '?' from the + XMPPath step. + + the parent XMPNode + the qualifier name + flag if nodes shall be created + Returns the qualifier node if found or created, null otherwise. + + + + + an array node + the segment containing the array index + flag if new nodes are allowed to be created. + Returns the index or index = -1 if not found + Throws Exceptions + + + + Searches for a field selector in a node: + [fieldName="value] - an element in an array of structs, chosen by a field value. + + + Searches for a field selector in a node: + [fieldName="value] - an element in an array of structs, chosen by a field value. + No implicit nodes are created by field selectors. + + + + + Returns the index of the field if found, otherwise -1. + + + + + Searches for a qualifier selector in a node: + [?qualName="value"] - an element in an array, chosen by a qualifier value. + + + Searches for a qualifier selector in a node: + [?qualName="value"] - an element in an array, chosen by a qualifier value. + No implicit nodes are created for qualifier selectors, + except for an alias to an x-default item. + + an array node + the qualifier name + the qualifier value + + in case the qual selector results from an alias, + an x-default node is created if there has not been one. + + Returns the index of th + + + + Make sure the x-default item is first. + + Make sure the x-default item is first. Touch up "single value" + arrays that have a default plus one real language. This case should have + the same value for both items. Older Adobe apps were hardwired to only + use the "x-default" item, so we copy that value to the other + item. + + an alt text array node + + + See if an array is an alt-text array. + + See if an array is an alt-text array. If so, make sure the x-default item + is first. + + the array node to check if its an alt-text array + + + Appends a language item to an alt text array. + the language array + the language of the item + the content of the item + Thrown if a duplicate property is added + + + + + Looks for the appropriate language item in a text alternative array.item + + an array node + the requested language + Returns the index if the language has been found, -1 otherwise. + + + + Aug 18, 2006 + + + caches the correct dc-property array forms + + + Hidden constructor + + + Normalizes a raw parsed XMPMeta-Object + the raw metadata object + the parsing options + Returns the normalized metadata object + Collects all severe processing errors. + + + + + Tweak old XMP: Move an instance ID from rdf:about to the + xmpMM:InstanceID property. + + + Tweak old XMP: Move an instance ID from rdf:about to the + xmpMM:InstanceID property. An old instance ID usually looks + like "uuid:bac965c4-9d87-11d9-9a30-000d936b79c4", plus InDesign + 3.0 wrote them like "bac965c4-9d87-11d9-9a30-000d936b79c4". If + the name looks like a UUID simply move it to xmpMM:InstanceID, + don't worry about any existing xmpMM:InstanceID. Both will + only be present when a newer file with the xmpMM:InstanceID + property is updated by an old app that uses rdf:about. + + the root of the metadata tree + Thrown if tweaking fails. + + + Visit all schemas to do general fixes and handle special cases. + the metadata object implementation + Thrown if the normalisation fails. + + + + + Undo the denormalization performed by the XMP used in Acrobat 5.
    + If a Dublin Core array had only one item, it was serialized as a simple + property. +
    + + Undo the denormalization performed by the XMP used in Acrobat 5.
    + If a Dublin Core array had only one item, it was serialized as a simple + property.
    + The xml:lang attribute was dropped from an + alt-text item if the language was x-default. +
    + the DC schema node + Thrown if normalization fails + +
    + + Make sure that the array is well-formed AltText. + + Make sure that the array is well-formed AltText. Each item must be simple + and have an "xml:lang" qualifier. If repairs are needed, keep simple + non-empty items by adding the "xml:lang" with value "x-repair". + + the property node of the array to repair. + Forwards unexpected exceptions. + + + + Visit all of the top level nodes looking for aliases. + + Visit all of the top level nodes looking for aliases. If there is + no base, transplant the alias subtree. If there is a base and strict + aliasing is on, make sure the alias and base subtrees match. + + the root of the metadata tree + th parsing options + Forwards XMP errors + + + + Moves an alias node of array form to another schema into an array + the node to be moved + the base array for the array item + Forwards XMP errors + + + Fixes the GPS Timestamp in EXIF. + the EXIF schema node + Thrown if the date conversion fails. + + + + Remove all empty schemas from the metadata tree that were generated during the rdf parsing. + + the root of the metadata tree + + + The outermost call is special. + + The outermost call is special. The names almost certainly differ. The + qualifiers (and hence options) will differ for an alias to the x-default + item of a langAlt array. + + the alias node + the base node of the alias + marks the outer call of the recursion + Forwards XMP errors + + + + The initial support for WAV files mapped a legacy ID3 audio copyright + into a new xmpDM:copyright property. + + + The initial support for WAV files mapped a legacy ID3 audio copyright + into a new xmpDM:copyright property. This is special case code to migrate + that into dc:rights['x-default']. The rules: +
    +            1. If there is no dc:rights array, or an empty array -
    +            Create one with dc:rights['x-default'] set from double linefeed and xmpDM:copyright.
    +            2. If there is a dc:rights array but it has no x-default item -
    +            Create an x-default item as a copy of the first item then apply rule #3.
    +            3. If there is a dc:rights array with an x-default item,
    +            Look for a double linefeed in the value.
    +            A. If no double linefeed, compare the x-default value to the xmpDM:copyright value.
    +            A1. If they match then leave the x-default value alone.
    +            A2. Otherwise, append a double linefeed and
    +            the xmpDM:copyright value to the x-default value.
    +            B. If there is a double linefeed, compare the trailing text to the xmpDM:copyright value.
    +            B1. If they match then leave the x-default value alone.
    +            B2. Otherwise, replace the trailing x-default text with the xmpDM:copyright value.
    +            4. In all cases, delete the xmpDM:copyright property.
    +            
    +
    + the metadata object + the "dm:copyright"-property +
    + + + Initializes the map that contains the known arrays, that are fixed by + + . + + + + The schema registry handles the namespaces, aliases and global options for the XMP Toolkit. + + + The schema registry handles the namespaces, aliases and global options for the XMP Toolkit. There + is only one single instance used by the toolkit. + + 27.01.2006 + + + + + + + + Returns the registered prefix/namespace-pairs as map, where the keys are the + namespaces and the values are the prefixes. + + + + + Returns the registered namespace/prefix-pairs as map, where the keys are the + prefixes and the values are the namespaces. + + + + + Determines if a name is an alias, and what it is aliased to. + + The namespace URI of the alias. Must not be null or the empty + string. + + + The name of the alias. May be an arbitrary path expression + path, must not be null or the empty string. + + + Returns the XMPAliasInfo for the given alias namespace and property or + null if there is no such alias. + + + + Collects all aliases that are contained in the provided namespace. + + Collects all aliases that are contained in the provided namespace. + If nothing is found, an empty array is returned. + + a schema namespace URI + Returns all alias infos from aliases that are contained in the provided namespace. + + + Searches for registered aliases. + an XML conform qname + + Returns if an alias definition for the given qname to another + schema and property is registered. + + + + + Returns the registered aliases as map, where the key is the "qname" (prefix and name) + and the value an XMPAliasInfo-object. + + + + a map from a namespace URI to its registered prefix + + + a map from a prefix to the associated namespace URI + + + a map of all registered aliases. + + a map of all registered aliases. + The map is a relationship from a qname to an XMPAliasInfo-object. + + + + The pattern that must not be contained in simple properties + + + + Performs the initialisation of the registry with the default namespaces, aliases and global + options. + + + + + + + + + + + + + + + + + + + + + + + + Register the standard namespaces of schemas and types that are included in the XMP + Specification and some other Adobe private namespaces. + + + Register the standard namespaces of schemas and types that are included in the XMP + Specification and some other Adobe private namespaces. + Note: This method is not lock because only called by the constructor. + + Forwards processing exceptions + + + + + + + + + + + + + + + Register the standard aliases. + + Register the standard aliases. + Note: This method is not lock because only called by the constructor. + + If the registrations of at least one alias fails. + + + + This interface is used to return info about an alias. + 27.01.2006 + + + Returns Returns the namespace URI for the base property. + + + Returns the default prefix for the given base property. + + + Returns the path of the base property. + + + + Returns the kind of the alias. This can be a direct alias + (ARRAY), a simple property to an ordered array + (ARRAY_ORDERED), to an alternate array + (ARRAY_ALTERNATE) or to an alternate text array + (ARRAY_ALT_TEXT). + + + + + + + + + + + + + + + + + + Serializes the XMPMeta-object to an OutputStream according to the + SerializeOptions. + + 11.07.2006 + + + Static method to serialize the metadata object. + + Static method to serialize the metadata object. For each serialisation, a new XMPSerializer + instance is created, either XMPSerializerRDF or XMPSerializerPlain so thats its possible to + serialialize the same XMPMeta objects in two threads. + + a metadata implementation object + the output stream to serialize to + serialization options, can be null for default. + + + + + Serializes an XMPMeta-object as RDF into a string. + + Serializes an XMPMeta-object as RDF into a string. + Note: Encoding is forced to UTF-16 when serializing to a + string to ensure the correctness of "exact packet size". + + a metadata implementation object + + Options to control the serialization (see + + ). + + Returns a string containing the serialized RDF. + on serializsation errors. + + + Serializes an XMPMeta-object as RDF into a byte buffer. + a metadata implementation object + + Options to control the serialization (see + + ). + + Returns a byte buffer containing the serialized RDF. + on serializsation errors. + + + Serializes the XMPMeta-object using the standard RDF serialization format. + + + Serializes the XMPMeta-object using the standard RDF serialization format. + The output is written to an OutputStream + according to the SerializeOptions. + + 11.07.2006 + + + default padding + + + The w/r is missing inbetween + + + a set of all rdf attribute qualifier + + + the metadata object to be serialized. + + + the output stream to serialize to + + + this writer is used to do the actual serialization + + + the stored serialization options + + + + the size of one unicode char, for UTF-8 set to 1 + (Note: only valid for ASCII chars lower than 0x80), + set to 2 in case of UTF-16 + + + + + the padding in the XMP Packet, or the length of the complete packet in + case of option exactPacketLength. + + + + The actual serialization. + the metadata object to be serialized + outputStream the output stream to serialize to + the serialization options + If case of wrong options or any other serialization error. + + + + Calculates the padding according to the options and write it to the stream. + + the length of the tail string + thrown if packet size is to small to fit the padding + + forwards writer errors + + + Checks if the supplied options are consistent. + Thrown if options are conflicting + + + + Writes the (optional) packet header and the outer rdf-tags. + Returns the packet end processing instraction to be written after the padding. + + Forwarded writer exceptions. + + + + Serializes the metadata in pretty-printed manner. + indent level + Forwarded writer exceptions + + + + + + + Serializes the metadata in compact manner. + indent level to start with + Forwarded writer exceptions + + + + Write each of the parent's simple unqualified properties as an attribute. + + + Write each of the parent's simple unqualified properties as an attribute. Returns true if all + of the properties are written as attributes. + + the parent property node + the current indent level + Returns true if all properties can be rendered as RDF attribute. + + + + + Recursively handles the "value" for a node that must be written as an RDF + property element. + + + Recursively handles the "value" for a node that must be written as an RDF + property element. It does not matter if it is a top level property, a + field of a struct, or an item of an array. The indent is that for the + property element. The patterns bwlow ignore attribute qualifiers such as + xml:lang, they don't affect the output form. +
    +
    +            <ns:UnqualifiedStructProperty-1
    +            ... The fields as attributes, if all are simple and unqualified
    +            />
    +            <ns:UnqualifiedStructProperty-2 rdf:parseType="Resource">
    +            ... The fields as elements, if none are simple and unqualified
    +            </ns:UnqualifiedStructProperty-2>
    +            <ns:UnqualifiedStructProperty-3>
    +            <rdf:Description
    +            ... The simple and unqualified fields as attributes
    +            >
    +            ... The compound or qualified fields as elements
    +            </rdf:Description>
    +            </ns:UnqualifiedStructProperty-3>
    +            <ns:UnqualifiedArrayProperty>
    +            <rdf:Bag> or Seq or Alt
    +            ... Array items as rdf:li elements, same forms as top level properties
    +            </rdf:Bag>
    +            </ns:UnqualifiedArrayProperty>
    +            <ns:QualifiedProperty rdf:parseType="Resource">
    +            <rdf:value> ... Property "value"
    +            following the unqualified forms ... </rdf:value>
    +            ... Qualifiers looking like named struct fields
    +            </ns:QualifiedProperty>
    +            
    +
    + *** Consider numbered array items, but has compatibility problems. + Consider qualified form with rdf:Description and attributes. +
    + the parent node + the current indent level + Forwards writer exceptions + If qualifier and element fields are mixed. + +
    + + Serializes a simple property. + an XMPNode + Returns an array containing the flags emitEndTag and indentEndTag. + Forwards the writer exceptions. + + + Serializes an array property. + an XMPNode + the current indent level + Forwards the writer exceptions. + If qualifier and element fields are mixed. + + + + Serializes a struct property. + an XMPNode + the current indent level + Flag if the element has resource qualifier + Returns true if an end flag shall be emitted. + Forwards the writer exceptions. + If qualifier and element fields are mixed. + + + + Serializes the general qualifier. + the root node of the subtree + the current indent level + Forwards all writer exceptions. + If qualifier and element fields are mixed. + + + + + Serializes one schema with all contained properties in pretty-printed + manner.
    + Each schema's properties are written to a single + rdf:Description element. +
    + + Serializes one schema with all contained properties in pretty-printed + manner.
    + Each schema's properties are written to a single + rdf:Description element. All of the necessary namespaces are declared in + the rdf:Description element. The baseIndent is the base level for the + entire serialization, that of the x:xmpmeta element. An xml:lang + qualifier is written as an attribute of the property start tag, not by + itself forcing the qualified property form. +
    +
    +            <rdf:Description rdf:about="TreeName" xmlns:ns="URI" ... >
    +            ... The actual properties of the schema, see SerializePrettyRDFProperty
    +            <!-- ns1:Alias is aliased to ns2:Actual -->  ... If alias comments are wanted
    +            </rdf:Description>
    +            
    +
    +
    + a schema node + + Forwarded writer exceptions + +
    + + Writes all used namespaces of the subtree in node to the output. + + Writes all used namespaces of the subtree in node to the output. + The subtree is recursivly traversed. + + the root node of the subtree + a set containing currently used prefixes + the current indent level + Forwards all writer exceptions. + + + Writes one namespace declaration to the output. + a namespace prefix (without colon) or a complete qname (when namespace == null) + + the a namespace + a set containing currently used prefixes + the current indent level + Forwards all writer exceptions. + + + Start the outer rdf:Description element, including all needed xmlns attributes. + + + Start the outer rdf:Description element, including all needed xmlns attributes. + Leave the element open so that the compact form can add property attributes. + + If the writing to + + + + Recursively handles the "value" for a node. + + Recursively handles the "value" for a node. It does not matter if it is a + top level property, a field of a struct, or an item of an array. The + indent is that for the property element. An xml:lang qualifier is written + as an attribute of the property start tag, not by itself forcing the + qualified property form. The patterns below mostly ignore attribute + qualifiers like xml:lang. Except for the one struct case, attribute + qualifiers don't affect the output form. +
    +
    +            <ns:UnqualifiedSimpleProperty>value</ns:UnqualifiedSimpleProperty>
    +            <ns:UnqualifiedStructProperty> (If no rdf:resource qualifier)
    +            <rdf:Description>
    +            ... Fields, same forms as top level properties
    +            </rdf:Description>
    +            </ns:UnqualifiedStructProperty>
    +            <ns:ResourceStructProperty rdf:resource="URI"
    +            ... Fields as attributes
    +            >
    +            <ns:UnqualifiedArrayProperty>
    +            <rdf:Bag> or Seq or Alt
    +            ... Array items as rdf:li elements, same forms as top level properties
    +            </rdf:Bag>
    +            </ns:UnqualifiedArrayProperty>
    +            <ns:QualifiedProperty>
    +            <rdf:Description>
    +            <rdf:value> ... Property "value" following the unqualified
    +            forms ... </rdf:value>
    +            ... Qualifiers looking like named struct fields
    +            </rdf:Description>
    +            </ns:QualifiedProperty>
    +            
    +
    +
    + the property node + property shall be rendered as attribute rather than tag + + + use canonical form with inner description tag or + the compact form with rdf:ParseType="resource" attribute. + + the current indent level + Forwards all writer exceptions. + If "rdf:resource" and general qualifiers are mixed. + +
    + + Writes the array start and end tags. + an array node + flag if its the start or end tag + the current indent level + forwards writer exceptions + + + Serializes the node value in XML encoding. + + Serializes the node value in XML encoding. Its used for tag bodies and + attributes. Note: The attribute is always limited by quotes, + thats why &apos; is never serialized. Note: + Control chars are written unescaped, but if the user uses others than tab, LF + and CR the resulting XML will become invalid. + + the value of the node + flag if value is an attribute value + + + + + Writes indents and automatically includes the baseindend from the options. + + number of indents to write + forwards exception + + + Writes a char to the output. + a char + forwards writer exceptions + + + Writes a String to the output. + a String + forwards writer exceptions + + + Writes an amount of chars, mostly spaces + number of chars + a char + + + + Writes a newline according to the options. + Forwards exception + + + 11.08.2006 + + + + U+0022 ASCII space
    + U+3000, ideographic space
    + U+303F, ideographic half fill space
    + U+2000..U+200B, en quad through zero width space +
    +
    + + + U+002C, ASCII comma
    + U+FF0C, full width comma
    + U+FF64, half width ideographic comma
    + U+FE50, small comma
    + U+FE51, small ideographic comma
    + U+3001, ideographic comma
    + U+060C, Arabic comma
    + U+055D, Armenian comma +
    +
    + + + U+003B, ASCII semicolon
    + U+FF1B, full width semicolon
    + U+FE54, small semicolon
    + U+061B, Arabic semicolon
    + U+037E, Greek "semicolon" (really a question mark) +
    +
    + + + U+0022 ASCII quote
    + The square brackets are not interpreted as quotes anymore (bug #2674672) + (ASCII '[' (0x5B) and ']' (0x5D) are used as quotes in Chinese and + Korean.)
    + U+00AB and U+00BB, guillemet quotes
    + U+3008..U+300F, various quotes.
    + U+301D..U+301F, double prime quotes.
    + U+2015, dash quote.
    + U+2018..U+201F, various quotes.
    + U+2039 and U+203A, guillemet quotes. +
    +
    + + + U+0000..U+001F ASCII controls
    + U+2028, line separator.
    + U+2029, paragraph separator. +
    +
    + + Private constructor, as + + + + The XMP object containing the array to be catenated. + + The schema namespace URI for the array. Must not be null or + the empty string. + + + The name of the array. May be a general path expression, must + not be null or the empty string. Each item in the array must + be a simple string value. + + + The string to be used to separate the items in the catenated + string. Defaults to "; ", ASCII semicolon and space + (U+003B, U+0020). + + + The characters to be used as quotes around array items that + contain a separator. Defaults to '"' + + Option flag to control the catenation. + Returns the string containing the catenated array items. + Forwards the Exceptions from the metadata processing + + + + + see + + + The XMP object containing the array to be updated. + + The schema namespace URI for the array. Must not be null or + the empty string. + + + The name of the array. May be a general path expression, must + not be null or the empty string. Each item in the array must + be a simple string value. + + The string to be separated into the array items. + Option flags to control the separation. + Flag if commas shall be preserved + Forwards the Exceptions from the metadata processing + + + + Utility to find or create the array used by separateArrayItems(). + + a the namespace fo the array + the name of the array + the options for the array if newly created + the xmp object + Returns the array node. + Forwards exceptions + + + + The XMP object containing the properties to be removed. + + Optional schema namespace URI for the properties to be + removed. + + Optional path expression for the property to be removed. + + Option flag to control the deletion: do internal properties in + addition to external properties. + + + Option flag to control the deletion: Include aliases in the + "named schema" case above. + + If metadata processing fails + + + + + The source XMP object. + The destination XMP object. + Do internal properties in addition to external properties. + + Replace the values of existing properties. + Delete destination values if source property is empty. + + Forwards the Exceptions from the metadata processing + + + + + Remove all schema children according to the flag + doAllProperties. + + + Remove all schema children according to the flag + doAllProperties. Empty schemas are automatically remove + by XMPNode + + a schema node + flag if all properties or only externals shall be removed. + + Returns true if the schema is empty after the operation. + + + + The destination XMP object. + the source node + the parent of the destination node + Replace the values of existing properties. + + flag if properties with empty values should be deleted + in the destination object. + + + + + Compares two nodes including its children and qualifier. + an XMPNode + an XMPNode + Returns true if the nodes are equal, false otherwise. + Forwards exceptions to the calling method. + + + + Make sure the separator is OK. + + Make sure the separator is OK. It must be one semicolon surrounded by + zero or more spaces. Any of the recognized semicolons or spaces are + allowed. + + + + + + + Make sure the open and close quotes are a legitimate pair and return the + correct closing quote or an exception. + + opened and closing quote in a string + the open quote + Returns a corresponding closing quote. + + + + + Classifies the character into normal chars, spaces, semicola, quotes, + control chars. + + a char + Return the character kind. + + + the open quote char + Returns the matching closing quote for an open quote. + + + Add quotes to the item. + the array item + the open quote character + the closing quote character + flag if commas are allowed + Returns the value in quotes. + + + a character + the opening quote char + the closing quote char + Return it the character is a surrounding quote. + + + a character + the opening quote char + the closing quote char + Returns true if the character is a closing quote. + + + Representates an XMP XMPPath with segment accessor methods. + 28.02.2006 + + + Marks a struct field step , also for top level nodes (schema "fields"). + + + Marks a qualifier step. + + Marks a qualifier step. + Note: Order is significant to separate struct/qual from array kinds! + + + + Marks an array index step + + + stores the segments of an XMPPath + + + Append a path segment + the segment to add + + + the index of the segment to return + Returns a path segment. + + + Returns the size of the xmp path. + + + Return a single String explaining which certificate was verified, how and why. + + + + Parser for XMP XPaths. + 01.03.2006 + + + Private constructor + + + + + + + + + Parses a struct segment + the current position in the path + Retusn the segment or an errror + If the sement is empty + + + Parses an array index segment. + the xmp path + Returns the segment or an error + thrown on xmp path errors + + + + Parses the root node of an XMP Path, checks if namespace and prefix fit together + and resolve the property to the base property if it is an alias. + + the root namespace + the parsing position helper + the path to contribute to + If the path is not valid. + + + + Verifies whether the qualifier name is not XML conformant or the + namespace prefix has not been registered. + + a qualifier name + If the name is not conformant + + + Verify if an XML name is conformant. + an XML name + When the name is not XML conformant + + + + This objects contains all needed char positions to parse. + + + the complete path + + + the start of a segment name + + + the end of a segment name + + + the begin of a step + + + the end of a step + + + A segment of a parsed XMPPath. + 23.06.2006 + + + name of the path segment + + + kind of the path segment + + + flag if segment is an alias + + + alias form if applicable + + + Constructor with initial values. + the name of the segment + + + Constructor with initial values. + the name of the segment + the kind of the segment + + + Returns the kind. + + + The kind to set. + + + Returns the name. + + + The name to set. + + + the flag to set + + + Returns the alias. + + + Returns the aliasForm if this segment has been created by an alias. + + + the aliasForm to set + + + + + + Options for XMPSchemaRegistryImpl#registerAlias. + 20.02.2006 + + + The base class for a collection of 32 flag bits. + + The base class for a collection of 32 flag bits. Individual flags are defined as enum value bit + masks. Inheriting classes add convenience accessor methods. + + 24.01.2006 + + + the internal int containing all options + + + a map containing the bit names + + + The default constructor. + + + Constructor with the options bit mask. + the options bit mask + If the options are not correct + + + Resets the options. + + + an option bitmask + Returns true, if this object is equal to the given options. + + + an option bitmask + Returns true, if this object contains all given options. + + + an option bitmask + Returns true, if this object contain at least one of the given options. + + + the binary bit or bits that are requested + Returns if all of the requested bits are set or not. + + + the binary bit or bits that shall be set to the given value + the boolean value to set + + + Is friendly to access it during the tests. + Returns the options. + + + The options to set. + + + + + + + + + + Creates a human readable string from the set options. + + Creates a human readable string from the set options. Note: This method is quite + expensive and should only be used within tests or as + + + Returns a String listing all options that are set to true by their name, + like "option1 | option4". + + + + Returns the options as hex bitmask. + + + To be implemeted by inheritants. + Returns a bit mask where all valid option bits are set. + + + To be implemeted by inheritants. + a single, valid option bit. + Returns a human readable name for an option bit. + + + The inheriting option class can do additional checks on the options. + + The inheriting option class can do additional checks on the options. + Note: For performance reasons this method is only called + when setting bitmasks directly. + When get- and set-methods are used, this method must be called manually, + normally only when the Options-object has been created from a client + (it has to be made public therefore). + + the bitmask to check. + Thrown if the options are not consistent. + + + Checks options before they are set. + + Checks options before they are set. + First it is checked if only defined options are used, + second the additional + + -method is called. + + the options to check + Thrown if the options are invalid. + + + Looks up or asks the inherited class for the name of an option bit. + + Looks up or asks the inherited class for the name of an option bit. + Its save that there is only one valid option handed into the method. + + a single option bit + Returns the option name or undefined. + + + Returns the optionNames map and creates it if required. + + + This is a direct mapping. + This is a direct mapping. The actual data type does not matter. + + + The actual is an unordered array, the alias is to the first element of the array. + + + The actual is an ordered array, the alias is to the first element of the array. + + + The actual is an alternate array, the alias is to the first element of the array. + + + The actual is an alternate text array, the alias is to the 'x-default' element of the array. + + + + + + the options to init with + If options are not consistant + + + Returns if the alias is of the simple form. + + + Returns the option. + + + the value to set + Returns the instance to call more set-methods. + + + Returns the option. + + + the value to set + Returns the instance to call more set-methods. + + + Returns the option. + + + the value to set + Returns the instance to call more set-methods. + + + Returns the option. + + + the value to set + Returns the instance to call more set-methods. + + + + returns a + + s object + + If the options are not consistant. + + + + + + + + + Options for XMPIterator construction. + 24.01.2006 + + + Just do the immediate children of the root, default is subtree. + + + Just do the leaf nodes, default is all nodes in the subtree. + + Just do the leaf nodes, default is all nodes in the subtree. + Bugfix #2658965: If this option is set the Iterator returns the namespace + of the leaf instead of the namespace of the base property. + + + + Return just the leaf part of the path, default is the full path. + + + Omit all qualifiers. + + + Returns whether the option is set. + + + Returns whether the option is set. + + + Returns whether the option is set. + + + Returns whether the option is set. + + + Sets the option and returns the instance. + the value to set + Returns the instance to call more set-methods. + + + Sets the option and returns the instance. + the value to set + Returns the instance to call more set-methods. + + + Sets the option and returns the instance. + the value to set + Returns the instance to call more set-methods. + + + Sets the option and returns the instance. + the value to set + Returns the instance to call more set-methods. + + + + + + + + + + Options for + + . + + 24.01.2006 + + + Require a surrounding "x:xmpmeta" element in the xml-document. + + + Do not reconcile alias differences, throw an exception instead. + + + Convert ASCII control characters 0x01 - 0x1F (except tab, cr, and lf) to spaces. + + + If the input is not unicode, try to parse it as ISO-8859-1. + + + Do not carry run the XMPNormalizer on a packet, leave it as it is. + + + Sets the options to the default values. + + + Returns the requireXMPMeta. + + + the value to set + Returns the instance to call more set-methods. + + + Returns the strictAliasing. + + + the value to set + Returns the instance to call more set-methods. + + + Returns the strictAliasing. + + + the value to set + Returns the instance to call more set-methods. + + + Returns the strictAliasing. + + + the value to set + Returns the instance to call more set-methods. + + + Returns the option "omit normalization". + + + the value to set + Returns the instance to call more set-methods. + + + + + + + + + + The property flags are used when properties are fetched from the XMPMeta-object + and provide more detailed information about the property. + + 03.07.2006 + + + may be used in the future + + + Updated by iText. + Updated by iText. Indicates if the property should be writted as a separate node + + + Default constructor + + + Intialization constructor + the initialization options + If the options are not valid + + + + Return whether the property value is a URI. It is serialized to RDF using the + rdf:resource attribute. Not mandatory for URIs, but considered RDF-savvy. + + + + the value to set + Returns this to enable cascaded options. + + + + Return whether the property has qualifiers. These could be an xml:lang + attribute, an rdf:type property, or a general qualifier. See the + introductory discussion of qualified properties for more information. + + + + the value to set + Returns this to enable cascaded options. + + + + Return whether this property is a qualifier for some other property. Note that if the + qualifier itself has a structured value, this flag is only set for the top node of + the qualifier's subtree. Qualifiers may have arbitrary structure, and may even have + qualifiers. + + + + the value to set + Returns this to enable cascaded options. + + + Return whether this property has an xml:lang qualifier. + + + the value to set + Returns this to enable cascaded options. + + + Return whether this property has an rdf:type qualifier. + + + the value to set + Returns this to enable cascaded options. + + + Return whether this property contains nested fields. + + + the value to set + Returns this to enable cascaded options. + + + + Return whether this property is an array. By itself this indicates a general + unordered array. It is serialized using an rdf:Bag container. + + + + the value to set + Returns this to enable cascaded options. + + + + Return whether this property is an ordered array. Appears in conjunction with + getPropValueIsArray(). It is serialized using an rdf:Seq container. + + + + the value to set + Returns this to enable cascaded options. + + + + Return whether this property is an alternative array. Appears in conjunction with + getPropValueIsArray(). It is serialized using an rdf:Alt container. + + + + the value to set + Returns this to enable cascaded options. + + + + Return whether this property is an alt-text array. Appears in conjunction with + getPropArrayIsAlternate(). It is serialized using an rdf:Alt container. + Each array element is a simple property with an xml:lang attribute. + + + + the value to set + Returns this to enable cascaded options. + + + Returns whether the SCHEMA_NODE option is set. + + + the option DELETE_EXISTING to set + Returns this to enable cascaded options. + + + Returns whether the property is of composite type - an array or a struct. + + + Returns whether the property is of composite type - an array or a struct. + + + Compares two options set for array compatibility. + other options + Returns true if the array options of the sets are equal. + + + Merges the set options of a another options object with this. + + Merges the set options of a another options object with this. + If the other options set is null, this objects stays the same. + + other options + If illegal options are provided + + + Returns true if only array options are set. + + + + + + + + + + Checks that a node not a struct and array at the same time; + and URI cannot be a struct. + + the bitmask to check. + Thrown if the options are not consistent. + + + + Omit the XML packet wrapper. + + + Mark packet as read-only. + Mark packet as read-only. Default is a writeable packet. + + + Use a compact form of RDF. + + Use a compact form of RDF. + The compact form is the default serialization format (this flag is technically ignored). + To serialize to the canonical form, set the flag USE_CANONICAL_FORMAT. + If both flags "compact" and "canonical" are set, canonical is used. + + + + Use the canonical form of RDF if set. + Use the canonical form of RDF if set. By default the compact form is used + + + Include a padding allowance for a thumbnail image. + + Include a padding allowance for a thumbnail image. If no xmp:Thumbnails property + is present, the typical space for a JPEG thumbnail is used. + + + + The padding parameter provides the overall packet length. + + The padding parameter provides the overall packet length. The actual amount of padding is + computed. An exception is thrown if the packet exceeds this length with no padding. + + + + + Sort the struct properties and qualifier before serializing + + + Bit indicating little endian encoding, unset is big endian + + + Bit indication UTF16 encoding. + + + UTF8 encoding; this is the default + + + UTF16BE encoding + + + UTF16LE encoding + + + The amount of padding to be added if a writeable XML packet is created. + + The amount of padding to be added if a writeable XML packet is created. If zero is passed + (the default) an appropriate amount of padding is computed. + + + + The string to be used as a line terminator. + + The string to be used as a line terminator. If empty it defaults to; linefeed, U+000A, the + standard XML newline. + + + + + The string to be used for each level of indentation in the serialized + RDF. + + + The string to be used for each level of indentation in the serialized + RDF. If empty it defaults to two ASCII spaces, U+0020. + + + + + The number of levels of indentation to be used for the outermost XML element in the + serialized RDF. + + + The number of levels of indentation to be used for the outermost XML element in the + serialized RDF. This is convenient when embedding the RDF in other text, defaults to 0. + + + + Omits the Toolkit version attribute, not published, only used for Unit tests. + + + Default constructor. + + + Constructor using inital options + the inital options + Thrown if options are not consistant. + + + Returns the option. + + + the value to set + Returns the instance to call more set-methods. + + + Returns the option. + + + the value to set + Returns the instance to call more set-methods. + + + Returns the option. + + + the value to set + Returns the instance to call more set-methods. + + + Returns the option. + + + the value to set + Returns the instance to call more set-methods. + + + Returns the option. + + + the value to set + Returns the instance to call more set-methods. + + + Returns the option. + + + the value to set + Returns the instance to call more set-methods. + + + Returns the option. + + + the value to set + Returns the instance to call more set-methods. + + + Returns the option. + + + the value to set + Returns the instance to call more set-methods. + + + Returns the option. + + + the value to set + Returns the instance to call more set-methods. + + + Returns the option. + + + the value to set + Returns the instance to call more set-methods. + + + Returns the baseIndent. + + + The baseIndent to set. + Returns the instance to call more set-methods. + + + Returns the indent. + + + The indent to set. + Returns the instance to call more set-methods. + + + Returns the newline. + + + The newline to set. + Returns the instance to call more set-methods. + + + Returns the padding. + + + The padding to set. + Returns the instance to call more set-methods. + + + + Returns whether the Toolkit version attribute shall be omitted. + Note: This options can only be set by unit tests. + + + + Returns the encoding as Java encoding String. + + + Returns clone of this SerializeOptions-object with the same options set. + Cannot happen in this place. + + + + + + + + + Class that contains several constants. + + + External Contributors to the resource (other than the authors). + + + The extent or scope of the resource. + + + The authors of the resource (listed in order of precedence, if significant). + + + Date(s) that something interesting happened to the resource. + + + A textual description of the content of the resource. + A textual description of the content of the resource. Multiple values may be present for different languages. + + + + The file format used when saving the resource. + The file format used when saving the resource. Tools and applications should set this property to the save format of the data. It may include appropriate qualifiers. + + + + An unordered array of text strings that unambiguously identify the resource within a given context. + + + + An unordered array specifying the languages used in the resource. + + + Publishers. + + + Relationships to other documents. + + + Informal rights statement, selected by language. + + + Unique identifier of the work from which this resource was derived. + + + An unordered array of descriptive phrases or keywords that specify the topic of the content of the resource. + + + + The title of the document, or the name given to the resource. + The title of the document, or the name given to the resource. Typically, it will be a name by which the resource is formally known. + + + + A document type; for example, novel, poem, or working paper. + + + Keywords. + + + The PDF file version (for example: 1.0, 1.3, and so on). + + + The Producer. + + + The part + + + An unordered array specifying properties that were edited outside the authoring application. + An unordered array specifying properties that were edited outside the authoring application. Each item should contain a single namespace and XPath separated by one ASCII space (U+0020). + + + + The base URL for relative URLs in the document content. + The base URL for relative URLs in the document content. If this document contains Internet links, and those links are relative, they are relative to this base URL. This property provides a standard way for embedded relative URLs to be interpreted by tools. Web authoring tools should set the value based on their notion of where URLs will be interpreted. + + + + The date and time the resource was originally created. + + + The name of the first known tool used to create the resource. + The name of the first known tool used to create the resource. If history is present in the metadata, this value should be equivalent to that of xmpMM:History's softwareAgent property. + + + + The date and time that any metadata for this resource was last changed. + + + The date and time the resource was last modified. + + + A short informal name for the resource. + + + An alternative array of thumbnail images for a file, which can differ in characteristics such as size or image encoding. + + + + This class writes the DOM structure of the XML to the specified output. + + + Print writer. + + + Canonical output. + + + Processing XML 1.1 document. + + + Default constructor. + + + Creates an XmlDomWriter. + should the writer write canonical output or not + + + Sets whether output is canonical. + + + Sets the output stream for printing. + + + + Writes the specified node, recursively. + + + + Returns a sorted list of attributes. + + + Normalizes and prints the given string. + + + + Normalizes and print the given character. + + + + + A factory to create XMPDateTime-instances from a Calendar or an + ISO 8601 string or for the current time. + + 16.02.2006 + + + Private constructor + + + Creates an XMPDateTime from a Calendar-object. + + a Calendar-object. + An XMPDateTime-object. + + + Creates an empty XMPDateTime-object. + Returns an XMPDateTime-object. + + + Creates an XMPDateTime-object from initial values. + years + + months from 1 to 12
    + Note: Remember that the month in + + is defined from 0 to 11. + + days + Returns an XMPDateTime-object. +
    + + Creates an XMPDateTime-object from initial values. + years + + months from 1 to 12
    + Note: Remember that the month in + + is defined from 0 to 11. + + days + hours + minutes + seconds + nanoseconds + Returns an XMPDateTime-object. +
    + + Creates an XMPDateTime from an ISO 8601 string. + The ISO 8601 string representation of the date/time. + An XMPDateTime-object. + When the ISO 8601 string is non-conform + + + + Obtain the current date and time. + + Returns The returned time is UTC, properly adjusted for the local time zone. The + resolution of the time is not guaranteed to be finer than seconds. + + + + + Sets the local time zone without touching any other Any existing time zone value is replaced, + the other date/time fields are not adjusted in any way. + + the XMPDateTime variable containing the value to be modified. + + Returns an updated XMPDateTime-object. + + + Make sure a time is UTC. + + Make sure a time is UTC. If the time zone is not UTC, the time is + adjusted and the time zone set to be UTC. + + + the XMPDateTime variable containing the time to + be modified. + + Returns an updated XMPDateTime-object. + + + Make sure a time is local. + + Make sure a time is local. If the time zone is not the local zone, the time is adjusted and + the time zone set to be local. + + the XMPDateTime variable containing the time to be modified. + + Returns an updated XMPDateTime-object. + + + 21.09.2006 + + + Note: This is an error code introduced by Java. + + + This exception wraps all errors that occur in the XMP Toolkit. + 16.02.2006 + + + the errorCode of the XMP toolkit + + + Constructs an exception with a message and an error code. + the message + the error code + + + Constructs an exception with a message, an error code and a Throwable + the error message. + the error code + the exception source + + + Returns the errorCode. + + + Creates XMPMeta-instances from an InputStream + 30.01.2006 + + + The singleton instance of the XMPSchemaRegistry. + + + cache for version info + + + Hides public constructor + + + Returns the singleton instance of the XMPSchemaRegistry. + + + Returns an empty XMPMeta-object. + + + Parsing with default options. + an InputStream + Returns the XMPMeta-object created from the input. + If the file is not well-formed XML or if the parsing fails. + + + + + + Parsing with default options. + a String contain an XMP-file. + Returns the XMPMeta-object created from the input. + If the file is not well-formed XML or if the parsing fails. + + + + + Creates an XMPMeta-object from a string. + a String contain an XMP-file. + Options controlling the parsing. + Returns the XMPMeta-object created from the input. + If the file is not well-formed XML or if the parsing fails. + + + + + Parsing with default options. + a String contain an XMP-file. + Returns the XMPMeta-object created from the input. + If the file is not well-formed XML or if the parsing fails. + + + + + Creates an XMPMeta-object from a byte-buffer. + a String contain an XMP-file. + Options controlling the parsing. + Returns the XMPMeta-object created from the input. + If the file is not well-formed XML or if the parsing fails. + + + + + + Serializes an XMPMeta-object as RDF into an OutputStream + with default options. + + a metadata object + an OutputStream to write the serialized RDF to. + on serializsation errors. + + + + Serializes an XMPMeta-object as RDF into an OutputStream. + a metadata object + + Options to control the serialization (see + + ). + + an OutputStream to write the serialized RDF to. + on serializsation errors. + + + + Serializes an XMPMeta-object as RDF into a byte buffer. + a metadata object + + Options to control the serialization (see + + ). + + Returns a byte buffer containing the serialized RDF. + on serializsation errors. + + + + Serializes an XMPMeta-object as RDF into a string. + + Serializes an XMPMeta-object as RDF into a string. Note: Encoding + is ignored when serializing to a string. + + a metadata object + + Options to control the serialization (see + + ). + + Returns a string containing the serialized RDF. + on serializsation errors. + + + + Asserts that xmp is compatible to XMPMetaImpl.s + + + Resets the schema registry to its original state (creates a new one). + + Resets the schema registry to its original state (creates a new one). + Be careful this might break all existing XMPMeta-objects and should be used + only for testing purpurses. + + + + Obtain version information. + + Obtain version information. The XMPVersionInfo singleton is created the first time + its requested. + + Returns the version information. + + + + Returns the primary release number, the "1" in version "1.2.3". + + + Returns the secondary release number, the "2" in version "1.2.3". + + + Returns the tertiary release number, the "3" in version "1.2.3". + + + Returns a rolling build number, monotonically increasing in a release. + + + Returns true if this is a debug build. + + + Returns a comprehensive version information string. + + + + Private constructor + + + Compose the path expression for an item in an array. + + The name of the array. May be a general path expression, must not be + null or the empty string. + + + The index of the desired item. Arrays in XMP are indexed from 1. + 0 and below means last array item and renders as [last()]. + + + Returns the composed path basing on fullPath. This will be of the form + ns:arrayName[i], where "ns" is the prefix for schemaNS and + "i" is the decimal representation of itemIndex. + + Throws exeption if index zero is used. + + + + Compose the path expression for a field in a struct. + + Compose the path expression for a field in a struct. The result can be added to the + path of + + + The namespace URI for the field. Must not be null or the empty + string. + + + The name of the field. Must be a simple XML name, must not be + null or the empty string. + + + Returns the composed path. This will be of the form + ns:structName/fNS:fieldName, where "ns" is the prefix for + schemaNS and "fNS" is the prefix for fieldNS. + + Thrown if the path to create is not valid. + + + + Compose the path expression for a qualifier. + + The namespace URI for the qualifier. May be null or the empty + string if the qualifier is in the XML empty namespace. + + + The name of the qualifier. Must be a simple XML name, must not be + null or the empty string. + + + Returns the composed path. This will be of the form + ns:propName/?qNS:qualName, where "ns" is the prefix for + schemaNS and "qNS" is the prefix for qualNS. + + Thrown if the path to create is not valid. + + + + Compose the path expression to select an alternate item by language. + + Compose the path expression to select an alternate item by language. The + path syntax allows two forms of "content addressing" that may + be used to select an item in an array of alternatives. The form used in + ComposeLangSelector lets you select an item in an alt-text array based on + the value of its xml:lang qualifier. The other form of content + addressing is shown in ComposeFieldSelector. \note ComposeLangSelector + does not supplant SetLocalizedText or GetLocalizedText. They should + generally be used, as they provide extra logic to choose the appropriate + language and maintain consistency with the 'x-default' value. + ComposeLangSelector gives you an path expression that is explicitly and + only for the language given in the langName parameter. + + + The name of the array. May be a general path expression, must + not be null or the empty string. + + The RFC 3066 code for the desired language. + + Returns the composed path. This will be of the form + ns:arrayName[@xml:lang='langName'], where + "ns" is the prefix for schemaNS. + + + + + ParameterAsserts that a qualifier namespace is set. + a qualifier namespace + Qualifier schema is null or empty + + + + ParameterAsserts that a qualifier name is set. + a qualifier name or path + Qualifier name is null or empty + + + + ParameterAsserts that a struct field namespace is set. + a struct field namespace + Struct field schema is null or empty + + + + ParameterAsserts that a struct field name is set. + a struct field name or path + Struct field name is null or empty + + + + Utility methods for XMP. + + Utility methods for XMP. I included only those that are different from the + Java default conversion utilities. + + 21.02.2006 + + + Private constructor + + + Create a single edit string from an array of strings. + The XMP object containing the array to be catenated. + + The schema namespace URI for the array. Must not be null or + the empty string. + + + The name of the array. May be a general path expression, must + not be null or the empty string. Each item in the array must + be a simple string value. + + + The string to be used to separate the items in the catenated + string. Defaults to "; ", ASCII semicolon and space + (U+003B, U+0020). + + + The characters to be used as quotes around array items that + contain a separator. Defaults to '"' + + Option flag to control the catenation. + Returns the string containing the catenated array items. + Forwards the Exceptions from the metadata processing + + + + Separate a single edit string into an array of strings. + The XMP object containing the array to be updated. + + The schema namespace URI for the array. Must not be null or + the empty string. + + + The name of the array. May be a general path expression, must + not be null or the empty string. Each item in the array must + be a simple string value. + + The string to be separated into the array items. + Option flags to control the separation. + Flag if commas shall be preserved + Forwards the Exceptions from the metadata processing + + + + + Alias without the new option deleteEmptyValues. + The source XMP object. + The destination XMP object. + Do internal properties in addition to external properties. + Replace the values of existing properties. + Forwards the Exceptions from the metadata processing + + + + + + Convert from boolean to string. + a boolean value + + The XMP string representation of the boolean. The values used are + given by the constnts + + and + + . + + + + Converts a string value to an int. + the string value + Returns an int. + + If the rawValue is null or empty or the + conversion fails. + + + + + Convert from int to string. + an int value + The string representation of the int. + + + Converts a string value to a long. + the string value + Returns a long. + + If the rawValue is null or empty or the + conversion fails. + + + + + Convert from long to string. + a long value + The string representation of the long. + + + Converts a string value to a double. + the string value + Returns a double. + + If the rawValue is null or empty or the + conversion fails. + + + + + Convert from long to string. + a long value + The string representation of the long. + + + Converts a string value to an XMPDateTime. + the string value + Returns an XMPDateTime-object. + + If the rawValue is null or empty or the + conversion fails. + + + + + Convert from XMPDateTime to string. + an XMPDateTime + The string representation of the long. + + + Convert from a byte array to a base64 encoded string. + the byte array to be converted + Returns the base64 string. + + + Decode from Base64 encoded string to raw data. + a base64 encoded string + Returns a byte array containg the decoded string. + Thrown if the given string is not property base64 encoded + + + + return the X9ECParameters object for the named curve represented by + the passed in object identifier. Null if the curve isn't present. + + @param oid an object identifier representing a named curve, if present. + + + return the object identifier signified by the passed in name. Null + if there is no object identifier associated with name. + + @return the object identifier associated with name, if present. + + + return the named curve name represented by the given object identifier. + + + returns an enumeration containing the name strings for curves + contained in this structure. + + + Return the DER encoding of the object, null if the DER encoding can not be made. + + @return a DER byte array, null otherwise. + + + a general purpose ASN.1 decoder - note: this class differs from the + others in that it returns null after it has read the last object in + the stream. If an ASN.1 Null is encountered a Der/BER Null object is + returned. + + + Create an ASN1InputStream where no DER object will be longer than limit. + + @param input stream containing ASN.1 encoded data. + @param limit maximum size of a DER encoded object. + + + Create an ASN1InputStream based on the input byte array. The length of DER objects in + the stream is automatically limited to the length of the input array. + + @param input array containing ASN.1 encoded data. + + + build an object given its tag and the number of bytes to construct it from. + + + A Null object. + + + Create a base ASN.1 object from a byte array. + The byte array to parse. + The base ASN.1 object represented by the byte array. + If there is a problem parsing the data. + + + Read a base ASN.1 object from a stream. + The stream to parse. + The base ASN.1 object represented by the byte array. + If there is a problem parsing the data. + + + return an Octet string from a tagged object. + + @param obj the tagged object holding the object we want. + @param explicitly true if the object is meant to be explicitly + tagged false otherwise. + @exception ArgumentException if the tagged object cannot + be converted. + + + return an Octet string from the given object. + + @param obj the object we want converted. + @exception ArgumentException if the object cannot be converted. + + + @param string the octets making up the octet string. + + + return an Asn1Sequence from the given object. + + @param obj the object we want converted. + @exception ArgumentException if the object cannot be converted. + + + Return an ASN1 sequence from a tagged object. There is a special + case here, if an object appears to have been explicitly tagged on + reading but we were expecting it to be implicitly tagged in the + normal course of events it indicates that we lost the surrounding + sequence - so we need to add it back (this will happen if the tagged + object is a sequence that contains other sequences). If you are + dealing with implicitly tagged sequences you really should + be using this method. + + @param obj the tagged object. + @param explicitly true if the object is meant to be explicitly tagged, + false otherwise. + @exception ArgumentException if the tagged object cannot + be converted. + + + return the object at the sequence position indicated by index. + + @param index the sequence number (starting at zero) of the object + @return the object at the sequence position indicated by index. + + + return an ASN1Set from the given object. + + @param obj the object we want converted. + @exception ArgumentException if the object cannot be converted. + + + Return an ASN1 set from a tagged object. There is a special + case here, if an object appears to have been explicitly tagged on + reading but we were expecting it to be implicitly tagged in the + normal course of events it indicates that we lost the surrounding + set - so we need to add it back (this will happen if the tagged + object is a sequence that contains other sequences). If you are + dealing with implicitly tagged sets you really should + be using this method. + + @param obj the tagged object. + @param explicitly true if the object is meant to be explicitly tagged + false otherwise. + @exception ArgumentException if the tagged object cannot + be converted. + + + return the object at the set position indicated by index. + + @param index the set number (starting at zero) of the object + @return the object at the set position indicated by index. + + + ASN.1 TaggedObject - in ASN.1 notation this is any object preceded by + a [n] where n is some number - these are assumed to follow the construction + rules (as with sequences). + + + @param tagNo the tag number for this object. + @param obj the tagged object. + + + @param explicitly true if the object is explicitly tagged. + @param tagNo the tag number for this object. + @param obj the tagged object. + + + return whether or not the object may be explicitly tagged. +

    + Note: if the object has been read from an input stream, the only + time you can be sure if isExplicit is returning the true state of + affairs is if it returns false. An implicitly tagged object may appear + to be explicitly tagged, so you need to understand the context under + which the reading was done as well, see GetObject below.

    +
    + + return whatever was following the tag. +

    + Note: tagged objects are generally context dependent if you're + trying to extract a tagged object you should be going via the + appropriate GetInstance method.

    +
    + + Return the object held in this tagged object as a parser assuming it has + the type of the passed in tag. If the object doesn't have a parser + associated with it, the base object is returned. + + + Base class for an application specific object + + + Return the enclosed object assuming explicit tagging. + + @return the resulting object + @throws IOException if reconstruction fails. + + + Return the enclosed object assuming implicit tagging. + + @param derTagNo the type tag that should be applied to the object's contents. + @return the resulting object + @throws IOException if reconstruction fails. + + + basic interface for Der string objects. + + + return a Bit string from the passed in object + + @exception ArgumentException if the object cannot be converted. + + + return a Bit string from a tagged object. + + @param obj the tagged object holding the object we want + @param explicitly true if the object is meant to be explicitly + tagged false otherwise. + @exception ArgumentException if the tagged object cannot + be converted. + + + @param data the octets making up the bit string. + @param padBits the number of extra bits at the end of the string. + + + Return the octets contained in this BIT STRING, checking that this BIT STRING really + does represent an octet aligned string. Only use this method when the standard you are + following dictates that the BIT STRING will be octet aligned. + + @return a copy of the octet aligned data. + + + @return the value of the bit string as an int (truncating if necessary) + + + A BER Null object. + + + A Null object. + + + The octets making up the octet string. + + + convert a vector of octet strings into a single byte string + + + The octets making up the octet string. + + + return the DER octets that make up this string. + + + create an empty sequence + + + create a sequence containing one object + + + create a sequence containing a vector of objects. + + + create an empty sequence + + + create a sequence containing one object + + + create a sequence containing a vector of objects. + + + A Der encoded set object + + + create an empty set + + + @param obj - a single object that makes up the set. + + + @param v - a vector of objects making up the set. + + + create an empty sequence + + + create a set containing one object + + + create a set containing a vector of objects. + + + BER TaggedObject - in ASN.1 notation this is any object preceded by + a [n] where n is some number - these are assumed to follow the construction + rules (as with sequences). + + + DER TaggedObject - in ASN.1 notation this is any object preceded by + a [n] where n is some number - these are assumed to follow the construction + rules (as with sequences). + + + @param tagNo the tag number for this object. + @param obj the tagged object. + + + @param explicitly true if an explicitly tagged object. + @param tagNo the tag number for this object. + @param obj the tagged object. + + + create an implicitly tagged object that contains a zero + length sequence. + + + @param tagNo the tag number for this object. + @param obj the tagged object. + + + @param explicitly true if an explicitly tagged object. + @param tagNo the tag number for this object. + @param obj the tagged object. + + + create an implicitly tagged object that contains a zero + length sequence. + + +
    +            CAKeyUpdAnnContent ::= SEQUENCE {
    +                                        oldWithNew   CmpCertificate, -- old pub signed with new priv
    +                                        newWithOld   CmpCertificate, -- new pub signed with old priv
    +                                        newWithNew   CmpCertificate  -- new pub signed with new priv
    +             }
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +            CertConfirmContent ::= SEQUENCE OF CertStatus
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +            CertifiedKeyPair ::= SEQUENCE {
    +                                             certOrEncCert       CertOrEncCert,
    +                                             privateKey      [0] EncryptedValue      OPTIONAL,
    +                                             -- see [CRMF] for comment on encoding
    +                                             publicationInfo [1] PKIPublicationInfo  OPTIONAL
    +                  }
    +            
    + @return a basic ASN.1 object representation. +
    + + Marker interface for CHOICE objects - if you implement this in a roll-your-own + object, any attempt to tag the object implicitly will convert the tag to an + explicit one as the encoding rules require. +

    + If you use this interface your class should also implement the getInstance + pattern which takes a tag object and the tagging mode used. +

    +
    + +
    +            CertOrEncCert ::= CHOICE {
    +                                 certificate     [0] CMPCertificate,
    +                                 encryptedCert   [1] EncryptedValue
    +                      }
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +            CertRepMessage ::= SEQUENCE {
    +                                     caPubs       [1] SEQUENCE SIZE (1..MAX) OF CMPCertificate
    +                                                                                        OPTIONAL,
    +                                     response         SEQUENCE OF CertResponse
    +            }
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +            CertResponse ::= SEQUENCE {
    +                                       certReqId           INTEGER,
    +                                       -- to match this response with corresponding request (a value
    +                                       -- of -1 is to be used if certReqId is not specified in the
    +                                       -- corresponding request)
    +                                       status              PKIStatusInfo,
    +                                       certifiedKeyPair    CertifiedKeyPair    OPTIONAL,
    +                                       rspInfo             OCTET STRING        OPTIONAL
    +                                       -- analogous to the id-regInfo-utf8Pairs string defined
    +                                       -- for regInfo in CertReqMsg [CRMF]
    +                        }
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +            CertStatus ::= SEQUENCE {
    +                              certHash    OCTET STRING,
    +                              -- the hash of the certificate, using the same hash algorithm
    +                              -- as is used to create and verify the certificate signature
    +                              certReqId   INTEGER,
    +                              -- to match this confirmation with the corresponding req/rep
    +                              statusInfo  PKIStatusInfo OPTIONAL
    +            }
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +             Challenge ::= SEQUENCE {
    +                             owf                 AlgorithmIdentifier  OPTIONAL,
    +            
    +                             -- MUST be present in the first Challenge; MAY be omitted in
    +                             -- any subsequent Challenge in POPODecKeyChallContent (if
    +                             -- omitted, then the owf used in the immediately preceding
    +                             -- Challenge is to be used).
    +            
    +                             witness             OCTET STRING,
    +                             -- the result of applying the one-way function (owf) to a
    +                             -- randomly-generated INTEGER, A.  [Note that a different
    +                             -- INTEGER MUST be used for each Challenge.]
    +                             challenge           OCTET STRING
    +                             -- the encryption (under the public key for which the cert.
    +                             -- request is being made) of Rand, where Rand is specified as
    +                             --   Rand ::= SEQUENCE {
    +                             --      int      INTEGER,
    +                             --       - the randomly-generated INTEGER A (above)
    +                             --      sender   GeneralName
    +                             --       - the sender's name (as included in PKIHeader)
    +                             --   }
    +                  }
    +             
    + @return a basic ASN.1 object representation. +
    + + Note: the addition of attribute certificates is a BC extension. + + +
    +             CMPCertificate ::= CHOICE {
    +                        x509v3PKCert        Certificate
    +                        x509v2AttrCert      [1] AttributeCertificate
    +              }
    +             
    + Note: the addition of attribute certificates is a BC extension. + + @return a basic ASN.1 object representation. +
    + +
    +            CrlAnnContent ::= SEQUENCE OF CertificateList
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +            ErrorMsgContent ::= SEQUENCE {
    +                                   pKIStatusInfo          PKIStatusInfo,
    +                                   errorCode              INTEGER           OPTIONAL,
    +                                   -- implementation-specific error codes
    +                                   errorDetails           PKIFreeText       OPTIONAL
    +                                   -- implementation-specific error details
    +            }
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +            GenMsgContent ::= SEQUENCE OF InfoTypeAndValue
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +            GenRepContent ::= SEQUENCE OF InfoTypeAndValue
    +            
    + @return a basic ASN.1 object representation. +
    + + Example InfoTypeAndValue contents include, but are not limited + to, the following (un-comment in this ASN.1 module and use as + appropriate for a given environment): +
    +               id-it-caProtEncCert    OBJECT IDENTIFIER ::= {id-it 1}
    +                  CAProtEncCertValue      ::= CMPCertificate
    +               id-it-signKeyPairTypes OBJECT IDENTIFIER ::= {id-it 2}
    +                 SignKeyPairTypesValue   ::= SEQUENCE OF AlgorithmIdentifier
    +               id-it-encKeyPairTypes  OBJECT IDENTIFIER ::= {id-it 3}
    +                 EncKeyPairTypesValue    ::= SEQUENCE OF AlgorithmIdentifier
    +               id-it-preferredSymmAlg OBJECT IDENTIFIER ::= {id-it 4}
    +                  PreferredSymmAlgValue   ::= AlgorithmIdentifier
    +               id-it-caKeyUpdateInfo  OBJECT IDENTIFIER ::= {id-it 5}
    +                  CAKeyUpdateInfoValue    ::= CAKeyUpdAnnContent
    +               id-it-currentCRL       OBJECT IDENTIFIER ::= {id-it 6}
    +                  CurrentCRLValue         ::= CertificateList
    +               id-it-unsupportedOIDs  OBJECT IDENTIFIER ::= {id-it 7}
    +                  UnsupportedOIDsValue    ::= SEQUENCE OF OBJECT IDENTIFIER
    +               id-it-keyPairParamReq  OBJECT IDENTIFIER ::= {id-it 10}
    +                  KeyPairParamReqValue    ::= OBJECT IDENTIFIER
    +               id-it-keyPairParamRep  OBJECT IDENTIFIER ::= {id-it 11}
    +                  KeyPairParamRepValue    ::= AlgorithmIdentifer
    +               id-it-revPassphrase    OBJECT IDENTIFIER ::= {id-it 12}
    +                  RevPassphraseValue      ::= EncryptedValue
    +               id-it-implicitConfirm  OBJECT IDENTIFIER ::= {id-it 13}
    +                  ImplicitConfirmValue    ::= NULL
    +               id-it-confirmWaitTime  OBJECT IDENTIFIER ::= {id-it 14}
    +                  ConfirmWaitTimeValue    ::= GeneralizedTime
    +               id-it-origPKIMessage   OBJECT IDENTIFIER ::= {id-it 15}
    +                  OrigPKIMessageValue     ::= PKIMessages
    +               id-it-suppLangTags     OBJECT IDENTIFIER ::= {id-it 16}
    +                  SuppLangTagsValue       ::= SEQUENCE OF UTF8String
    +            
    +             where
    +            
    +               id-pkix OBJECT IDENTIFIER ::= {
    +                  iso(1) identified-organization(3)
    +                  dod(6) internet(1) security(5) mechanisms(5) pkix(7)}
    +             and
    +                  id-it   OBJECT IDENTIFIER ::= {id-pkix 4}
    +             
    +
    + +
    +            InfoTypeAndValue ::= SEQUENCE {
    +                                    infoType               OBJECT IDENTIFIER,
    +                                    infoValue              ANY DEFINED BY infoType  OPTIONAL
    +            }
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +            KeyRecRepContent ::= SEQUENCE {
    +                                    status                  PKIStatusInfo,
    +                                    newSigCert          [0] CMPCertificate OPTIONAL,
    +                                    caCerts             [1] SEQUENCE SIZE (1..MAX) OF
    +                                                                      CMPCertificate OPTIONAL,
    +                                    keyPairHist         [2] SEQUENCE SIZE (1..MAX) OF
    +                                                                      CertifiedKeyPair OPTIONAL
    +                         }
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +            OobCertHash ::= SEQUENCE {
    +                                 hashAlg     [0] AlgorithmIdentifier     OPTIONAL,
    +                                 certId      [1] CertId                  OPTIONAL,
    +                                 hashVal         BIT STRING
    +                                 -- hashVal is calculated over the Der encoding of the
    +                                 -- self-signed certificate with the identifier certID.
    +                  }
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +             PbmParameter ::= SEQUENCE {
    +                                   salt                OCTET STRING,
    +                                   -- note:  implementations MAY wish to limit acceptable sizes
    +                                   -- of this string to values appropriate for their environment
    +                                   -- in order to reduce the risk of denial-of-service attacks
    +                                   owf                 AlgorithmIdentifier,
    +                                   -- AlgId for a One-Way Function (SHA-1 recommended)
    +                                   iterationCount      INTEGER,
    +                                   -- number of times the OWF is applied
    +                                   -- note:  implementations MAY wish to limit acceptable sizes
    +                                   -- of this integer to values appropriate for their environment
    +                                   -- in order to reduce the risk of denial-of-service attacks
    +                                   mac                 AlgorithmIdentifier
    +                                   -- the MAC AlgId (e.g., DES-MAC, Triple-DES-MAC [PKCS11],
    +               }   -- or HMAC [RFC2104, RFC2202])
    +            
    + @return a basic ASN.1 object representation. +
    + + Creates a new PkiBody. + @param type one of the TYPE_* constants + @param content message content + + +
    +            PkiBody ::= CHOICE {       -- message-specific body elements
    +                   ir       [0]  CertReqMessages,        --Initialization Request
    +                   ip       [1]  CertRepMessage,         --Initialization Response
    +                   cr       [2]  CertReqMessages,        --Certification Request
    +                   cp       [3]  CertRepMessage,         --Certification Response
    +                   p10cr    [4]  CertificationRequest,   --imported from [PKCS10]
    +                   popdecc  [5]  POPODecKeyChallContent, --pop Challenge
    +                   popdecr  [6]  POPODecKeyRespContent,  --pop Response
    +                   kur      [7]  CertReqMessages,        --Key Update Request
    +                   kup      [8]  CertRepMessage,         --Key Update Response
    +                   krr      [9]  CertReqMessages,        --Key Recovery Request
    +                   krp      [10] KeyRecRepContent,       --Key Recovery Response
    +                   rr       [11] RevReqContent,          --Revocation Request
    +                   rp       [12] RevRepContent,          --Revocation Response
    +                   ccr      [13] CertReqMessages,        --Cross-Cert. Request
    +                   ccp      [14] CertRepMessage,         --Cross-Cert. Response
    +                   ckuann   [15] CAKeyUpdAnnContent,     --CA Key Update Ann.
    +                   cann     [16] CertAnnContent,         --Certificate Ann.
    +                   rann     [17] RevAnnContent,          --Revocation Ann.
    +                   crlann   [18] CRLAnnContent,          --CRL Announcement
    +                   pkiconf  [19] PKIConfirmContent,      --Confirmation
    +                   nested   [20] NestedMessageContent,   --Nested Message
    +                   genm     [21] GenMsgContent,          --General Message
    +                   genp     [22] GenRepContent,          --General Response
    +                   error    [23] ErrorMsgContent,        --Error Message
    +                   certConf [24] CertConfirmContent,     --Certificate confirm
    +                   pollReq  [25] PollReqContent,         --Polling request
    +                   pollRep  [26] PollRepContent          --Polling response
    +            }
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +            PkiConfirmContent ::= NULL
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +            PKIFailureInfo ::= BIT STRING {
    +            badAlg               (0),
    +              -- unrecognized or unsupported Algorithm Identifier
    +            badMessageCheck      (1), -- integrity check failed (e.g., signature did not verify)
    +            badRequest           (2),
    +              -- transaction not permitted or supported
    +            badTime              (3), -- messageTime was not sufficiently close to the system time, as defined by local policy
    +            badCertId            (4), -- no certificate could be found matching the provided criteria
    +            badDataFormat        (5),
    +              -- the data submitted has the wrong format
    +            wrongAuthority       (6), -- the authority indicated in the request is different from the one creating the response token
    +            incorrectData        (7), -- the requester's data is incorrect (for notary services)
    +            missingTimeStamp     (8), -- when the timestamp is missing but should be there (by policy)
    +            badPOP               (9)  -- the proof-of-possession failed
    +            certRevoked         (10),
    +            certConfirmed       (11),
    +            wrongIntegrity      (12),
    +            badRecipientNonce   (13), 
    +            timeNotAvailable    (14),
    +              -- the TSA's time source is not available
    +            unacceptedPolicy    (15),
    +              -- the requested TSA policy is not supported by the TSA
    +            unacceptedExtension (16),
    +              -- the requested extension is not supported by the TSA
    +            addInfoNotAvailable (17)
    +              -- the additional information requested could not be understood
    +              -- or is not available
    +            badSenderNonce      (18),
    +            badCertTemplate     (19),
    +            signerNotTrusted    (20),
    +            transactionIdInUse  (21),
    +            unsupportedVersion  (22),
    +            notAuthorized       (23),
    +            systemUnavail       (24),    
    +            systemFailure       (25),
    +              -- the request cannot be handled due to system failure
    +            duplicateCertReq    (26) 
    +            
    +
    + + Basic constructor. + + +
    +            PkiFreeText ::= SEQUENCE SIZE (1..MAX) OF UTF8String
    +            
    +
    + + Return the number of string elements present. + + @return number of elements present. + + + Return the UTF8STRING at index. + + @param index index of the string of interest + @return the string at index. + + + Value for a "null" recipient or sender. + + +
    +             PkiHeader ::= SEQUENCE {
    +                       pvno                INTEGER     { cmp1999(1), cmp2000(2) },
    +                       sender              GeneralName,
    +                       -- identifies the sender
    +                       recipient           GeneralName,
    +                       -- identifies the intended recipient
    +                       messageTime     [0] GeneralizedTime         OPTIONAL,
    +                       -- time of production of this message (used when sender
    +                       -- believes that the transport will be "suitable"; i.e.,
    +                       -- that the time will still be meaningful upon receipt)
    +                       protectionAlg   [1] AlgorithmIdentifier     OPTIONAL,
    +                       -- algorithm used for calculation of protection bits
    +                       senderKID       [2] KeyIdentifier           OPTIONAL,
    +                       recipKID        [3] KeyIdentifier           OPTIONAL,
    +                       -- to identify specific keys used for protection
    +                       transactionID   [4] OCTET STRING            OPTIONAL,
    +                       -- identifies the transaction; i.e., this will be the same in
    +                       -- corresponding request, response, certConf, and PKIConf
    +                       -- messages
    +                       senderNonce     [5] OCTET STRING            OPTIONAL,
    +                       recipNonce      [6] OCTET STRING            OPTIONAL,
    +                       -- nonces used to provide replay protection, senderNonce
    +                       -- is inserted by the creator of this message; recipNonce
    +                       -- is a nonce previously inserted in a related message by
    +                       -- the intended recipient of this message
    +                       freeText        [7] PKIFreeText             OPTIONAL,
    +                       -- this may be used to indicate context-specific instructions
    +                       -- (this field is intended for human consumption)
    +                       generalInfo     [8] SEQUENCE SIZE (1..MAX) OF
    +                                            InfoTypeAndValue     OPTIONAL
    +                       -- this may be used to convey context-specific information
    +                       -- (this field not primarily intended for human consumption)
    +            }
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +             PKIHeader ::= SEQUENCE {
    +                       pvno                INTEGER     { cmp1999(1), cmp2000(2) },
    +                       sender              GeneralName,
    +                       -- identifies the sender
    +                       recipient           GeneralName,
    +                       -- identifies the intended recipient
    +                       messageTime     [0] GeneralizedTime         OPTIONAL,
    +                       -- time of production of this message (used when sender
    +                       -- believes that the transport will be "suitable"; i.e.,
    +                       -- that the time will still be meaningful upon receipt)
    +                       protectionAlg   [1] AlgorithmIdentifier     OPTIONAL,
    +                       -- algorithm used for calculation of protection bits
    +                       senderKID       [2] KeyIdentifier           OPTIONAL,
    +                       recipKID        [3] KeyIdentifier           OPTIONAL,
    +                       -- to identify specific keys used for protection
    +                       transactionID   [4] OCTET STRING            OPTIONAL,
    +                       -- identifies the transaction; i.e., this will be the same in
    +                       -- corresponding request, response, certConf, and PKIConf
    +                       -- messages
    +                       senderNonce     [5] OCTET STRING            OPTIONAL,
    +                       recipNonce      [6] OCTET STRING            OPTIONAL,
    +                       -- nonces used to provide replay protection, senderNonce
    +                       -- is inserted by the creator of this message; recipNonce
    +                       -- is a nonce previously inserted in a related message by
    +                       -- the intended recipient of this message
    +                       freeText        [7] PKIFreeText             OPTIONAL,
    +                       -- this may be used to indicate context-specific instructions
    +                       -- (this field is intended for human consumption)
    +                       generalInfo     [8] SEQUENCE SIZE (1..MAX) OF
    +                                            InfoTypeAndValue     OPTIONAL
    +                       -- this may be used to convey context-specific information
    +                       -- (this field not primarily intended for human consumption)
    +            }
    +            
    + @return a basic ASN.1 object representation. +
    + + Creates a new PkiMessage. + + @param header message header + @param body message body + @param protection message protection (may be null) + @param extraCerts extra certificates (may be null) + + +
    +            PkiMessage ::= SEQUENCE {
    +                             header           PKIHeader,
    +                             body             PKIBody,
    +                             protection   [0] PKIProtection OPTIONAL,
    +                             extraCerts   [1] SEQUENCE SIZE (1..MAX) OF CMPCertificate
    +                                                                                OPTIONAL
    +            }
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +            PkiMessages ::= SEQUENCE SIZE (1..MAX) OF PkiMessage
    +            
    + @return a basic ASN.1 object representation. +
    + + @param status + + + @param status + @param statusString + + +
    +             PkiStatusInfo ::= SEQUENCE {
    +                 status        PKIStatus,                (INTEGER)
    +                 statusString  PkiFreeText     OPTIONAL,
    +                 failInfo      PkiFailureInfo  OPTIONAL  (BIT STRING)
    +             }
    +            
    +             PKIStatus:
    +               granted                (0), -- you got exactly what you asked for
    +               grantedWithMods        (1), -- you got something like what you asked for
    +               rejection              (2), -- you don't get it, more information elsewhere in the message
    +               waiting                (3), -- the request body part has not yet been processed, expect to hear more later
    +               revocationWarning      (4), -- this message contains a warning that a revocation is imminent
    +               revocationNotification (5), -- notification that a revocation has occurred
    +               keyUpdateWarning       (6)  -- update already done for the oldCertId specified in CertReqMsg
    +            
    +             PkiFailureInfo:
    +               badAlg           (0), -- unrecognized or unsupported Algorithm Identifier
    +               badMessageCheck  (1), -- integrity check failed (e.g., signature did not verify)
    +               badRequest       (2), -- transaction not permitted or supported
    +               badTime          (3), -- messageTime was not sufficiently close to the system time, as defined by local policy
    +               badCertId        (4), -- no certificate could be found matching the provided criteria
    +               badDataFormat    (5), -- the data submitted has the wrong format
    +               wrongAuthority   (6), -- the authority indicated in the request is different from the one creating the response token
    +               incorrectData    (7), -- the requester's data is incorrect (for notary services)
    +               missingTimeStamp (8), -- when the timestamp is missing but should be there (by policy)
    +               badPOP           (9)  -- the proof-of-possession failed
    +            
    +             
    +
    + +
    +            PollRepContent ::= SEQUENCE OF SEQUENCE {
    +                    certReqId              INTEGER,
    +                    checkAfter             INTEGER,  -- time in seconds
    +                    reason                 PKIFreeText OPTIONAL
    +                }
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +            PollReqContent ::= SEQUENCE OF SEQUENCE {
    +                                   certReqId              INTEGER
    +            }
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +            PopoDecKeyChallContent ::= SEQUENCE OF Challenge
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +            PopoDecKeyRespContent ::= SEQUENCE OF INTEGER
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +            ProtectedPart ::= SEQUENCE {
    +                               header    PKIHeader,
    +                               body      PKIBody
    +            }
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +            RevAnnContent ::= SEQUENCE {
    +                  status              PKIStatus,
    +                  certId              CertId,
    +                  willBeRevokedAt     GeneralizedTime,
    +                  badSinceDate        GeneralizedTime,
    +                  crlDetails          Extensions  OPTIONAL
    +                   -- extra CRL details (e.g., crl number, reason, location, etc.)
    +            }
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +            RevDetails ::= SEQUENCE {
    +                             certDetails         CertTemplate,
    +                              -- allows requester to specify as much as they can about
    +                              -- the cert. for which revocation is requested
    +                              -- (e.g., for cases in which serialNumber is not available)
    +                              crlEntryDetails     Extensions       OPTIONAL
    +                              -- requested crlEntryExtensions
    +                        }
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +            RevRepContent ::= SEQUENCE {
    +                   status       SEQUENCE SIZE (1..MAX) OF PKIStatusInfo,
    +                   -- in same order as was sent in RevReqContent
    +                   revCerts [0] SEQUENCE SIZE (1..MAX) OF CertId OPTIONAL,
    +                   -- IDs for which revocation was requested
    +                   -- (same order as status)
    +                   crls     [1] SEQUENCE SIZE (1..MAX) OF CertificateList OPTIONAL
    +                   -- the resulting CRLs (there may be more than one)
    +              }
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +            RevReqContent ::= SEQUENCE OF RevDetails
    +            
    + @return a basic ASN.1 object representation. +
    + + return an Attribute object from the given object. + + @param o the object we want converted. + @exception ArgumentException if the object cannot be converted. + + + Produce an object suitable for an Asn1OutputStream. +
    +            Attribute ::= SEQUENCE {
    +                attrType OBJECT IDENTIFIER,
    +                attrValues SET OF AttributeValue
    +            }
    +            
    +
    + +
    +            Attributes ::=
    +              SET SIZE(1..MAX) OF Attribute -- according to RFC 5652
    +            
    + @return +
    + + Return all the attributes matching the OBJECT IDENTIFIER oid. The vector will be + empty if there are no attributes of the required type present. + + @param oid type of attribute required. + @return a vector of all the attributes found of type oid. + + + Return a new table with the passed in attribute added. + + @param attrType + @param attrValue + @return + + + Return the first attribute matching the given OBJECT IDENTIFIER + + + return an AuthenticatedData object from a tagged object. + + @param obj the tagged object holding the object we want. + @param isExplicit true if the object is meant to be explicitly + tagged false otherwise. + @throws ArgumentException if the object held by the + tagged object cannot be converted. + + + return an AuthenticatedData object from the given object. + + @param obj the object we want converted. + @throws ArgumentException if the object cannot be converted. + + + Produce an object suitable for an Asn1OutputStream. +
    +             AuthenticatedData ::= SEQUENCE {
    +                   version CMSVersion,
    +                   originatorInfo [0] IMPLICIT OriginatorInfo OPTIONAL,
    +                   recipientInfos RecipientInfos,
    +                   macAlgorithm MessageAuthenticationCodeAlgorithm,
    +                   digestAlgorithm [1] DigestAlgorithmIdentifier OPTIONAL,
    +                   encapContentInfo EncapsulatedContentInfo,
    +                   authAttrs [2] IMPLICIT AuthAttributes OPTIONAL,
    +                   mac MessageAuthenticationCode,
    +                   unauthAttrs [3] IMPLICIT UnauthAttributes OPTIONAL }
    +            
    +             AuthAttributes ::= SET SIZE (1..MAX) OF Attribute
    +            
    +             UnauthAttributes ::= SET SIZE (1..MAX) OF Attribute
    +            
    +             MessageAuthenticationCode ::= OCTET STRING
    +             
    +
    + + Produce an object suitable for an Asn1OutputStream. +
    +             AuthenticatedData ::= SEQUENCE {
    +                   version CMSVersion,
    +                   originatorInfo [0] IMPLICIT OriginatorInfo OPTIONAL,
    +                   recipientInfos RecipientInfos,
    +                   macAlgorithm MessageAuthenticationCodeAlgorithm,
    +                   digestAlgorithm [1] DigestAlgorithmIdentifier OPTIONAL,
    +                   encapContentInfo EncapsulatedContentInfo,
    +                   authAttrs [2] IMPLICIT AuthAttributes OPTIONAL,
    +                   mac MessageAuthenticationCode,
    +                   unauthAttrs [3] IMPLICIT UnauthAttributes OPTIONAL }
    +            
    +             AuthAttributes ::= SET SIZE (1..MAX) OF Attribute
    +            
    +             UnauthAttributes ::= SET SIZE (1..MAX) OF Attribute
    +            
    +             MessageAuthenticationCode ::= OCTET STRING
    +             
    +
    + + return an AuthEnvelopedData object from a tagged object. + + @param obj the tagged object holding the object we want. + @param isExplicit true if the object is meant to be explicitly + tagged false otherwise. + @throws ArgumentException if the object held by the + tagged object cannot be converted. + + + return an AuthEnvelopedData object from the given object. + + @param obj the object we want converted. + @throws ArgumentException if the object cannot be converted. + + + Produce an object suitable for an Asn1OutputStream. +
    +            AuthEnvelopedData ::= SEQUENCE {
    +              version CMSVersion,
    +              originatorInfo [0] IMPLICIT OriginatorInfo OPTIONAL,
    +              recipientInfos RecipientInfos,
    +              authEncryptedContentInfo EncryptedContentInfo,
    +              authAttrs [1] IMPLICIT AuthAttributes OPTIONAL,
    +              mac MessageAuthenticationCode,
    +              unauthAttrs [2] IMPLICIT UnauthAttributes OPTIONAL }
    +            
    +
    + + Produce an object suitable for an Asn1OutputStream. + +
    +            AuthEnvelopedData ::= SEQUENCE {
    +              version CMSVersion,
    +              originatorInfo [0] IMPLICIT OriginatorInfo OPTIONAL,
    +              recipientInfos RecipientInfos,
    +              authEncryptedContentInfo EncryptedContentInfo,
    +              authAttrs [1] IMPLICIT AuthAttributes OPTIONAL,
    +              mac MessageAuthenticationCode,
    +              unauthAttrs [2] IMPLICIT UnauthAttributes OPTIONAL }
    +            
    +
    + + The other Revocation Info arc + id-ri OBJECT IDENTIFIER ::= { iso(1) identified-organization(3) + dod(6) internet(1) security(5) mechanisms(5) pkix(7) ri(16) } + + + RFC 3274 - CMS Compressed Data. +
    +            CompressedData ::= Sequence {
    +             version CMSVersion,
    +             compressionAlgorithm CompressionAlgorithmIdentifier,
    +             encapContentInfo EncapsulatedContentInfo
    +            }
    +            
    +
    + + return a CompressedData object from a tagged object. + + @param ato the tagged object holding the object we want. + @param explicitly true if the object is meant to be explicitly + tagged false otherwise. + @exception ArgumentException if the object held by the + tagged object cannot be converted. + + + return a CompressedData object from the given object. + + @param _obj the object we want converted. + @exception ArgumentException if the object cannot be converted. + + + RFC 3274 - CMS Compressed Data. +
    +            CompressedData ::= SEQUENCE {
    +             version CMSVersion,
    +             compressionAlgorithm CompressionAlgorithmIdentifier,
    +             encapContentInfo EncapsulatedContentInfo
    +            }
    +            
    +
    + + Produce an object suitable for an Asn1OutputStream. +
    +            ContentInfo ::= Sequence {
    +                     contentType ContentType,
    +                     content
    +                     [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL }
    +            
    +
    + + Produce an object suitable for an Asn1OutputStream. +
    +            ContentInfo ::= SEQUENCE {
    +                     contentType ContentType,
    +                     content
    +                     [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL }
    +            
    +
    + + return an AuthEnvelopedData object from a tagged object. + + @param obj the tagged object holding the object we want. + @param isExplicit true if the object is meant to be explicitly + tagged false otherwise. + @throws ArgumentException if the object held by the + tagged object cannot be converted. + + + return an AuthEnvelopedData object from the given object. + + @param obj the object we want converted. + @throws ArgumentException if the object cannot be converted. + + + Produce an object suitable for an Asn1OutputStream. +
    +            MQVuserKeyingMaterial ::= SEQUENCE {
    +              ephemeralPublicKey OriginatorPublicKey,
    +              addedukm [0] EXPLICIT UserKeyingMaterial OPTIONAL  }
    +            
    +
    + + return an EncryptedContentInfo object from the given object. + + @param obj the object we want converted. + @exception ArgumentException if the object cannot be converted. + + + Produce an object suitable for an Asn1OutputStream. +
    +            EncryptedContentInfo ::= Sequence {
    +                contentType ContentType,
    +                contentEncryptionAlgorithm ContentEncryptionAlgorithmIdentifier,
    +                encryptedContent [0] IMPLICIT EncryptedContent OPTIONAL
    +            }
    +            
    +
    + +
    +            EncryptedContentInfo ::= SEQUENCE {
    +                contentType ContentType,
    +                contentEncryptionAlgorithm ContentEncryptionAlgorithmIdentifier,
    +                encryptedContent [0] IMPLICIT EncryptedContent OPTIONAL
    +            }
    +            
    +
    + +
    +                  EncryptedData ::= SEQUENCE {
    +                                version CMSVersion,
    +                                encryptedContentInfo EncryptedContentInfo,
    +                                unprotectedAttrs [1] IMPLICIT UnprotectedAttributes OPTIONAL }
    +            
    + @return a basic ASN.1 object representation. +
    + + return an EnvelopedData object from a tagged object. + + @param obj the tagged object holding the object we want. + @param explicitly true if the object is meant to be explicitly + tagged false otherwise. + @exception ArgumentException if the object held by the + tagged object cannot be converted. + + + return an EnvelopedData object from the given object. + + @param obj the object we want converted. + @exception ArgumentException if the object cannot be converted. + + + Produce an object suitable for an Asn1OutputStream. +
    +            EnvelopedData ::= Sequence {
    +                version CMSVersion,
    +                originatorInfo [0] IMPLICIT OriginatorInfo OPTIONAL,
    +                recipientInfos RecipientInfos,
    +                encryptedContentInfo EncryptedContentInfo,
    +                unprotectedAttrs [1] IMPLICIT UnprotectedAttributes OPTIONAL
    +            }
    +            
    +
    + + Produce an object suitable for an Asn1OutputStream. +
    +            EnvelopedData ::= SEQUENCE {
    +                version CMSVersion,
    +                originatorInfo [0] IMPLICIT OriginatorInfo OPTIONAL,
    +                recipientInfos RecipientInfos,
    +                encryptedContentInfo EncryptedContentInfo,
    +                unprotectedAttrs [1] IMPLICIT UnprotectedAttributes OPTIONAL
    +            }
    +            
    +
    + + return a KekIdentifier object from a tagged object. + + @param obj the tagged object holding the object we want. + @param explicitly true if the object is meant to be explicitly + tagged false otherwise. + @exception ArgumentException if the object held by the + tagged object cannot be converted. + + + return a KekIdentifier object from the given object. + + @param obj the object we want converted. + @exception ArgumentException if the object cannot be converted. + + + Produce an object suitable for an Asn1OutputStream. +
    +            KekIdentifier ::= Sequence {
    +                keyIdentifier OCTET STRING,
    +                date GeneralizedTime OPTIONAL,
    +                other OtherKeyAttribute OPTIONAL
    +            }
    +            
    +
    + + return a KekRecipientInfo object from a tagged object. + + @param obj the tagged object holding the object we want. + @param explicitly true if the object is meant to be explicitly + tagged false otherwise. + @exception ArgumentException if the object held by the + tagged object cannot be converted. + + + return a KekRecipientInfo object from the given object. + + @param obj the object we want converted. + @exception ArgumentException if the object cannot be converted. + + + Produce an object suitable for an Asn1OutputStream. +
    +            KekRecipientInfo ::= Sequence {
    +                version CMSVersion,  -- always set to 4
    +                kekID KekIdentifier,
    +                keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier,
    +                encryptedKey EncryptedKey
    +            }
    +            
    +
    + + return an KeyAgreeRecipientIdentifier object from a tagged object. + + @param obj the tagged object holding the object we want. + @param isExplicit true if the object is meant to be explicitly + tagged false otherwise. + @exception ArgumentException if the object held by the + tagged object cannot be converted. + + + return an KeyAgreeRecipientIdentifier object from the given object. + + @param obj the object we want converted. + @exception ArgumentException if the object cannot be converted. + + + Produce an object suitable for an Asn1OutputStream. +
    +            KeyAgreeRecipientIdentifier ::= CHOICE {
    +                issuerAndSerialNumber IssuerAndSerialNumber,
    +                rKeyId [0] IMPLICIT RecipientKeyIdentifier
    +            }
    +            
    +
    + + return a KeyAgreeRecipientInfo object from a tagged object. + + @param obj the tagged object holding the object we want. + @param explicitly true if the object is meant to be explicitly + tagged false otherwise. + @exception ArgumentException if the object held by the + tagged object cannot be converted. + + + return a KeyAgreeRecipientInfo object from the given object. + + @param obj the object we want converted. + @exception ArgumentException if the object cannot be converted. + + + * Produce an object suitable for an Asn1OutputStream. + *
    +                     * KeyAgreeRecipientInfo ::= Sequence {
    +                     *     version CMSVersion,  -- always set to 3
    +                     *     originator [0] EXPLICIT OriginatorIdentifierOrKey,
    +                     *     ukm [1] EXPLICIT UserKeyingMaterial OPTIONAL,
    +                     *     keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier,
    +                     *     recipientEncryptedKeys RecipientEncryptedKeys
    +                     * }
    +            		 *
    +            		 * UserKeyingMaterial ::= OCTET STRING
    +                     * 
    +
    + + return a KeyTransRecipientInfo object from the given object. + + @param obj the object we want converted. + @exception ArgumentException if the object cannot be converted. + + + Produce an object suitable for an Asn1OutputStream. +
    +            KeyTransRecipientInfo ::= Sequence {
    +                version CMSVersion,  -- always set to 0 or 2
    +                rid RecipientIdentifier,
    +                keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier,
    +                encryptedKey EncryptedKey
    +            }
    +            
    +
    + +
    +            MetaData ::= SEQUENCE {
    +              hashProtected        BOOLEAN,
    +              fileName             UTF8String OPTIONAL,
    +              mediaType            IA5String OPTIONAL,
    +              otherMetaData        Attributes OPTIONAL
    +            }
    +            
    + @return +
    + + return an OriginatorIdentifierOrKey object from a tagged object. + + @param o the tagged object holding the object we want. + @param explicitly true if the object is meant to be explicitly + tagged false otherwise. + @exception ArgumentException if the object held by the + tagged object cannot be converted. + + + return an OriginatorIdentifierOrKey object from the given object. + + @param o the object we want converted. + @exception ArgumentException if the object cannot be converted. + + + Produce an object suitable for an Asn1OutputStream. +
    +             OriginatorIdentifierOrKey ::= CHOICE {
    +                 issuerAndSerialNumber IssuerAndSerialNumber,
    +                 subjectKeyIdentifier [0] SubjectKeyIdentifier,
    +                 originatorKey [1] OriginatorPublicKey
    +             }
    +            
    +             SubjectKeyIdentifier ::= OCTET STRING
    +             
    +
    + + return an OriginatorInfo object from a tagged object. + + @param obj the tagged object holding the object we want. + @param explicitly true if the object is meant to be explicitly + tagged false otherwise. + @exception ArgumentException if the object held by the + tagged object cannot be converted. + + + return an OriginatorInfo object from the given object. + + @param obj the object we want converted. + @exception ArgumentException if the object cannot be converted. + + + Produce an object suitable for an Asn1OutputStream. +
    +            OriginatorInfo ::= Sequence {
    +                certs [0] IMPLICIT CertificateSet OPTIONAL,
    +                crls [1] IMPLICIT CertificateRevocationLists OPTIONAL
    +            }
    +            
    +
    + + return an OriginatorPublicKey object from a tagged object. + + @param obj the tagged object holding the object we want. + @param explicitly true if the object is meant to be explicitly + tagged false otherwise. + @exception ArgumentException if the object held by the + tagged object cannot be converted. + + + return an OriginatorPublicKey object from the given object. + + @param obj the object we want converted. + @exception ArgumentException if the object cannot be converted. + + + Produce an object suitable for an Asn1OutputStream. +
    +            OriginatorPublicKey ::= Sequence {
    +                algorithm AlgorithmIdentifier,
    +                publicKey BIT STRING
    +            }
    +            
    +
    + + return an OtherKeyAttribute object from the given object. + + @param o the object we want converted. + @exception ArgumentException if the object cannot be converted. + + + Produce an object suitable for an Asn1OutputStream. +
    +            OtherKeyAttribute ::= Sequence {
    +                keyAttrId OBJECT IDENTIFIER,
    +                keyAttr ANY DEFINED BY keyAttrId OPTIONAL
    +            }
    +            
    +
    + + return a OtherRecipientInfo object from a tagged object. + + @param obj the tagged object holding the object we want. + @param explicitly true if the object is meant to be explicitly + tagged false otherwise. + @exception ArgumentException if the object held by the + tagged object cannot be converted. + + + return a OtherRecipientInfo object from the given object. + + @param obj the object we want converted. + @exception ArgumentException if the object cannot be converted. + + + Produce an object suitable for an Asn1OutputStream. +
    +            OtherRecipientInfo ::= Sequence {
    +               oriType OBJECT IDENTIFIER,
    +               oriValue ANY DEFINED BY oriType }
    +            
    +
    + + return a OtherRevocationInfoFormat object from a tagged object. + + @param obj the tagged object holding the object we want. + @param explicit true if the object is meant to be explicitly + tagged false otherwise. + @exception IllegalArgumentException if the object held by the + tagged object cannot be converted. + + + return a OtherRevocationInfoFormat object from the given object. + + @param obj the object we want converted. + @exception IllegalArgumentException if the object cannot be converted. + + + Produce an object suitable for an ASN1OutputStream. +
    +            OtherRevocationInfoFormat ::= SEQUENCE {
    +                 otherRevInfoFormat OBJECT IDENTIFIER,
    +                 otherRevInfo ANY DEFINED BY otherRevInfoFormat }
    +            
    +
    + + return a PasswordRecipientInfo object from a tagged object. + + @param obj the tagged object holding the object we want. + @param explicitly true if the object is meant to be explicitly + tagged false otherwise. + @exception ArgumentException if the object held by the + tagged object cannot be converted. + + + return a PasswordRecipientInfo object from the given object. + + @param obj the object we want converted. + @exception ArgumentException if the object cannot be converted. + + + Produce an object suitable for an Asn1OutputStream. +
    +            PasswordRecipientInfo ::= Sequence {
    +              version CMSVersion,   -- Always set to 0
    +              keyDerivationAlgorithm [0] KeyDerivationAlgorithmIdentifier
    +                                        OPTIONAL,
    +             keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier,
    +             encryptedKey EncryptedKey }
    +            
    +
    + + return an RecipientEncryptedKey object from a tagged object. + + @param obj the tagged object holding the object we want. + @param isExplicit true if the object is meant to be explicitly + tagged false otherwise. + @exception ArgumentException if the object held by the + tagged object cannot be converted. + + + return a RecipientEncryptedKey object from the given object. + + @param obj the object we want converted. + @exception ArgumentException if the object cannot be converted. + + + Produce an object suitable for an Asn1OutputStream. +
    +            RecipientEncryptedKey ::= SEQUENCE {
    +                rid KeyAgreeRecipientIdentifier,
    +                encryptedKey EncryptedKey
    +            }
    +            
    +
    + + return a RecipientIdentifier object from the given object. + + @param o the object we want converted. + @exception ArgumentException if the object cannot be converted. + + + Produce an object suitable for an Asn1OutputStream. +
    +             RecipientIdentifier ::= CHOICE {
    +                 issuerAndSerialNumber IssuerAndSerialNumber,
    +                 subjectKeyIdentifier [0] SubjectKeyIdentifier
    +             }
    +            
    +             SubjectKeyIdentifier ::= OCTET STRING
    +             
    +
    + + Produce an object suitable for an Asn1OutputStream. +
    +            RecipientInfo ::= CHOICE {
    +                ktri KeyTransRecipientInfo,
    +                kari [1] KeyAgreeRecipientInfo,
    +                kekri [2] KekRecipientInfo,
    +                pwri [3] PasswordRecipientInfo,
    +                ori [4] OtherRecipientInfo }
    +            
    +
    + + return a RecipientKeyIdentifier object from a tagged object. + + @param _ato the tagged object holding the object we want. + @param _explicit true if the object is meant to be explicitly + tagged false otherwise. + @exception ArgumentException if the object held by the + tagged object cannot be converted. + + + return a RecipientKeyIdentifier object from the given object. + + @param _obj the object we want converted. + @exception ArgumentException if the object cannot be converted. + + + Produce an object suitable for an Asn1OutputStream. +
    +             RecipientKeyIdentifier ::= Sequence {
    +                 subjectKeyIdentifier SubjectKeyIdentifier,
    +                 date GeneralizedTime OPTIONAL,
    +                 other OtherKeyAttribute OPTIONAL
    +             }
    +            
    +             SubjectKeyIdentifier ::= OCTET STRING
    +             
    +
    + +
    +               ScvpReqRes ::= SEQUENCE {
    +               request  [0] EXPLICIT ContentInfo OPTIONAL,
    +               response     ContentInfo }
    +            
    + @return the ASN.1 primitive representation. +
    + + a signed data object. + + + Produce an object suitable for an Asn1OutputStream. +
    +            SignedData ::= Sequence {
    +                version CMSVersion,
    +                digestAlgorithms DigestAlgorithmIdentifiers,
    +                encapContentInfo EncapsulatedContentInfo,
    +                certificates [0] IMPLICIT CertificateSet OPTIONAL,
    +                crls [1] IMPLICIT CertificateRevocationLists OPTIONAL,
    +                signerInfos SignerInfos
    +              }
    +            
    +
    + +
    +            SignedData ::= SEQUENCE {
    +                version CMSVersion,
    +                digestAlgorithms DigestAlgorithmIdentifiers,
    +                encapContentInfo EncapsulatedContentInfo,
    +                certificates [0] IMPLICIT CertificateSet OPTIONAL,
    +                crls [1] IMPLICIT CertificateRevocationLists OPTIONAL,
    +                signerInfos SignerInfos
    +              }
    +            
    +
    + + return a SignerIdentifier object from the given object. + + @param o the object we want converted. + @exception ArgumentException if the object cannot be converted. + + + Produce an object suitable for an Asn1OutputStream. +
    +             SignerIdentifier ::= CHOICE {
    +                 issuerAndSerialNumber IssuerAndSerialNumber,
    +                 subjectKeyIdentifier [0] SubjectKeyIdentifier
    +             }
    +            
    +             SubjectKeyIdentifier ::= OCTET STRING
    +             
    +
    + + Produce an object suitable for an Asn1OutputStream. +
    +              SignerInfo ::= Sequence {
    +                  version Version,
    +                  SignerIdentifier sid,
    +                  digestAlgorithm DigestAlgorithmIdentifier,
    +                  authenticatedAttributes [0] IMPLICIT Attributes OPTIONAL,
    +                  digestEncryptionAlgorithm DigestEncryptionAlgorithmIdentifier,
    +                  encryptedDigest EncryptedDigest,
    +                  unauthenticatedAttributes [1] IMPLICIT Attributes OPTIONAL
    +              }
    +            
    +              EncryptedDigest ::= OCTET STRING
    +            
    +              DigestAlgorithmIdentifier ::= AlgorithmIdentifier
    +            
    +              DigestEncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
    +             
    +
    + + creates a time object from a given date - if the date is between 1950 + and 2049 a UTCTime object is Generated, otherwise a GeneralizedTime + is used. + + + Produce an object suitable for an Asn1OutputStream. +
    +            Time ::= CHOICE {
    +                        utcTime        UTCTime,
    +                        generalTime    GeneralizedTime }
    +            
    +
    + +
    +            TimeStampAndCRL ::= SEQUENCE {
    +                timeStamp   TimeStampToken,          -- according to RFC 3161
    +                crl         CertificateList OPTIONAL -- according to RFC 5280
    +             }
    +            
    + @return +
    + +
    +            TimeStampedData ::= SEQUENCE {
    +              version              INTEGER { v1(1) },
    +              dataUri              IA5String OPTIONAL,
    +              metaData             MetaData OPTIONAL,
    +              content              OCTET STRING OPTIONAL,
    +              temporalEvidence     Evidence
    +            }
    +            
    + @return +
    + +
    +            TimeStampTokenEvidence ::=
    +               SEQUENCE SIZE(1..MAX) OF TimeStampAndCrl
    +            
    + @return +
    + +
    +            AttributeTypeAndValue ::= SEQUENCE {
    +                      type         OBJECT IDENTIFIER,
    +                      value        ANY DEFINED BY type }
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +            CertId ::= SEQUENCE {
    +                            issuer           GeneralName,
    +                            serialNumber     INTEGER }
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +            CertReqMessages ::= SEQUENCE SIZE (1..MAX) OF CertReqMsg
    +            
    + @return a basic ASN.1 object representation. +
    + + Creates a new CertReqMsg. + @param certReq CertRequest + @param popo may be null + @param regInfo may be null + + +
    +            CertReqMsg ::= SEQUENCE {
    +                               certReq   CertRequest,
    +                               pop       ProofOfPossession  OPTIONAL,
    +                               -- content depends upon key type
    +                               regInfo   SEQUENCE SIZE(1..MAX) OF AttributeTypeAndValue OPTIONAL }
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +            CertRequest ::= SEQUENCE {
    +                                 certReqId     INTEGER,          -- ID for matching request and reply
    +                                 certTemplate  CertTemplate,  -- Selected fields of cert to be issued
    +                                 controls      Controls OPTIONAL }   -- Attributes affecting issuance
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +             CertTemplate ::= SEQUENCE {
    +                 version      [0] Version               OPTIONAL,
    +                 serialNumber [1] INTEGER               OPTIONAL,
    +                 signingAlg   [2] AlgorithmIdentifier   OPTIONAL,
    +                 issuer       [3] Name                  OPTIONAL,
    +                 validity     [4] OptionalValidity      OPTIONAL,
    +                 subject      [5] Name                  OPTIONAL,
    +                 publicKey    [6] SubjectPublicKeyInfo  OPTIONAL,
    +                 issuerUID    [7] UniqueIdentifier      OPTIONAL,
    +                 subjectUID   [8] UniqueIdentifier      OPTIONAL,
    +                 extensions   [9] Extensions            OPTIONAL }
    +            
    + @return a basic ASN.1 object representation. +
    + + Sets the X.509 version. Note: for X509v3, use 2 here. + + + Sets the issuer unique ID (deprecated in X.509v3) + + + Sets the subject unique ID (deprecated in X.509v3) + + +
    +             CertTemplate ::= SEQUENCE {
    +                 version      [0] Version               OPTIONAL,
    +                 serialNumber [1] INTEGER               OPTIONAL,
    +                 signingAlg   [2] AlgorithmIdentifier   OPTIONAL,
    +                 issuer       [3] Name                  OPTIONAL,
    +                 validity     [4] OptionalValidity      OPTIONAL,
    +                 subject      [5] Name                  OPTIONAL,
    +                 publicKey    [6] SubjectPublicKeyInfo  OPTIONAL,
    +                 issuerUID    [7] UniqueIdentifier      OPTIONAL,
    +                 subjectUID   [8] UniqueIdentifier      OPTIONAL,
    +                 extensions   [9] Extensions            OPTIONAL }
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +            Controls  ::= SEQUENCE SIZE(1..MAX) OF AttributeTypeAndValue
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +            EncKeyWithID ::= SEQUENCE {
    +                 privateKey           PrivateKeyInfo,
    +                 identifier CHOICE {
    +                    string               UTF8String,
    +                    generalName          GeneralName
    +                } OPTIONAL
    +            }
    +            
    + @return +
    + +
    +               EncryptedKey ::= CHOICE {
    +                   encryptedValue        EncryptedValue, -- deprecated
    +                   envelopedData     [0] EnvelopedData }
    +                   -- The encrypted private key MUST be placed in the envelopedData
    +                   -- encryptedContentInfo encryptedContent OCTET STRING.
    +            
    +
    + +
    +            EncryptedValue ::= SEQUENCE {
    +                                intendedAlg   [0] AlgorithmIdentifier  OPTIONAL,
    +                                -- the intended algorithm for which the value will be used
    +                                symmAlg       [1] AlgorithmIdentifier  OPTIONAL,
    +                                -- the symmetric algorithm used to encrypt the value
    +                                encSymmKey    [2] BIT STRING           OPTIONAL,
    +                                -- the (encrypted) symmetric key used to encrypt the value
    +                                keyAlg        [3] AlgorithmIdentifier  OPTIONAL,
    +                                -- algorithm used to encrypt the symmetric key
    +                                valueHint     [4] OCTET STRING         OPTIONAL,
    +                                -- a brief description or identifier of the encValue content
    +                                -- (may be meaningful only to the sending entity, and used only
    +                                -- if EncryptedValue might be re-examined by the sending entity
    +                                -- in the future)
    +                                encValue       BIT STRING }
    +                                -- the encrypted value itself
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +            OptionalValidity ::= SEQUENCE {
    +                                   notBefore  [0] Time OPTIONAL,
    +                                   notAfter   [1] Time OPTIONAL } --at least one MUST be present
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +             PkiArchiveOptions ::= CHOICE {
    +                 encryptedPrivKey     [0] EncryptedKey,
    +                 -- the actual value of the private key
    +                 keyGenParameters     [1] KeyGenParameters,
    +                 -- parameters which allow the private key to be re-generated
    +                 archiveRemGenPrivKey [2] BOOLEAN }
    +                 -- set to TRUE if sender wishes receiver to archive the private
    +                 -- key of a key pair that the receiver generates in response to
    +                 -- this request; set to FALSE if no archival is desired.
    +            
    +
    + +
    +            PkiPublicationInfo ::= SEQUENCE {
    +                             action     INTEGER {
    +                                            dontPublish (0),
    +                                            pleasePublish (1) },
    +                             pubInfos  SEQUENCE SIZE (1..MAX) OF SinglePubInfo OPTIONAL }
    +            -- pubInfos MUST NOT be present if action is "dontPublish"
    +            -- (if action is "pleasePublish" and pubInfos is omitted,
    +            -- "dontCare" is assumed)
    +            
    + @return a basic ASN.1 object representation. +
    + + Password-based MAC value for use with POPOSigningKeyInput. + + + Creates a new PKMACValue. + @param params parameters for password-based MAC + @param value MAC of the DER-encoded SubjectPublicKeyInfo + + + Creates a new PKMACValue. + @param aid CMPObjectIdentifiers.passwordBasedMAC, with PBMParameter + @param value MAC of the DER-encoded SubjectPublicKeyInfo + + +
    +            PKMACValue ::= SEQUENCE {
    +                 algId  AlgorithmIdentifier,
    +                 -- algorithm value shall be PasswordBasedMac 1.2.840.113533.7.66.13
    +                 -- parameter value is PBMParameter
    +                 value  BIT STRING }
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +            PopoPrivKey ::= CHOICE {
    +                   thisMessage       [0] BIT STRING,         -- Deprecated
    +                    -- possession is proven in this message (which contains the private
    +                    -- key itself (encrypted for the CA))
    +                   subsequentMessage [1] SubsequentMessage,
    +                    -- possession will be proven in a subsequent message
    +                   dhMAC             [2] BIT STRING,         -- Deprecated
    +                   agreeMAC          [3] PKMACValue,
    +                   encryptedKey      [4] EnvelopedData }
    +            
    +
    + + Creates a new Proof of Possession object for a signing key. + @param poposkIn the PopoSigningKeyInput structure, or null if the + CertTemplate includes both subject and publicKey values. + @param aid the AlgorithmIdentifier used to sign the proof of possession. + @param signature a signature over the DER-encoded value of poposkIn, + or the DER-encoded value of certReq if poposkIn is null. + + +
    +            PopoSigningKey ::= SEQUENCE {
    +                                 poposkInput           [0] PopoSigningKeyInput OPTIONAL,
    +                                 algorithmIdentifier   AlgorithmIdentifier,
    +                                 signature             BIT STRING }
    +             -- The signature (using "algorithmIdentifier") is on the
    +             -- DER-encoded value of poposkInput.  NOTE: If the CertReqMsg
    +             -- certReq CertTemplate contains the subject and publicKey values,
    +             -- then poposkInput MUST be omitted and the signature MUST be
    +             -- computed on the DER-encoded value of CertReqMsg certReq.  If
    +             -- the CertReqMsg certReq CertTemplate does not contain the public
    +             -- key and subject values, then poposkInput MUST be present and
    +             -- MUST be signed.  This strategy ensures that the public key is
    +             -- not present in both the poposkInput and CertReqMsg certReq
    +             -- CertTemplate fields.
    +            
    + @return a basic ASN.1 object representation. +
    + + Creates a new PopoSigningKeyInput with sender name as authInfo. + + + Creates a new PopoSigningKeyInput using password-based MAC. + + +
    +            PopoSigningKeyInput ::= SEQUENCE {
    +                   authInfo             CHOICE {
    +                                            sender              [0] GeneralName,
    +                                            -- used only if an authenticated identity has been
    +                                            -- established for the sender (e.g., a DN from a
    +                                            -- previously-issued and currently-valid certificate
    +                                            publicKeyMac        PKMacValue },
    +                                            -- used if no authenticated GeneralName currently exists for
    +                                            -- the sender; publicKeyMac contains a password-based MAC
    +                                            -- on the DER-encoded value of publicKey
    +                   publicKey           SubjectPublicKeyInfo }  -- from CertTemplate
    +            
    + @return a basic ASN.1 object representation. +
    + + Returns the sender field, or null if authInfo is publicKeyMac + + + Returns the publicKeyMac field, or null if authInfo is sender + + + Creates a ProofOfPossession with type raVerified. + + + Creates a ProofOfPossession for a signing key. + + + Creates a ProofOfPossession for key encipherment or agreement. + @param type one of TYPE_KEY_ENCIPHERMENT or TYPE_KEY_AGREEMENT + + +
    +            ProofOfPossession ::= CHOICE {
    +                                      raVerified        [0] NULL,
    +                                      -- used if the RA has already verified that the requester is in
    +                                      -- possession of the private key
    +                                      signature         [1] PopoSigningKey,
    +                                      keyEncipherment   [2] PopoPrivKey,
    +                                      keyAgreement      [3] PopoPrivKey }
    +            
    + @return a basic ASN.1 object representation. +
    + +
    +            SinglePubInfo ::= SEQUENCE {
    +                   pubMethod    INTEGER {
    +                      dontCare    (0),
    +                      x500        (1),
    +                      web         (2),
    +                      ldap        (3) },
    +                  pubLocation  GeneralName OPTIONAL }
    +            
    + @return a basic ASN.1 object representation. +
    + + return an integer from the passed in object + + @exception ArgumentException if the object cannot be converted. + + + return an Integer from a tagged object. + + @param obj the tagged object holding the object we want + @param isExplicit true if the object is meant to be explicitly + tagged false otherwise. + @exception ArgumentException if the tagged object cannot + be converted. + + + in some cases positive values Get crammed into a space, + that's not quite big enough... + + + table of the available named parameters for GOST 3410-2001. + + + return the ECDomainParameters object for the given OID, null if it + isn't present. + + @param oid an object identifier representing a named parameters, if present. + + + return the named curve name represented by the given object identifier. + + + returns an enumeration containing the name strings for curves + contained in this structure. + + +
    +             Gost28147-89-Parameters ::=
    +                           SEQUENCE {
    +                                   iv                   Gost28147-89-IV,
    +                                   encryptionParamSet   OBJECT IDENTIFIER
    +                            }
    +            
    +               Gost28147-89-IV ::= OCTET STRING (SIZE (8))
    +             
    +
    + + table of the available named parameters for GOST 3410-94. + + + return the GOST3410ParamSetParameters object for the given OID, null if it + isn't present. + + @param oid an object identifier representing a named parameters, if present. + + + returns an enumeration containing the name strings for parameters + contained in this structure. + + + Der BMPString object. + + + return a BMP string from the given object. + + @param obj the object we want converted. + @exception ArgumentException if the object cannot be converted. + + + return a BMP string from a tagged object. + + @param obj the tagged object holding the object we want + @param explicitly true if the object is meant to be explicitly + tagged false otherwise. + @exception ArgumentException if the tagged object cannot + be converted. + + + basic constructor - byte encoded string. + + + basic constructor + + + return a bool from the passed in object. + + @exception ArgumentException if the object cannot be converted. + + + return a DerBoolean from the passed in bool. + + + return a Boolean from a tagged object. + + @param obj the tagged object holding the object we want + @param explicitly true if the object is meant to be explicitly + tagged false otherwise. + @exception ArgumentException if the tagged object cannot + be converted. + + + return an integer from the passed in object + + @exception ArgumentException if the object cannot be converted. + + + return an Enumerated from a tagged object. + + @param obj the tagged object holding the object we want + @param explicitly true if the object is meant to be explicitly + tagged false otherwise. + @exception ArgumentException if the tagged object cannot + be converted. + + + Class representing the DER-type External + + + Creates a new instance of DerExternal + See X.690 for more informations about the meaning of these parameters + @param directReference The direct reference or null if not set. + @param indirectReference The indirect reference or null if not set. + @param dataValueDescriptor The data value descriptor or null if not set. + @param externalData The external data in its encoded form. + + + Creates a new instance of DerExternal. + See X.690 for more informations about the meaning of these parameters + @param directReference The direct reference or null if not set. + @param indirectReference The indirect reference or null if not set. + @param dataValueDescriptor The data value descriptor or null if not set. + @param encoding The encoding to be used for the external data + @param externalData The external data + + + The encoding of the content. Valid values are +
      +
    • 0 single-ASN1-type
    • +
    • 1 OCTET STRING
    • +
    • 2 BIT STRING
    • +
    +
    + + Generalized time object. + + + return a generalized time from the passed in object + + @exception ArgumentException if the object cannot be converted. + + + return a Generalized Time object from a tagged object. + + @param obj the tagged object holding the object we want + @param explicitly true if the object is meant to be explicitly + tagged false otherwise. + @exception ArgumentException if the tagged object cannot + be converted. + + + The correct format for this is YYYYMMDDHHMMSS[.f]Z, or without the Z + for local time, or Z+-HHMM on the end, for difference between local + time and UTC time. The fractional second amount f must consist of at + least one number with trailing zeroes removed. + + @param time the time string. + @exception ArgumentException if string is an illegal format. + + + base constructor from a local time object + + + return the time - always in the form of + YYYYMMDDhhmmssGMT(+hh:mm|-hh:mm). +

    + Normally in a certificate we would expect "Z" rather than "GMT", + however adding the "GMT" means we can just use: +

    +                dateF = new SimpleDateFormat("yyyyMMddHHmmssz");
    +            
    + To read in the time and Get a date which is compatible with our local + time zone.

    +
    + + Return the time. + @return The time string as it appeared in the encoded object. + + + return a Graphic String from the passed in object + + @param obj a DerGraphicString or an object that can be converted into one. + @exception IllegalArgumentException if the object cannot be converted. + @return a DerGraphicString instance, or null. + + + return a Graphic String from a tagged object. + + @param obj the tagged object holding the object we want + @param explicit true if the object is meant to be explicitly + tagged false otherwise. + @exception IllegalArgumentException if the tagged object cannot + be converted. + @return a DerGraphicString instance, or null. + + + basic constructor - with bytes. + @param string the byte encoding of the characters making up the string. + + + Der IA5String object - this is an ascii string. + + + return a IA5 string from the passed in object + + @exception ArgumentException if the object cannot be converted. + + + return an IA5 string from a tagged object. + + @param obj the tagged object holding the object we want + @param explicitly true if the object is meant to be explicitly + tagged false otherwise. + @exception ArgumentException if the tagged object cannot + be converted. + + + basic constructor - with bytes. + + + basic constructor - without validation. + + + Constructor with optional validation. + + @param string the base string to wrap. + @param validate whether or not to check the string. + @throws ArgumentException if validate is true and the string + contains characters that should not be in an IA5String. + + + return true if the passed in String can be represented without + loss as an IA5String, false otherwise. + + @return true if in printable set, false otherwise. + + + Der NumericString object - this is an ascii string of characters {0,1,2,3,4,5,6,7,8,9, }. + + + return a Numeric string from the passed in object + + @exception ArgumentException if the object cannot be converted. + + + return an Numeric string from a tagged object. + + @param obj the tagged object holding the object we want + @param explicitly true if the object is meant to be explicitly + tagged false otherwise. + @exception ArgumentException if the tagged object cannot + be converted. + + + basic constructor - with bytes. + + + basic constructor - without validation.. + + + Constructor with optional validation. + + @param string the base string to wrap. + @param validate whether or not to check the string. + @throws ArgumentException if validate is true and the string + contains characters that should not be in a NumericString. + + + Return true if the string can be represented as a NumericString ('0'..'9', ' ') + + @param str string to validate. + @return true if numeric, fale otherwise. + + + return an Oid from the passed in object + + @exception ArgumentException if the object cannot be converted. + + + return an object Identifier from a tagged object. + + @param obj the tagged object holding the object we want + @param explicitly true if the object is meant to be explicitly + tagged false otherwise. + @exception ArgumentException if the tagged object cannot + be converted. + + + Return true if this oid is an extension of the passed in branch, stem. + @param stem the arc or branch that is a possible parent. + @return true if the branch is on the passed in stem, false otherwise. + + + Der PrintableString object. + + + return a printable string from the passed in object. + + @exception ArgumentException if the object cannot be converted. + + + return a Printable string from a tagged object. + + @param obj the tagged object holding the object we want + @param explicitly true if the object is meant to be explicitly + tagged false otherwise. + @exception ArgumentException if the tagged object cannot + be converted. + + + basic constructor - byte encoded string. + + + basic constructor - this does not validate the string + + + Constructor with optional validation. + + @param string the base string to wrap. + @param validate whether or not to check the string. + @throws ArgumentException if validate is true and the string + contains characters that should not be in a PrintableString. + + + return true if the passed in String can be represented without + loss as a PrintableString, false otherwise. + + @return true if in printable set, false otherwise. + + + Der T61String (also the teletex string) - 8-bit characters + + + return a T61 string from the passed in object. + + @exception ArgumentException if the object cannot be converted. + + + return an T61 string from a tagged object. + + @param obj the tagged object holding the object we want + @param explicitly true if the object is meant to be explicitly + tagged false otherwise. + @exception ArgumentException if the tagged object cannot + be converted. + + + basic constructor - with bytes. + + + basic constructor - with string. + + + Der UniversalString object. + + + return a Universal string from the passed in object. + + @exception ArgumentException if the object cannot be converted. + + + return a Universal string from a tagged object. + + @param obj the tagged object holding the object we want + @param explicitly true if the object is meant to be explicitly + tagged false otherwise. + @exception ArgumentException if the tagged object cannot + be converted. + + + basic constructor - byte encoded string. + + + UTC time object. + + + return an UTC Time from the passed in object. + + @exception ArgumentException if the object cannot be converted. + + + return an UTC Time from a tagged object. + + @param obj the tagged object holding the object we want + @param explicitly true if the object is meant to be explicitly + tagged false otherwise. + @exception ArgumentException if the tagged object cannot + be converted. + + + The correct format for this is YYMMDDHHMMSSZ (it used to be that seconds were + never encoded. When you're creating one of these objects from scratch, that's + what you want to use, otherwise we'll try to deal with whatever Gets read from + the input stream... (this is why the input format is different from the GetTime() + method output). +

    + @param time the time string.

    +
    + + base constructor from a DateTime object + + + return the time as a date based on whatever a 2 digit year will return. For + standardised processing use ToAdjustedDateTime(). + + @return the resulting date + @exception ParseException if the date string cannot be parsed. + + + return the time as an adjusted date + in the range of 1950 - 2049. + + @return a date in the range of 1950 to 2049. + @exception ParseException if the date string cannot be parsed. + + + return the time - always in the form of + YYMMDDhhmmssGMT(+hh:mm|-hh:mm). +

    + Normally in a certificate we would expect "Z" rather than "GMT", + however adding the "GMT" means we can just use: +

    +                dateF = new SimpleDateFormat("yyMMddHHmmssz");
    +            
    + To read in the time and Get a date which is compatible with our local + time zone.

    +

    + Note: In some cases, due to the local date processing, this + may lead to unexpected results. If you want to stick the normal + convention of 1950 to 2049 use the GetAdjustedTime() method.

    +
    + + + Return a time string as an adjusted date with a 4 digit year. + This goes in the range of 1950 - 2049. + + + + Der UTF8String object. + + + return an UTF8 string from the passed in object. + + @exception ArgumentException if the object cannot be converted. + + + return an UTF8 string from a tagged object. + + @param obj the tagged object holding the object we want + @param explicitly true if the object is meant to be explicitly + tagged false otherwise. + @exception ArgumentException if the tagged object cannot + be converted. + + + basic constructor - byte encoded string. + + + basic constructor + + + return a Videotex String from the passed in object + + @param obj a DERVideotexString or an object that can be converted into one. + @exception IllegalArgumentException if the object cannot be converted. + @return a DERVideotexString instance, or null. + + + return a Videotex String from a tagged object. + + @param obj the tagged object holding the object we want + @param explicit true if the object is meant to be explicitly + tagged false otherwise. + @exception IllegalArgumentException if the tagged object cannot + be converted. + @return a DERVideotexString instance, or null. + + + basic constructor - with bytes. + @param string the byte encoding of the characters making up the string. + + + Der VisibleString object. + + + return a Visible string from the passed in object. + + @exception ArgumentException if the object cannot be converted. + + + return a Visible string from a tagged object. + + @param obj the tagged object holding the object we want + @param explicitly true if the object is meant to be explicitly + tagged false otherwise. + @exception ArgumentException if the tagged object cannot + be converted. + + + basic constructor - byte encoded string. + + + basic constructor + + + + RFC 3126: 4.3.1 Certificate Values Attribute Definition + + CertificateValues ::= SEQUENCE OF Certificate + + + + +
    +            CommitmentTypeIndication ::= SEQUENCE {
    +                 commitmentTypeId   CommitmentTypeIdentifier,
    +                 commitmentTypeQualifier   SEQUENCE SIZE (1..MAX) OF
    +                         CommitmentTypeQualifier OPTIONAL }
    +            
    +
    + + Commitment type qualifiers, used in the Commitment-Type-Indication attribute (RFC3126). + +
    +               CommitmentTypeQualifier ::= SEQUENCE {
    +                   commitmentTypeIdentifier  CommitmentTypeIdentifier,
    +                   qualifier          ANY DEFINED BY commitmentTypeIdentifier OPTIONAL }
    +             
    +
    + + Creates a new CommitmentTypeQualifier instance. + + @param commitmentTypeIdentifier a CommitmentTypeIdentifier value + + + Creates a new CommitmentTypeQualifier instance. + + @param commitmentTypeIdentifier a CommitmentTypeIdentifier value + @param qualifier the qualifier, defined by the above field. + + + Creates a new CommitmentTypeQualifier instance. + + @param as CommitmentTypeQualifier structure + encoded as an Asn1Sequence. + + + Returns a DER-encodable representation of this instance. + + @return a Asn1Object value + + + + RFC 3126: 4.2.1 Complete Certificate Refs Attribute Definition + + CompleteCertificateRefs ::= SEQUENCE OF OtherCertID + + + + + + RFC 3126: 4.2.2 Complete Revocation Refs Attribute Definition + + CompleteRevocationRefs ::= SEQUENCE OF CrlOcspRef + + + + + + RFC 3126: 4.2.2 Complete Revocation Refs Attribute Definition + + CrlIdentifier ::= SEQUENCE + { + crlissuer Name, + crlIssuedTime UTCTime, + crlNumber INTEGER OPTIONAL + } + + + + + + RFC 3126: 4.2.2 Complete Revocation Refs Attribute Definition + + CRLListID ::= SEQUENCE + { + crls SEQUENCE OF CrlValidatedID + } + + + + + + RFC 3126: 4.2.2 Complete Revocation Refs Attribute Definition + + CrlOcspRef ::= SEQUENCE { + crlids [0] CRLListID OPTIONAL, + ocspids [1] OcspListID OPTIONAL, + otherRev [2] OtherRevRefs OPTIONAL + } + + + + + + RFC 3126: 4.2.2 Complete Revocation Refs Attribute Definition + + CrlValidatedID ::= SEQUENCE { + crlHash OtherHash, + crlIdentifier CrlIdentifier OPTIONAL} + + + + + + RFC 3126: 4.2.2 Complete Revocation Refs Attribute Definition + + OcspIdentifier ::= SEQUENCE { + ocspResponderID ResponderID, + -- As in OCSP response data + producedAt GeneralizedTime + -- As in OCSP response data + } + + + + + + RFC 3126: 4.2.2 Complete Revocation Refs Attribute Definition + + OcspListID ::= SEQUENCE { + ocspResponses SEQUENCE OF OcspResponsesID + } + + + + + + RFC 3126: 4.2.2 Complete Revocation Refs Attribute Definition + + OcspResponsesID ::= SEQUENCE { + ocspIdentifier OcspIdentifier, + ocspRepHash OtherHash OPTIONAL + } + + + + + + + OtherCertID ::= SEQUENCE { + otherCertHash OtherHash, + issuerSerial IssuerSerial OPTIONAL + } + + + + + + + OtherHash ::= CHOICE { + sha1Hash OtherHashValue, -- This contains a SHA-1 hash + otherHash OtherHashAlgAndValue + } + + OtherHashValue ::= OCTET STRING + + + + + + Summary description for OtherHashAlgAndValue. + + + + OtherHashAlgAndValue ::= SEQUENCE { + hashAlgorithm AlgorithmIdentifier, + hashValue OtherHashValue + } + + OtherHashValue ::= OCTET STRING + + + + + + RFC 3126: 4.2.2 Complete Revocation Refs Attribute Definition + + OtherRevRefs ::= SEQUENCE + { + otherRevRefType OtherRevRefType, + otherRevRefs ANY DEFINED BY otherRevRefType + } + + OtherRevRefType ::= OBJECT IDENTIFIER + + + + + + RFC 3126: 4.3.2 Revocation Values Attribute Definition + + OtherRevVals ::= SEQUENCE + { + otherRevValType OtherRevValType, + otherRevVals ANY DEFINED BY otherRevValType + } + + OtherRevValType ::= OBJECT IDENTIFIER + + + + + + + OtherSigningCertificate ::= SEQUENCE { + certs SEQUENCE OF OtherCertID, + policies SEQUENCE OF PolicyInformation OPTIONAL + } + + + + + + RFC 5126: 6.3.4. revocation-values Attribute Definition + + RevocationValues ::= SEQUENCE { + crlVals [0] SEQUENCE OF CertificateList OPTIONAL, + ocspVals [1] SEQUENCE OF BasicOCSPResponse OPTIONAL, + otherRevVals [2] OtherRevVals OPTIONAL + } + + + + + + + SignaturePolicyId ::= SEQUENCE { + sigPolicyIdentifier SigPolicyId, + sigPolicyHash SigPolicyHash, + sigPolicyQualifiers SEQUENCE SIZE (1..MAX) OF SigPolicyQualifierInfo OPTIONAL + } + + SigPolicyId ::= OBJECT IDENTIFIER + + SigPolicyHash ::= OtherHashAlgAndValue + + + + + + + SignaturePolicyIdentifier ::= CHOICE { + SignaturePolicyId SignaturePolicyId, + SignaturePolicyImplied SignaturePolicyImplied + } + + SignaturePolicyImplied ::= NULL + + + + + +
    +              SignerAttribute ::= SEQUENCE OF CHOICE {
    +                  claimedAttributes   [0] ClaimedAttributes,
    +                  certifiedAttributes [1] CertifiedAttributes }
    +            
    +              ClaimedAttributes ::= SEQUENCE OF Attribute
    +              CertifiedAttributes ::= AttributeCertificate -- as defined in RFC 3281: see clause 4.1.
    +             
    +
    + + Signer-Location attribute (RFC3126). + +
    +               SignerLocation ::= SEQUENCE {
    +                   countryName        [0] DirectoryString OPTIONAL,
    +                   localityName       [1] DirectoryString OPTIONAL,
    +                   postalAddress      [2] PostalAddress OPTIONAL }
    +            
    +               PostalAddress ::= SEQUENCE SIZE(1..6) OF DirectoryString
    +             
    +
    + +
    +               SignerLocation ::= SEQUENCE {
    +                   countryName        [0] DirectoryString OPTIONAL,
    +                   localityName       [1] DirectoryString OPTIONAL,
    +                   postalAddress      [2] PostalAddress OPTIONAL }
    +            
    +               PostalAddress ::= SEQUENCE SIZE(1..6) OF DirectoryString
    +            
    +               DirectoryString ::= CHOICE {
    +                     teletexString           TeletexString (SIZE (1..MAX)),
    +                     printableString         PrintableString (SIZE (1..MAX)),
    +                     universalString         UniversalString (SIZE (1..MAX)),
    +                     utf8String              UTF8String (SIZE (1.. MAX)),
    +                     bmpString               BMPString (SIZE (1..MAX)) }
    +             
    +
    + + + + SigPolicyQualifierInfo ::= SEQUENCE { + sigPolicyQualifierId SigPolicyQualifierId, + sigQualifier ANY DEFINED BY sigPolicyQualifierId + } + + SigPolicyQualifierId ::= OBJECT IDENTIFIER + + + + + constructor + + +
    +            ContentHints ::= SEQUENCE {
    +              contentDescription UTF8String (SIZE (1..MAX)) OPTIONAL,
    +              contentType ContentType }
    +            
    +
    + + Create from OCTET STRING whose octets represent the identifier. + + + Create from byte array representing the identifier. + + + The definition of ContentIdentifier is +
    +            ContentIdentifier ::=  OCTET STRING
    +            
    + id-aa-contentIdentifier OBJECT IDENTIFIER ::= { iso(1) + member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9) + smime(16) id-aa(2) 7 } +
    + + constructor + + +
    +            EssCertID ::= SEQUENCE {
    +                certHash Hash,
    +                issuerSerial IssuerSerial OPTIONAL }
    +            
    +
    + +
    +             EssCertIDv2 ::=  SEQUENCE {
    +                 hashAlgorithm     AlgorithmIdentifier
    +                          DEFAULT {algorithm id-sha256},
    +                 certHash          Hash,
    +                 issuerSerial      IssuerSerial OPTIONAL
    +             }
    +            
    +             Hash ::= OCTET STRING
    +            
    +             IssuerSerial ::= SEQUENCE {
    +                 issuer         GeneralNames,
    +                 serialNumber   CertificateSerialNumber
    +             }
    +             
    +
    + + constructor + + +
    +             OtherCertID ::= SEQUENCE {
    +                 otherCertHash    OtherHash,
    +                 issuerSerial     IssuerSerial OPTIONAL }
    +            
    +             OtherHash ::= CHOICE {
    +                 sha1Hash     OCTET STRING,
    +                 otherHash    OtherHashAlgAndValue }
    +            
    +             OtherHashAlgAndValue ::= SEQUENCE {
    +                 hashAlgorithm    AlgorithmIdentifier,
    +                 hashValue        OCTET STRING }
    +            
    +             
    +
    + + constructors + + + The definition of OtherSigningCertificate is +
    +            OtherSigningCertificate ::=  SEQUENCE {
    +                 certs        SEQUENCE OF OtherCertID,
    +                 policies     SEQUENCE OF PolicyInformation OPTIONAL
    +            }
    +            
    + id-aa-ets-otherSigCert OBJECT IDENTIFIER ::= { iso(1) + member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9) + smime(16) id-aa(2) 19 } +
    + + constructors + + + The definition of SigningCertificate is +
    +            SigningCertificate ::=  SEQUENCE {
    +                 certs        SEQUENCE OF EssCertID,
    +                 policies     SEQUENCE OF PolicyInformation OPTIONAL
    +            }
    +            
    + id-aa-signingCertificate OBJECT IDENTIFIER ::= { iso(1) + member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9) + smime(16) id-aa(2) 12 } +
    + + The definition of SigningCertificateV2 is +
    +            SigningCertificateV2 ::=  SEQUENCE {
    +                 certs        SEQUENCE OF EssCertIDv2,
    +                 policies     SEQUENCE OF PolicyInformation OPTIONAL
    +            }
    +            
    + id-aa-signingCertificateV2 OBJECT IDENTIFIER ::= { iso(1) + member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9) + smime(16) id-aa(2) 47 } +
    + + The CscaMasterList object. This object can be wrapped in a + CMSSignedData to be published in LDAP. + +
    +             CscaMasterList ::= SEQUENCE {
    +               version                CscaMasterListVersion,
    +               certList               SET OF Certificate }
    +               
    +             CscaMasterListVersion :: INTEGER {v0(0)}
    +             
    +
    + + The DataGroupHash object. +
    +             DataGroupHash  ::=  SEQUENCE {
    +                  dataGroupNumber         DataGroupNumber,
    +                  dataGroupHashValue     OCTET STRING }
    +            
    +             DataGroupNumber ::= INTEGER {
    +                     dataGroup1    (1),
    +                     dataGroup1    (2),
    +                     dataGroup1    (3),
    +                     dataGroup1    (4),
    +                     dataGroup1    (5),
    +                     dataGroup1    (6),
    +                     dataGroup1    (7),
    +                     dataGroup1    (8),
    +                     dataGroup1    (9),
    +                     dataGroup1    (10),
    +                     dataGroup1    (11),
    +                     dataGroup1    (12),
    +                     dataGroup1    (13),
    +                     dataGroup1    (14),
    +                     dataGroup1    (15),
    +                     dataGroup1    (16) }
    +            
    +             
    +
    + + The LDSSecurityObject object (V1.8). +
    +             LDSSecurityObject ::= SEQUENCE {
    +               version                LDSSecurityObjectVersion,
    +               hashAlgorithm          DigestAlgorithmIdentifier,
    +               dataGroupHashValues    SEQUENCE SIZE (2..ub-DataGroups) OF DataHashGroup,
    +               ldsVersionInfo         LDSVersionInfo OPTIONAL
    +                 -- if present, version MUST be v1 }
    +            
    +             DigestAlgorithmIdentifier ::= AlgorithmIdentifier,
    +            
    +             LDSSecurityObjectVersion :: INTEGER {V0(0)}
    +             
    +
    + +
    +            LDSVersionInfo ::= SEQUENCE {
    +               ldsVersion PRINTABLE STRING
    +               unicodeVersion PRINTABLE STRING
    +             }
    +            
    + @return +
    + + The id-isismtt-cp-accredited OID indicates that the certificate is a + qualified certificate according to Directive 1999/93/EC of the European + Parliament and of the Council of 13 December 1999 on a Community + Framework for Electronic Signatures, which additionally conforms the + special requirements of the SigG and has been issued by an accredited CA. + + + Certificate extensionDate of certificate generation + +
    +            		DateOfCertGenSyntax ::= GeneralizedTime
    +             
    +
    + + Attribute to indicate that the certificate holder may sign in the name of + a third person. May also be used as extension in a certificate. + + + Attribute to indicate admissions to certain professions. May be used as + attribute in attribute certificate or as extension in a certificate + + + Monetary limit for transactions. The QcEuMonetaryLimit QC statement MUST + be used in new certificates in place of the extension/attribute + MonetaryLimit since January 1, 2004. For the sake of backward + compatibility with certificates already in use, SigG conforming + components MUST support MonetaryLimit (as well as QcEuLimitValue). + + + A declaration of majority. May be used as attribute in attribute + certificate or as extension in a certificate + + + + Serial number of the smart card containing the corresponding private key + +
    +            		ICCSNSyntax ::= OCTET STRING (SIZE(8..20))
    +             
    +
    + + + Reference for a file of a smartcard that stores the public key of this + certificate and that is used as �security anchor�. + +
    +            		PKReferenceSyntax ::= OCTET STRING (SIZE(20))
    +             
    +
    + + Some other restriction regarding the usage of this certificate. May be + used as attribute in attribute certificate or as extension in a + certificate. + +
    +            		RestrictionSyntax ::= DirectoryString (SIZE(1..1024))
    +             
    + + @see Org.BouncyCastle.Asn1.IsisMtt.X509.Restriction +
    + + + (Single)Request extension: Clients may include this extension in a + (single) Request to request the responder to send the certificate in the + response message along with the status information. Besides the LDAP + service, this extension provides another mechanism for the distribution + of certificates, which MAY optionally be provided by certificate + repositories. + +
    +            		RetrieveIfAllowed ::= BOOLEAN
    +             
    +
    + + SingleOCSPResponse extension: The certificate requested by the client by + inserting the RetrieveIfAllowed extension in the request, will be + returned in this extension. + + @see Org.BouncyCastle.Asn1.IsisMtt.Ocsp.RequestedCertificate + + + Base ObjectIdentifier for naming authorities + + + SingleOCSPResponse extension: Date, when certificate has been published + in the directory and status information has become available. Currently, + accrediting authorities enforce that SigG-conforming OCSP servers include + this extension in the responses. + +
    +            		CertInDirSince ::= GeneralizedTime
    +             
    +
    + + Hash of a certificate in OCSP. + + @see Org.BouncyCastle.Asn1.IsisMtt.Ocsp.CertHash + + +
    +            		NameAtBirth ::= DirectoryString(SIZE(1..64)
    +             
    + + Used in + {@link Org.BouncyCastle.Asn1.X509.SubjectDirectoryAttributes SubjectDirectoryAttributes} +
    + + Some other information of non-restrictive nature regarding the usage of + this certificate. May be used as attribute in atribute certificate or as + extension in a certificate. + +
    +                          AdditionalInformationSyntax ::= DirectoryString (SIZE(1..2048))
    +            
    + + @see Org.BouncyCastle.Asn1.IsisMtt.X509.AdditionalInformationSyntax +
    + + Indicates that an attribute certificate exists, which limits the + usability of this public key certificate. Whenever verifying a signature + with the help of this certificate, the content of the corresponding + attribute certificate should be concerned. This extension MUST be + included in a PKC, if a corresponding attribute certificate (having the + PKC as base certificate) contains some attribute that restricts the + usability of the PKC too. Attribute certificates with restricting content + MUST always be included in the signed document. + +
    +            		LiabilityLimitationFlagSyntax ::= BOOLEAN
    +             
    +
    + + ISIS-MTT PROFILE: The responder may include this extension in a response to + send the hash of the requested certificate to the responder. This hash is + cryptographically bound to the certificate and serves as evidence that the + certificate is known to the responder (i.e. it has been issued and is present + in the directory). Hence, this extension is a means to provide a positive + statement of availability as described in T8.[8]. As explained in T13.[1], + clients may rely on this information to be able to validate signatures after + the expiry of the corresponding certificate. Hence, clients MUST support this + extension. If a positive statement of availability is to be delivered, this + extension syntax and OID MUST be used. +

    +

    +

    +                CertHash ::= SEQUENCE {
    +                  hashAlgorithm AlgorithmIdentifier,
    +                  certificateHash OCTET STRING
    +                }
    +            
    +
    + + Constructor from Asn1Sequence. +

    + The sequence is of type CertHash: +

    +

    +                 CertHash ::= SEQUENCE {
    +                   hashAlgorithm AlgorithmIdentifier,
    +                   certificateHash OCTET STRING
    +                 }
    +             
    + + @param seq The ASN.1 sequence. +
    + + Constructor from a given details. + + @param hashAlgorithm The hash algorithm identifier. + @param certificateHash The hash of the whole DER encoding of the certificate. + + + Produce an object suitable for an Asn1OutputStream. +

    + Returns: +

    +

    +                 CertHash ::= SEQUENCE {
    +                   hashAlgorithm AlgorithmIdentifier,
    +                   certificateHash OCTET STRING
    +                 }
    +             
    + + @return an Asn1Object +
    + + ISIS-MTT-Optional: The certificate requested by the client by inserting the + RetrieveIfAllowed extension in the request, will be returned in this + extension. +

    + ISIS-MTT-SigG: The signature act allows publishing certificates only then, + when the certificate owner gives his isExplicit permission. Accordingly, there + may be �nondownloadable� certificates, about which the responder must provide + status information, but MUST NOT include them in the response. Clients may + get therefore the following three kind of answers on a single request + including the RetrieveIfAllowed extension: +

      +
    • a) the responder supports the extension and is allowed to publish the + certificate: RequestedCertificate returned including the requested + certificate
    • +
    • b) the responder supports the extension but is NOT allowed to publish + the certificate: RequestedCertificate returned including an empty OCTET + STRING
    • +
    • c) the responder does not support the extension: RequestedCertificate is + not included in the response
    • +
    + Clients requesting RetrieveIfAllowed MUST be able to handle these cases. If + any of the OCTET STRING options is used, it MUST contain the DER encoding of + the requested certificate. +

    +

    +                       RequestedCertificate ::= CHOICE {
    +                         Certificate Certificate,
    +                         publicKeyCertificate [0] EXPLICIT OCTET STRING,
    +                         attributeCertificate [1] EXPLICIT OCTET STRING
    +                       }
    +            
    +
    + + Constructor from a given details. +

    + Only one parameter can be given. All other must be null. + + @param certificate Given as Certificate + + + Produce an object suitable for an Asn1OutputStream. +

    + Returns: +

    +

    +                        RequestedCertificate ::= CHOICE {
    +                          Certificate Certificate,
    +                          publicKeyCertificate [0] EXPLICIT OCTET STRING,
    +                          attributeCertificate [1] EXPLICIT OCTET STRING
    +                        }
    +             
    + + @return an Asn1Object +
    + + Some other information of non-restrictive nature regarding the usage of this + certificate. + +
    +               AdditionalInformationSyntax ::= DirectoryString (SIZE(1..2048))
    +            
    +
    + + Constructor from a given details. + + @param information The describtion of the information. + + + Produce an object suitable for an Asn1OutputStream. +

    + Returns: +

    +

    +               AdditionalInformationSyntax ::= DirectoryString (SIZE(1..2048))
    +             
    + + @return an Asn1Object +
    + + An Admissions structure. +

    +

    +                        Admissions ::= SEQUENCE
    +                        {
    +                          admissionAuthority [0] EXPLICIT GeneralName OPTIONAL
    +                          namingAuthority [1] EXPLICIT NamingAuthority OPTIONAL
    +                          professionInfos SEQUENCE OF ProfessionInfo
    +                        }
    +             

    +

    + + @see Org.BouncyCastle.Asn1.IsisMtt.X509.AdmissionSyntax + @see Org.BouncyCastle.Asn1.IsisMtt.X509.ProfessionInfo + @see Org.BouncyCastle.Asn1.IsisMtt.X509.NamingAuthority +
    + + Constructor from Asn1Sequence. +

    + The sequence is of type ProcurationSyntax: +

    +

    +                        Admissions ::= SEQUENCE
    +                        {
    +                          admissionAuthority [0] EXPLICIT GeneralName OPTIONAL
    +                          namingAuthority [1] EXPLICIT NamingAuthority OPTIONAL
    +                          professionInfos SEQUENCE OF ProfessionInfo
    +                        }
    +             
    + + @param seq The ASN.1 sequence. +
    + + Constructor from a given details. +

    + Parameter professionInfos is mandatory. + + @param admissionAuthority The admission authority. + @param namingAuthority The naming authority. + @param professionInfos The profession infos. + + + Produce an object suitable for an Asn1OutputStream. +

    + Returns: +

    +

    +                   Admissions ::= SEQUENCE
    +                   {
    +                     admissionAuthority [0] EXPLICIT GeneralName OPTIONAL
    +                     namingAuthority [1] EXPLICIT NamingAuthority OPTIONAL
    +                     professionInfos SEQUENCE OF ProfessionInfo
    +                   }
    +             

    +

    + + @return an Asn1Object +
    + + Attribute to indicate admissions to certain professions. +

    +

    +                 AdmissionSyntax ::= SEQUENCE
    +                 {
    +                   admissionAuthority GeneralName OPTIONAL,
    +                   contentsOfAdmissions SEQUENCE OF Admissions
    +                 }
    +             

    + Admissions ::= SEQUENCE + { + admissionAuthority [0] EXPLICIT GeneralName OPTIONAL + namingAuthority [1] EXPLICIT NamingAuthority OPTIONAL + professionInfos SEQUENCE OF ProfessionInfo + } +

    + NamingAuthority ::= SEQUENCE + { + namingAuthorityId OBJECT IDENTIFIER OPTIONAL, + namingAuthorityUrl IA5String OPTIONAL, + namingAuthorityText DirectoryString(SIZE(1..128)) OPTIONAL + } +

    + ProfessionInfo ::= SEQUENCE + { + namingAuthority [0] EXPLICIT NamingAuthority OPTIONAL, + professionItems SEQUENCE OF DirectoryString (SIZE(1..128)), + professionOIDs SEQUENCE OF OBJECT IDENTIFIER OPTIONAL, + registrationNumber PrintableString(SIZE(1..128)) OPTIONAL, + addProfessionInfo OCTET STRING OPTIONAL + } +

    +

    +

    + ISIS-MTT PROFILE: The relatively complex structure of AdmissionSyntax + supports the following concepts and requirements: +

      +
    • External institutions (e.g. professional associations, chambers, unions, + administrative bodies, companies, etc.), which are responsible for granting + and verifying professional admissions, are indicated by means of the data + field admissionAuthority. An admission authority is indicated by a + GeneralName object. Here an X.501 directory name (distinguished name) can be + indicated in the field directoryName, a URL address can be indicated in the + field uniformResourceIdentifier, and an object identifier can be indicated in + the field registeredId.
    • +
    • The names of authorities which are responsible for the administration of + title registers are indicated in the data field namingAuthority. The name of + the authority can be identified by an object identifier in the field + namingAuthorityId, by means of a text string in the field + namingAuthorityText, by means of a URL address in the field + namingAuthorityUrl, or by a combination of them. For example, the text string + can contain the name of the authority, the country and the name of the title + register. The URL-option refers to a web page which contains lists with + officially registered professions (text and possibly OID) as well as + further information on these professions. Object identifiers for the + component namingAuthorityId are grouped under the OID-branch + id-isis-at-namingAuthorities and must be applied for.
    • +
    • See http://www.teletrust.de/anwend.asp?Id=30200&Sprache=E_&HomePG=0 + for an application form and http://www.teletrust.de/links.asp?id=30220,11 + for an overview of registered naming authorities.
    • +
    • By means of the data type ProfessionInfo certain professions, + specializations, disciplines, fields of activity, etc. are identified. A + profession is represented by one or more text strings, resp. profession OIDs + in the fields professionItems and professionOIDs and by a registration number + in the field registrationNumber. An indication in text form must always be + present, whereas the other indications are optional. The component + addProfessionInfo may contain additional applicationspecific information in + DER-encoded form.
    • +
    +

    + By means of different namingAuthority-OIDs or profession OIDs hierarchies of + professions, specializations, disciplines, fields of activity, etc. can be + expressed. The issuing admission authority should always be indicated (field + admissionAuthority), whenever a registration number is presented. Still, + information on admissions can be given without indicating an admission or a + naming authority by the exclusive use of the component professionItems. In + this case the certification authority is responsible for the verification of + the admission information. +

    +

    +

    + This attribute is single-valued. Still, several admissions can be captured in + the sequence structure of the component contentsOfAdmissions of + AdmissionSyntax or in the component professionInfos of Admissions. The + component admissionAuthority of AdmissionSyntax serves as default value for + the component admissionAuthority of Admissions. Within the latter component + the default value can be overwritten, in case that another authority is + responsible. The component namingAuthority of Admissions serves as a default + value for the component namingAuthority of ProfessionInfo. Within the latter + component the default value can be overwritten, in case that another naming + authority needs to be recorded. +

    + The length of the string objects is limited to 128 characters. It is + recommended to indicate a namingAuthorityURL in all issued attribute + certificates. If a namingAuthorityURL is indicated, the field professionItems + of ProfessionInfo should contain only registered titles. If the field + professionOIDs exists, it has to contain the OIDs of the professions listed + in professionItems in the same order. In general, the field professionInfos + should contain only one entry, unless the admissions that are to be listed + are logically connected (e.g. they have been issued under the same admission + number). + + @see Org.BouncyCastle.Asn1.IsisMtt.X509.Admissions + @see Org.BouncyCastle.Asn1.IsisMtt.X509.ProfessionInfo + @see Org.BouncyCastle.Asn1.IsisMtt.X509.NamingAuthority + + + Constructor from Asn1Sequence. +

    + The sequence is of type ProcurationSyntax: +

    +

    +                 AdmissionSyntax ::= SEQUENCE
    +                 {
    +                   admissionAuthority GeneralName OPTIONAL,
    +                   contentsOfAdmissions SEQUENCE OF Admissions
    +                 }
    +             

    + Admissions ::= SEQUENCE + { + admissionAuthority [0] EXPLICIT GeneralName OPTIONAL + namingAuthority [1] EXPLICIT NamingAuthority OPTIONAL + professionInfos SEQUENCE OF ProfessionInfo + } +

    + NamingAuthority ::= SEQUENCE + { + namingAuthorityId OBJECT IDENTIFIER OPTIONAL, + namingAuthorityUrl IA5String OPTIONAL, + namingAuthorityText DirectoryString(SIZE(1..128)) OPTIONAL + } +

    + ProfessionInfo ::= SEQUENCE + { + namingAuthority [0] EXPLICIT NamingAuthority OPTIONAL, + professionItems SEQUENCE OF DirectoryString (SIZE(1..128)), + professionOIDs SEQUENCE OF OBJECT IDENTIFIER OPTIONAL, + registrationNumber PrintableString(SIZE(1..128)) OPTIONAL, + addProfessionInfo OCTET STRING OPTIONAL + } +

    + + @param seq The ASN.1 sequence. +
    + + Constructor from given details. + + @param admissionAuthority The admission authority. + @param contentsOfAdmissions The admissions. + + + Produce an object suitable for an Asn1OutputStream. +

    + Returns: +

    +

    +                 AdmissionSyntax ::= SEQUENCE
    +                 {
    +                   admissionAuthority GeneralName OPTIONAL,
    +                   contentsOfAdmissions SEQUENCE OF Admissions
    +                 }
    +             

    + Admissions ::= SEQUENCE + { + admissionAuthority [0] EXPLICIT GeneralName OPTIONAL + namingAuthority [1] EXPLICIT NamingAuthority OPTIONAL + professionInfos SEQUENCE OF ProfessionInfo + } +

    + NamingAuthority ::= SEQUENCE + { + namingAuthorityId OBJECT IDENTIFIER OPTIONAL, + namingAuthorityUrl IA5String OPTIONAL, + namingAuthorityText DirectoryString(SIZE(1..128)) OPTIONAL + } +

    + ProfessionInfo ::= SEQUENCE + { + namingAuthority [0] EXPLICIT NamingAuthority OPTIONAL, + professionItems SEQUENCE OF DirectoryString (SIZE(1..128)), + professionOIDs SEQUENCE OF OBJECT IDENTIFIER OPTIONAL, + registrationNumber PrintableString(SIZE(1..128)) OPTIONAL, + addProfessionInfo OCTET STRING OPTIONAL + } +

    + + @return an Asn1Object +
    + + @return Returns the contentsOfAdmissions. + + + @return Returns the admissionAuthority if present, null otherwise. + + + A declaration of majority. +

    +

    +                      DeclarationOfMajoritySyntax ::= CHOICE
    +                      {
    +                        notYoungerThan [0] IMPLICIT INTEGER,
    +                        fullAgeAtCountry [1] IMPLICIT SEQUENCE
    +                        {
    +                          fullAge BOOLEAN DEFAULT TRUE,
    +                          country PrintableString (SIZE(2))
    +                        }
    +                        dateOfBirth [2] IMPLICIT GeneralizedTime
    +                      }
    +            
    +

    + fullAgeAtCountry indicates the majority of the owner with respect to the laws + of a specific country. + + + Produce an object suitable for an Asn1OutputStream. +

    + Returns: +

    +

    +                       DeclarationOfMajoritySyntax ::= CHOICE
    +                       {
    +                         notYoungerThan [0] IMPLICIT INTEGER,
    +                         fullAgeAtCountry [1] IMPLICIT SEQUENCE
    +                         {
    +                           fullAge BOOLEAN DEFAULT TRUE,
    +                           country PrintableString (SIZE(2))
    +                         }
    +                         dateOfBirth [2] IMPLICIT GeneralizedTime
    +                       }
    +             
    + + @return an Asn1Object +
    + + @return notYoungerThan if that's what we are, -1 otherwise + + + Monetary limit for transactions. The QcEuMonetaryLimit QC statement MUST be + used in new certificates in place of the extension/attribute MonetaryLimit + since January 1, 2004. For the sake of backward compatibility with + certificates already in use, components SHOULD support MonetaryLimit (as well + as QcEuLimitValue). +

    + Indicates a monetary limit within which the certificate holder is authorized + to act. (This value DOES NOT express a limit on the liability of the + certification authority). +

    +

    +               MonetaryLimitSyntax ::= SEQUENCE
    +               {
    +                 currency PrintableString (SIZE(3)),
    +                 amount INTEGER,
    +                 exponent INTEGER
    +               }
    +            
    +

    + currency must be the ISO code. +

    + value = amount�10*exponent + + + Constructor from a given details. +

    +

    + value = amount�10^exponent + + @param currency The currency. Must be the ISO code. + @param amount The amount + @param exponent The exponent + + + Produce an object suitable for an Asn1OutputStream. +

    + Returns: +

    +

    +                MonetaryLimitSyntax ::= SEQUENCE
    +                {
    +                  currency PrintableString (SIZE(3)),
    +                  amount INTEGER,
    +                  exponent INTEGER
    +                }
    +             
    + + @return an Asn1Object +
    + + Names of authorities which are responsible for the administration of title + registers. + +
    +                        NamingAuthority ::= SEQUENCE 
    +                        {
    +                          namingAuthorityID OBJECT IDENTIFIER OPTIONAL,
    +                          namingAuthorityUrl IA5String OPTIONAL,
    +                          namingAuthorityText DirectoryString(SIZE(1..128)) OPTIONAL
    +                        }
    +            
    + @see Org.BouncyCastle.Asn1.IsisMtt.X509.AdmissionSyntax + +
    + + Profession OIDs should always be defined under the OID branch of the + responsible naming authority. At the time of this writing, the work group + �Recht, Wirtschaft, Steuern� (�Law, Economy, Taxes�) is registered as the + first naming authority under the OID id-isismtt-at-namingAuthorities. + + + Constructor from Asn1Sequence. +

    +

    +

    +                         NamingAuthority ::= SEQUENCE
    +                         {
    +                           namingAuthorityID OBJECT IDENTIFIER OPTIONAL,
    +                           namingAuthorityUrl IA5String OPTIONAL,
    +                           namingAuthorityText DirectoryString(SIZE(1..128)) OPTIONAL
    +                         }
    +             
    + + @param seq The ASN.1 sequence. +
    + + Constructor from given details. +

    + All parameters can be combined. + + @param namingAuthorityID ObjectIdentifier for naming authority. + @param namingAuthorityUrl URL for naming authority. + @param namingAuthorityText Textual representation of naming authority. + + + Produce an object suitable for an Asn1OutputStream. +

    + Returns: +

    +

    +                         NamingAuthority ::= SEQUENCE
    +                         {
    +                           namingAuthorityID OBJECT IDENTIFIER OPTIONAL,
    +                           namingAuthorityUrl IA5String OPTIONAL,
    +                           namingAuthorityText DirectoryString(SIZE(1..128)) OPTIONAL
    +                         }
    +             
    + + @return an Asn1Object +
    + + @return Returns the namingAuthorityID. + + + @return Returns the namingAuthorityText. + + + @return Returns the namingAuthorityUrl. + + + Attribute to indicate that the certificate holder may sign in the name of a + third person. +

    + ISIS-MTT PROFILE: The corresponding ProcurationSyntax contains either the + name of the person who is represented (subcomponent thirdPerson) or a + reference to his/her base certificate (in the component signingFor, + subcomponent certRef), furthermore the optional components country and + typeSubstitution to indicate the country whose laws apply, and respectively + the type of procuration (e.g. manager, procuration, custody). +

    +

    + ISIS-MTT PROFILE: The GeneralName MUST be of type directoryName and MAY only + contain: - RFC3039 attributes, except pseudonym (countryName, commonName, + surname, givenName, serialNumber, organizationName, organizationalUnitName, + stateOrProvincename, localityName, postalAddress) and - SubjectDirectoryName + attributes (title, dateOfBirth, placeOfBirth, gender, countryOfCitizenship, + countryOfResidence and NameAtBirth). +

    +
    +                          ProcurationSyntax ::= SEQUENCE {
    +                            country [1] EXPLICIT PrintableString(SIZE(2)) OPTIONAL,
    +                            typeOfSubstitution [2] EXPLICIT DirectoryString (SIZE(1..128)) OPTIONAL,
    +                            signingFor [3] EXPLICIT SigningFor 
    +                          }
    +                          
    +                          SigningFor ::= CHOICE 
    +                          { 
    +                            thirdPerson GeneralName,
    +                            certRef IssuerSerial 
    +                          }
    +            
    + +
    + + Constructor from Asn1Sequence. +

    + The sequence is of type ProcurationSyntax: +

    +

    +                           ProcurationSyntax ::= SEQUENCE {
    +                             country [1] EXPLICIT PrintableString(SIZE(2)) OPTIONAL,
    +                             typeOfSubstitution [2] EXPLICIT DirectoryString (SIZE(1..128)) OPTIONAL,
    +                             signingFor [3] EXPLICIT SigningFor
    +                           }
    +             

    + SigningFor ::= CHOICE + { + thirdPerson GeneralName, + certRef IssuerSerial + } +

    + + @param seq The ASN.1 sequence. +
    + + Constructor from a given details. +

    +

    + Either generalName or certRef MUST be + null. + + @param country The country code whose laws apply. + @param typeOfSubstitution The type of procuration. + @param certRef Reference to certificate of the person who is represented. + + + Constructor from a given details. +

    +

    + Either generalName or certRef MUST be + null. + + @param country The country code whose laws apply. + @param typeOfSubstitution The type of procuration. + @param thirdPerson The GeneralName of the person who is represented. + + + Produce an object suitable for an Asn1OutputStream. +

    + Returns: +

    +

    +                           ProcurationSyntax ::= SEQUENCE {
    +                             country [1] EXPLICIT PrintableString(SIZE(2)) OPTIONAL,
    +                             typeOfSubstitution [2] EXPLICIT DirectoryString (SIZE(1..128)) OPTIONAL,
    +                             signingFor [3] EXPLICIT SigningFor
    +                           }
    +             

    + SigningFor ::= CHOICE + { + thirdPerson GeneralName, + certRef IssuerSerial + } +

    + + @return an Asn1Object +
    + + Professions, specializations, disciplines, fields of activity, etc. + +
    +                          ProfessionInfo ::= SEQUENCE 
    +                          {
    +                            namingAuthority [0] EXPLICIT NamingAuthority OPTIONAL,
    +                            professionItems SEQUENCE OF DirectoryString (SIZE(1..128)),
    +                            professionOids SEQUENCE OF OBJECT IDENTIFIER OPTIONAL,
    +                            registrationNumber PrintableString(SIZE(1..128)) OPTIONAL,
    +                            addProfessionInfo OCTET STRING OPTIONAL 
    +                          }
    +            
    + + @see Org.BouncyCastle.Asn1.IsisMtt.X509.AdmissionSyntax +
    + + Rechtsanw�ltin + + + Rechtsanwalt + + + Rechtsbeistand + + + Steuerberaterin + + + Steuerberater + + + Steuerbevollm�chtigte + + + Steuerbevollm�chtigter + + + Notarin + + + Notar + + + Notarvertreterin + + + Notarvertreter + + + Notariatsverwalterin + + + Notariatsverwalter + + + Wirtschaftspr�ferin + + + Wirtschaftspr�fer + + + Vereidigte Buchpr�ferin + + + Vereidigter Buchpr�fer + + + Patentanw�ltin + + + Patentanwalt + + + Constructor from Asn1Sequence. +

    +

    +

    +                           ProfessionInfo ::= SEQUENCE
    +                           {
    +                             namingAuthority [0] EXPLICIT NamingAuthority OPTIONAL,
    +                             professionItems SEQUENCE OF DirectoryString (SIZE(1..128)),
    +                             professionOids SEQUENCE OF OBJECT IDENTIFIER OPTIONAL,
    +                             registrationNumber PrintableString(SIZE(1..128)) OPTIONAL,
    +                             addProfessionInfo OCTET STRING OPTIONAL
    +                           }
    +             
    + + @param seq The ASN.1 sequence. +
    + + Constructor from given details. +

    + professionItems is mandatory, all other parameters are + optional. + + @param namingAuthority The naming authority. + @param professionItems Directory strings of the profession. + @param professionOids DERObjectIdentfier objects for the + profession. + @param registrationNumber Registration number. + @param addProfessionInfo Additional infos in encoded form. + + + Produce an object suitable for an Asn1OutputStream. +

    + Returns: +

    +

    +                           ProfessionInfo ::= SEQUENCE
    +                           {
    +                             namingAuthority [0] EXPLICIT NamingAuthority OPTIONAL,
    +                             professionItems SEQUENCE OF DirectoryString (SIZE(1..128)),
    +                             professionOids SEQUENCE OF OBJECT IDENTIFIER OPTIONAL,
    +                             registrationNumber PrintableString(SIZE(1..128)) OPTIONAL,
    +                             addProfessionInfo OCTET STRING OPTIONAL
    +                           }
    +             
    + + @return an Asn1Object +
    + + @return Returns the professionItems. + + + @return Returns the professionOids. + + + @return Returns the addProfessionInfo. + + + @return Returns the namingAuthority. + + + @return Returns the registrationNumber. + + + Some other restriction regarding the usage of this certificate. +

    +

    +             RestrictionSyntax ::= DirectoryString (SIZE(1..1024))
    +            
    +
    + + Constructor from DirectoryString. +

    + The DirectoryString is of type RestrictionSyntax: +

    +

    +                  RestrictionSyntax ::= DirectoryString (SIZE(1..1024))
    +             
    + + @param restriction A IAsn1String. +
    + + Constructor from a given details. + + @param restriction The description of the restriction. + + + Produce an object suitable for an Asn1OutputStream. +

    + Returns: +

    +

    +                  RestrictionSyntax ::= DirectoryString (SIZE(1..1024))
    +             

    +

    + + @return an Asn1Object +
    + + Produce an object suitable for an Asn1OutputStream. +
    +            cast5CBCParameters ::= Sequence {
    +                                      iv         OCTET STRING DEFAULT 0,
    +                                             -- Initialization vector
    +                                      keyLength  Integer
    +                                             -- Key length, in bits
    +                                 }
    +            
    +
    + + Produce an object suitable for an Asn1OutputStream. +
    +            IDEA-CBCPar ::= Sequence {
    +                                 iv    OCTET STRING OPTIONAL -- exactly 8 octets
    +                             }
    +            
    +
    + + The NetscapeCertType object. +
    +               NetscapeCertType ::= BIT STRING {
    +                    SSLClient               (0),
    +                    SSLServer               (1),
    +                    S/MIME                  (2),
    +                    Object Signing          (3),
    +                    Reserved                (4),
    +                    SSL CA                  (5),
    +                    S/MIME CA               (6),
    +                    Object Signing CA       (7) }
    +            
    +
    + + Basic constructor. + + @param usage - the bitwise OR of the Key Usage flags giving the + allowed uses for the key. + e.g. (X509NetscapeCertType.sslCA | X509NetscapeCertType.smimeCA) + + + This is designed to parse + the PublicKeyAndChallenge created by the KEYGEN tag included by + Mozilla based browsers. +
    +              PublicKeyAndChallenge ::= SEQUENCE {
    +                spki SubjectPublicKeyInfo,
    +                challenge IA5STRING
    +              }
    +            
    +              
    +
    + + Utility class for fetching curves using their NIST names as published in FIPS-PUB 186-3 + + + return the X9ECParameters object for the named curve represented by + the passed in object identifier. Null if the curve isn't present. + + @param oid an object identifier representing a named curve, if present. + + + return the object identifier signified by the passed in name. Null + if there is no object identifier associated with name. + + @return the object identifier associated with name, if present. + + + return the named curve name represented by the given object identifier. + + + returns an enumeration containing the name strings for curves + contained in this structure. + + + From RFC 3657 + + + Produce an object suitable for an Asn1OutputStream. +
    +            BasicOcspResponse       ::= Sequence {
    +                 tbsResponseData      ResponseData,
    +                 signatureAlgorithm   AlgorithmIdentifier,
    +                 signature            BIT STRING,
    +                 certs                [0] EXPLICIT Sequence OF Certificate OPTIONAL }
    +            
    +
    + + Produce an object suitable for an Asn1OutputStream. +
    +            CertID          ::=     Sequence {
    +                hashAlgorithm       AlgorithmIdentifier,
    +                issuerNameHash      OCTET STRING, -- Hash of Issuer's DN
    +                issuerKeyHash       OCTET STRING, -- Hash of Issuers public key
    +                serialNumber        CertificateSerialNumber }
    +            
    +
    + + create a CertStatus object with a tag of zero. + + + Produce an object suitable for an Asn1OutputStream. +
    +             CertStatus ::= CHOICE {
    +                             good        [0]     IMPLICIT Null,
    +                             revoked     [1]     IMPLICIT RevokedInfo,
    +                             unknown     [2]     IMPLICIT UnknownInfo }
    +            
    +
    + + Produce an object suitable for an Asn1OutputStream. +
    +            CrlID ::= Sequence {
    +                crlUrl               [0]     EXPLICIT IA5String OPTIONAL,
    +                crlNum               [1]     EXPLICIT Integer OPTIONAL,
    +                crlTime              [2]     EXPLICIT GeneralizedTime OPTIONAL }
    +            
    +
    + + Produce an object suitable for an Asn1OutputStream. +
    +            OcspRequest     ::=     Sequence {
    +                tbsRequest                  TBSRequest,
    +                optionalSignature   [0]     EXPLICIT Signature OPTIONAL }
    +            
    +
    + + Produce an object suitable for an Asn1OutputStream. +
    +            OcspResponse ::= Sequence {
    +                responseStatus         OcspResponseStatus,
    +                responseBytes          [0] EXPLICIT ResponseBytes OPTIONAL }
    +            
    +
    + + The OcspResponseStatus enumeration. +
    +            OcspResponseStatus ::= Enumerated {
    +                successful            (0),  --Response has valid confirmations
    +                malformedRequest      (1),  --Illegal confirmation request
    +                internalError         (2),  --Internal error in issuer
    +                tryLater              (3),  --Try again later
    +                                            --(4) is not used
    +                sigRequired           (5),  --Must sign the request
    +                unauthorized          (6)   --Request unauthorized
    +            }
    +            
    +
    + + Produce an object suitable for an Asn1OutputStream. +
    +            Request         ::=     Sequence {
    +                reqCert                     CertID,
    +                singleRequestExtensions     [0] EXPLICIT Extensions OPTIONAL }
    +            
    +
    + + Produce an object suitable for an Asn1OutputStream. +
    +            ResponderID ::= CHOICE {
    +                 byName          [1] Name,
    +                 byKey           [2] KeyHash }
    +            
    +
    + + Produce an object suitable for an Asn1OutputStream. +
    +            ResponseBytes ::=       Sequence {
    +                responseType   OBJECT IDENTIFIER,
    +                response       OCTET STRING }
    +            
    +
    + + Produce an object suitable for an Asn1OutputStream. +
    +            ResponseData ::= Sequence {
    +                version              [0] EXPLICIT Version DEFAULT v1,
    +                responderID              ResponderID,
    +                producedAt               GeneralizedTime,
    +                responses                Sequence OF SingleResponse,
    +                responseExtensions   [1] EXPLICIT Extensions OPTIONAL }
    +            
    +
    + + Produce an object suitable for an Asn1OutputStream. +
    +            RevokedInfo ::= Sequence {
    +                 revocationTime              GeneralizedTime,
    +                 revocationReason    [0]     EXPLICIT CRLReason OPTIONAL }
    +            
    +
    + + Produce an object suitable for an Asn1OutputStream. +
    +            ServiceLocator ::= Sequence {
    +                issuer    Name,
    +                locator   AuthorityInfoAccessSyntax OPTIONAL }
    +            
    +
    + + Produce an object suitable for an Asn1OutputStream. +
    +            Signature       ::=     Sequence {
    +                signatureAlgorithm      AlgorithmIdentifier,
    +                signature               BIT STRING,
    +                certs               [0] EXPLICIT Sequence OF Certificate OPTIONAL}
    +            
    +
    + + Produce an object suitable for an Asn1OutputStream. +
    +             SingleResponse ::= Sequence {
    +                     certID                       CertID,
    +                     certStatus                   CertStatus,
    +                     thisUpdate                   GeneralizedTime,
    +                     nextUpdate         [0]       EXPLICIT GeneralizedTime OPTIONAL,
    +                     singleExtensions   [1]       EXPLICIT Extensions OPTIONAL }
    +            
    +
    + + Produce an object suitable for an Asn1OutputStream. +
    +            TBSRequest      ::=     Sequence {
    +                version             [0]     EXPLICIT Version DEFAULT v1,
    +                requestorName       [1]     EXPLICIT GeneralName OPTIONAL,
    +                requestList                 Sequence OF Request,
    +                requestExtensions   [2]     EXPLICIT Extensions OPTIONAL }
    +            
    +
    + + class for breaking up an Oid into it's component tokens, ala + java.util.StringTokenizer. We need this class as some of the + lightweight Java environment don't support classes like + StringTokenizer. + + + return an Attribute object from the given object. + + @param o the object we want converted. + @exception ArgumentException if the object cannot be converted. + + + Produce an object suitable for an Asn1OutputStream. +
    +            Attr ::= Sequence {
    +                attrType OBJECT IDENTIFIER,
    +                attrValues Set OF AttributeValue
    +            }
    +            
    +
    + + Pkcs10 Certfication request object. +
    +            CertificationRequest ::= Sequence {
    +              certificationRequestInfo  CertificationRequestInfo,
    +              signatureAlgorithm        AlgorithmIdentifier{{ SignatureAlgorithms }},
    +              signature                 BIT STRING
    +            }
    +            
    +
    + + Pkcs10 CertificationRequestInfo object. +
    +              CertificationRequestInfo ::= Sequence {
    +               version             Integer { v1(0) } (v1,...),
    +               subject             Name,
    +               subjectPKInfo   SubjectPublicKeyInfo{{ PKInfoAlgorithms }},
    +               attributes          [0] Attributes{{ CRIAttributes }}
    +              }
    +            
    +              Attributes { ATTRIBUTE:IOSet } ::= Set OF Attr{{ IOSet }}
    +            
    +              Attr { ATTRIBUTE:IOSet } ::= Sequence {
    +                type    ATTRIBUTE.&id({IOSet}),
    +                values  Set SIZE(1..MAX) OF ATTRIBUTE.&Type({IOSet}{\@type})
    +              }
    +             
    +
    + + Produce an object suitable for an Asn1OutputStream. +
    +            ContentInfo ::= Sequence {
    +                     contentType ContentType,
    +                     content
    +                     [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL }
    +            
    +
    + + The EncryptedData object. +
    +                  EncryptedData ::= Sequence {
    +                       version Version,
    +                       encryptedContentInfo EncryptedContentInfo
    +                  }
    +            
    +            
    +                  EncryptedContentInfo ::= Sequence {
    +                      contentType ContentType,
    +                      contentEncryptionAlgorithm  ContentEncryptionAlgorithmIdentifier,
    +                      encryptedContent [0] IMPLICIT EncryptedContent OPTIONAL
    +                }
    +            
    +                EncryptedContent ::= OCTET STRING
    +             
    +
    + + Produce an object suitable for an Asn1OutputStream. +
    +             EncryptedPrivateKeyInfo ::= Sequence {
    +                  encryptionAlgorithm AlgorithmIdentifier {{KeyEncryptionAlgorithms}},
    +                  encryptedData EncryptedData
    +             }
    +            
    +             EncryptedData ::= OCTET STRING
    +            
    +             KeyEncryptionAlgorithms ALGORITHM-IDENTIFIER ::= {
    +                      ... -- For local profiles
    +             }
    +             
    +
    + + Produce an object suitable for an Asn1OutputStream. +
    +                 AlgorithmIdentifier ::= Sequence {
    +                                       algorithm OBJECT IDENTIFIER,
    +                                       parameters ANY DEFINED BY algorithm OPTIONAL }
    +            
    +
    + + + Return the OID in the Algorithm entry of this identifier. + + + + + Return the parameters structure in the Parameters entry of this identifier. + + + +
    +            MacData ::= SEQUENCE {
    +                mac      DigestInfo,
    +                macSalt  OCTET STRING,
    +                iterations INTEGER DEFAULT 1
    +                -- Note: The default is for historic reasons and its use is deprecated. A
    +                -- higher value, like 1024 is recommended.
    +            
    + @return the basic DERObject construction. +
    + + the infamous Pfx from Pkcs12 + + + write out an RSA private key with its associated information + as described in Pkcs8. +
    +                  PrivateKeyInfo ::= Sequence {
    +                                          version Version,
    +                                          privateKeyAlgorithm AlgorithmIdentifier {{PrivateKeyAlgorithms}},
    +                                          privateKey PrivateKey,
    +                                          attributes [0] IMPLICIT Attributes OPTIONAL
    +                                      }
    +                  Version ::= Integer {v1(0)} (v1,...)
    +            
    +                  PrivateKey ::= OCTET STRING
    +            
    +                  Attributes ::= Set OF Attr
    +             
    +
    + + The default version + + +
    +              RSAES-OAEP-params ::= SEQUENCE {
    +                 hashAlgorithm      [0] OAEP-PSSDigestAlgorithms     DEFAULT sha1,
    +                 maskGenAlgorithm   [1] PKCS1MGFAlgorithms  DEFAULT mgf1SHA1,
    +                 pSourceAlgorithm   [2] PKCS1PSourceAlgorithms  DEFAULT pSpecifiedEmpty
    +               }
    +            
    +               OAEP-PSSDigestAlgorithms    ALGORITHM-IDENTIFIER ::= {
    +                 { OID id-sha1 PARAMETERS NULL   }|
    +                 { OID id-sha256 PARAMETERS NULL }|
    +                 { OID id-sha384 PARAMETERS NULL }|
    +                 { OID id-sha512 PARAMETERS NULL },
    +                 ...  -- Allows for future expansion --
    +               }
    +               PKCS1MGFAlgorithms    ALGORITHM-IDENTIFIER ::= {
    +                 { OID id-mgf1 PARAMETERS OAEP-PSSDigestAlgorithms },
    +                ...  -- Allows for future expansion --
    +               }
    +               PKCS1PSourceAlgorithms    ALGORITHM-IDENTIFIER ::= {
    +                 { OID id-pSpecified PARAMETERS OCTET STRING },
    +                 ...  -- Allows for future expansion --
    +              }
    +             
    + @return the asn1 primitive representing the parameters. +
    + + This outputs the key in Pkcs1v2 format. +
    +                  RsaPrivateKey ::= Sequence {
    +                                      version Version,
    +                                      modulus Integer, -- n
    +                                      publicExponent Integer, -- e
    +                                      privateExponent Integer, -- d
    +                                      prime1 Integer, -- p
    +                                      prime2 Integer, -- q
    +                                      exponent1 Integer, -- d mod (p-1)
    +                                      exponent2 Integer, -- d mod (q-1)
    +                                      coefficient Integer -- (inverse of q) mod p
    +                                  }
    +            
    +                  Version ::= Integer
    +             
    +

    This routine is written to output Pkcs1 version 0, private keys.

    +
    + + The default version + + +
    +             RSASSA-PSS-params ::= SEQUENCE {
    +               hashAlgorithm      [0] OAEP-PSSDigestAlgorithms  DEFAULT sha1,
    +                maskGenAlgorithm   [1] PKCS1MGFAlgorithms  DEFAULT mgf1SHA1,
    +                saltLength         [2] INTEGER  DEFAULT 20,
    +                trailerField       [3] TrailerField  DEFAULT trailerFieldBC
    +              }
    +            
    +             OAEP-PSSDigestAlgorithms    ALGORITHM-IDENTIFIER ::= {
    +                { OID id-sha1 PARAMETERS NULL   }|
    +                { OID id-sha256 PARAMETERS NULL }|
    +                { OID id-sha384 PARAMETERS NULL }|
    +                { OID id-sha512 PARAMETERS NULL },
    +                ...  -- Allows for future expansion --
    +             }
    +            
    +             PKCS1MGFAlgorithms    ALGORITHM-IDENTIFIER ::= {
    +               { OID id-mgf1 PARAMETERS OAEP-PSSDigestAlgorithms },
    +                ...  -- Allows for future expansion --
    +             }
    +            
    +             TrailerField ::= INTEGER { trailerFieldBC(1) }
    +             
    + @return the asn1 primitive representing the parameters. +
    + + a Pkcs#7 signed data object. + + + Produce an object suitable for an Asn1OutputStream. +
    +             SignedData ::= Sequence {
    +                 version Version,
    +                 digestAlgorithms DigestAlgorithmIdentifiers,
    +                 contentInfo ContentInfo,
    +                 certificates
    +                     [0] IMPLICIT ExtendedCertificatesAndCertificates
    +                              OPTIONAL,
    +                 crls
    +                     [1] IMPLICIT CertificateRevocationLists OPTIONAL,
    +                 signerInfos SignerInfos }
    +            
    +
    + + a Pkcs#7 signer info object. + + + Produce an object suitable for an Asn1OutputStream. +
    +              SignerInfo ::= Sequence {
    +                  version Version,
    +                  issuerAndSerialNumber IssuerAndSerialNumber,
    +                  digestAlgorithm DigestAlgorithmIdentifier,
    +                  authenticatedAttributes [0] IMPLICIT Attributes OPTIONAL,
    +                  digestEncryptionAlgorithm DigestEncryptionAlgorithmIdentifier,
    +                  encryptedDigest EncryptedDigest,
    +                  unauthenticatedAttributes [1] IMPLICIT Attributes OPTIONAL
    +              }
    +            
    +              EncryptedDigest ::= OCTET STRING
    +            
    +              DigestAlgorithmIdentifier ::= AlgorithmIdentifier
    +            
    +              DigestEncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
    +             
    +
    + + the elliptic curve private key object from SEC 1 + + + ECPrivateKey ::= SEQUENCE { + version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1), + privateKey OCTET STRING, + parameters [0] Parameters OPTIONAL, + publicKey [1] BIT STRING OPTIONAL } + + + return the X9ECParameters object for the named curve represented by + the passed in object identifier. Null if the curve isn't present. + + @param oid an object identifier representing a named curve, if present. + + + return the object identifier signified by the passed in name. Null + if there is no object identifier associated with name. + + @return the object identifier associated with name, if present. + + + return the named curve name represented by the given object identifier. + + + returns an enumeration containing the name strings for curves + contained in this structure. + + + EllipticCurve OBJECT IDENTIFIER ::= { + iso(1) identified-organization(3) certicom(132) curve(0) + } + + + Handler class for dealing with S/MIME Capabilities + + + general preferences + + + encryption algorithms preferences + + + return an Attr object from the given object. + + @param o the object we want converted. + @exception ArgumentException if the object cannot be converted. + + + returns an ArrayList with 0 or more objects of all the capabilities + matching the passed in capability Oid. If the Oid passed is null the + entire set is returned. + + + Produce an object suitable for an Asn1OutputStream. +
    +            SMIMECapabilities ::= Sequence OF SMIMECapability
    +            
    +
    + + return an Attr object from the given object. + + @param o the object we want converted. + @exception ArgumentException if the object cannot be converted. + + + Produce an object suitable for an Asn1OutputStream. +
    +            Attr ::= Sequence {
    +                attrType OBJECT IDENTIFIER,
    +                attrValues Set OF AttributeValue
    +            }
    +            
    +
    + + general preferences + + + encryption algorithms preferences + + + Produce an object suitable for an Asn1OutputStream. +
    +            SMIMECapability ::= Sequence {
    +                capabilityID OBJECT IDENTIFIER,
    +                parameters ANY DEFINED BY capabilityID OPTIONAL
    +            }
    +            
    +
    + + Handler for creating a vector S/MIME Capabilities + + + The SmimeEncryptionKeyPreference object. +
    +            SmimeEncryptionKeyPreference ::= CHOICE {
    +                issuerAndSerialNumber   [0] IssuerAndSerialNumber,
    +                receipentKeyId          [1] RecipientKeyIdentifier,
    +                subjectAltKeyIdentifier [2] SubjectKeyIdentifier
    +            }
    +            
    +
    + + @param sKeyId the subjectKeyIdentifier value (normally the X.509 one) + + + elliptic curves defined in "ECC Brainpool Standard Curves and Curve Generation" + http://www.ecc-brainpool.org/download/draft_pkix_additional_ecc_dp.txt + + + return the X9ECParameters object for the named curve represented by + the passed in object identifier. Null if the curve isn't present. + + @param oid an object identifier representing a named curve, if present. + + + return the object identifier signified by the passed in name. Null + if there is no object identifier associated with name. + + @return the object identifier associated with name, if present. + + + return the named curve name represented by the given object identifier. + + + returns an enumeration containing the name strings for curves + contained in this structure. + + +
    +            Accuracy ::= SEQUENCE {
    +                        seconds        INTEGER              OPTIONAL,
    +                        millis     [0] INTEGER  (1..999)    OPTIONAL,
    +                        micros     [1] INTEGER  (1..999)    OPTIONAL
    +                        }
    +            
    +
    + + @param o + @return a MessageImprint object. + + +
    +               MessageImprint ::= SEQUENCE  {
    +                  hashAlgorithm                AlgorithmIdentifier,
    +                  hashedMessage                OCTET STRING  }
    +            
    +
    + +
    +            TimeStampReq ::= SEQUENCE  {
    +             version                      INTEGER  { v1(1) },
    +             messageImprint               MessageImprint,
    +               --a hash algorithm OID and the hash value of the data to be
    +               --time-stamped
    +             reqPolicy             TSAPolicyId              OPTIONAL,
    +             nonce                 INTEGER                  OPTIONAL,
    +             certReq               BOOLEAN                  DEFAULT FALSE,
    +             extensions            [0] IMPLICIT Extensions  OPTIONAL
    +            }
    +            
    +
    + +
    +            TimeStampResp ::= SEQUENCE  {
    +              status                  PkiStatusInfo,
    +              timeStampToken          TimeStampToken     OPTIONAL  }
    +            
    +
    + +
    +            
    +                 TstInfo ::= SEQUENCE  {
    +                    version                      INTEGER  { v1(1) },
    +                    policy                       TSAPolicyId,
    +                    messageImprint               MessageImprint,
    +                      -- MUST have the same value as the similar field in
    +                      -- TimeStampReq
    +                    serialNumber                 INTEGER,
    +                     -- Time-Stamping users MUST be ready to accommodate integers
    +                     -- up to 160 bits.
    +                    genTime                      GeneralizedTime,
    +                    accuracy                     Accuracy                 OPTIONAL,
    +                    ordering                     BOOLEAN             DEFAULT FALSE,
    +                    nonce                        INTEGER                  OPTIONAL,
    +                      -- MUST be present if the similar field was present
    +                      -- in TimeStampReq.  In that case it MUST have the same value.
    +                    tsa                          [0] GeneralName          OPTIONAL,
    +                    extensions                   [1] IMPLICIT Extensions   OPTIONAL  }
    +            
    +             
    +
    + + dump a Der object as a formatted string with indentation + + @param obj the Asn1Object to be dumped out. + + + dump out a DER object as a formatted string, in non-verbose mode + + @param obj the Asn1Encodable to be dumped out. + @return the resulting string. + + + Dump out the object as a string + + @param obj the Asn1Encodable to be dumped out. + @param verbose if true, dump out the contents of octet and bit strings. + @return the resulting string. + + +
    +             DirectoryString ::= CHOICE {
    +               teletexString               TeletexString (SIZE (1..MAX)),
    +               printableString             PrintableString (SIZE (1..MAX)),
    +               universalString             UniversalString (SIZE (1..MAX)),
    +               utf8String                  UTF8String (SIZE (1..MAX)),
    +               bmpString                   BMPString (SIZE (1..MAX))  }
    +            
    +
    + + The AccessDescription object. +
    +            AccessDescription  ::=  SEQUENCE {
    +                  accessMethod          OBJECT IDENTIFIER,
    +                  accessLocation        GeneralName  }
    +            
    +
    + + create an AccessDescription with the oid and location provided. + + + + @return the access method. + + + + @return the access location + + + + Don't use this one if you are trying to be RFC 3281 compliant. + Use it for v1 attribute certificates only. + + Our GeneralNames structure + + + Produce an object suitable for an Asn1OutputStream. +
    +             AttCertIssuer ::= CHOICE {
    +                  v1Form   GeneralNames,  -- MUST NOT be used in this
    +                                          -- profile
    +                  v2Form   [0] V2Form     -- v2 only
    +             }
    +            
    +
    + + Produce an object suitable for an Asn1OutputStream. +
    +             AttCertValidityPeriod  ::= Sequence {
    +                  notBeforeTime  GeneralizedTime,
    +                  notAfterTime   GeneralizedTime
    +             }
    +            
    +
    + + @param obj + @return + + + Produce an object suitable for an Asn1OutputStream. +
    +             AttributeCertificate ::= Sequence {
    +                  acinfo               AttributeCertificateInfo,
    +                  signatureAlgorithm   AlgorithmIdentifier,
    +                  signatureValue       BIT STRING
    +             }
    +            
    +
    + + Produce an object suitable for an Asn1OutputStream. +
    +              AttributeCertificateInfo ::= Sequence {
    +                   version              AttCertVersion -- version is v2,
    +                   holder               Holder,
    +                   issuer               AttCertIssuer,
    +                   signature            AlgorithmIdentifier,
    +                   serialNumber         CertificateSerialNumber,
    +                   attrCertValidityPeriod   AttCertValidityPeriod,
    +                   attributes           Sequence OF Attr,
    +                   issuerUniqueID       UniqueIdentifier OPTIONAL,
    +                   extensions           Extensions OPTIONAL
    +              }
    +            
    +              AttCertVersion ::= Integer { v2(1) }
    +             
    +
    + + The AuthorityInformationAccess object. +
    +             id-pe-authorityInfoAccess OBJECT IDENTIFIER ::= { id-pe 1 }
    +            
    +             AuthorityInfoAccessSyntax  ::=
    +                  Sequence SIZE (1..MAX) OF AccessDescription
    +             AccessDescription  ::=  Sequence {
    +                   accessMethod          OBJECT IDENTIFIER,
    +                   accessLocation        GeneralName  }
    +            
    +             id-ad OBJECT IDENTIFIER ::= { id-pkix 48 }
    +             id-ad-caIssuers OBJECT IDENTIFIER ::= { id-ad 2 }
    +             id-ad-ocsp OBJECT IDENTIFIER ::= { id-ad 1 }
    +             
    +
    + + create an AuthorityInformationAccess with the oid and location provided. + + + The AuthorityKeyIdentifier object. +
    +             id-ce-authorityKeyIdentifier OBJECT IDENTIFIER ::=  { id-ce 35 }
    +            
    +               AuthorityKeyIdentifier ::= Sequence {
    +                  keyIdentifier             [0] IMPLICIT KeyIdentifier           OPTIONAL,
    +                  authorityCertIssuer       [1] IMPLICIT GeneralNames            OPTIONAL,
    +                  authorityCertSerialNumber [2] IMPLICIT CertificateSerialNumber OPTIONAL  }
    +            
    +               KeyIdentifier ::= OCTET STRING
    +             
    + +
    + + * + * Calulates the keyidentifier using a SHA1 hash over the BIT STRING + * from SubjectPublicKeyInfo as defined in RFC2459. + * + * Example of making a AuthorityKeyIdentifier: + *
    +            	     *   SubjectPublicKeyInfo apki = new SubjectPublicKeyInfo((ASN1Sequence)new ASN1InputStream(
    +            		 *       publicKey.getEncoded()).readObject());
    +                     *   AuthorityKeyIdentifier aki = new AuthorityKeyIdentifier(apki);
    +                     * 
    + * + * +
    + + create an AuthorityKeyIdentifier with the GeneralNames tag and + the serial number provided as well. + + + create an AuthorityKeyIdentifier with the GeneralNames tag and + the serial number provided. + + + create an AuthorityKeyIdentifier with a precomputed key identifier + + + create an AuthorityKeyIdentifier with a precomupted key identifier + and the GeneralNames tag and the serial number provided as well. + + + Produce an object suitable for an Asn1OutputStream. + + + create a cA=true object for the given path length constraint. + + @param pathLenConstraint + + + Produce an object suitable for an Asn1OutputStream. +
    +            BasicConstraints := Sequence {
    +               cA                  Boolean DEFAULT FALSE,
    +               pathLenConstraint   Integer (0..MAX) OPTIONAL
    +            }
    +            
    +
    + + PKIX RFC-2459 + + The X.509 v2 CRL syntax is as follows. For signature calculation, + the data that is to be signed is ASN.1 Der encoded. + +
    +             CertificateList  ::=  Sequence  {
    +                  tbsCertList          TbsCertList,
    +                  signatureAlgorithm   AlgorithmIdentifier,
    +                  signatureValue       BIT STRING  }
    +             
    +
    + + This class helps to support crossCerfificatePairs in a LDAP directory + according RFC 2587 + +
    +                 crossCertificatePairATTRIBUTE::={
    +                   WITH SYNTAX   CertificatePair
    +                   EQUALITY MATCHING RULE certificatePairExactMatch
    +                   ID joint-iso-ccitt(2) ds(5) attributeType(4) crossCertificatePair(40)}
    +             
    + +
    The forward elements of the crossCertificatePair attribute of a + CA's directory entry shall be used to store all, except self-issued + certificates issued to this CA. Optionally, the reverse elements of the + crossCertificatePair attribute, of a CA's directory entry may contain a + subset of certificates issued by this CA to other CAs. When both the forward + and the reverse elements are present in a single attribute value, issuer name + in one certificate shall match the subject name in the other and vice versa, + and the subject public key in one certificate shall be capable of verifying + the digital signature on the other certificate and vice versa. + + When a reverse element is present, the forward element value and the reverse + element value need not be stored in the same attribute value; in other words, + they can be stored in either a single attribute value or two attribute + values.
    + +
    +                   CertificatePair ::= SEQUENCE {
    +                     forward		[0]	Certificate OPTIONAL,
    +                     reverse		[1]	Certificate OPTIONAL,
    +                     -- at least one of the pair shall be present -- }
    +             
    +
    + + Constructor from Asn1Sequence. +

    + The sequence is of type CertificatePair: +

    +

    +                   CertificatePair ::= SEQUENCE {
    +                     forward		[0]	Certificate OPTIONAL,
    +                     reverse		[1]	Certificate OPTIONAL,
    +                     -- at least one of the pair shall be present -- }
    +             
    + + @param seq The ASN.1 sequence. +
    + + Constructor from a given details. + + @param forward Certificates issued to this CA. + @param reverse Certificates issued by this CA to other CAs. + + + Produce an object suitable for an Asn1OutputStream. +

    + Returns: +

    +

    +                   CertificatePair ::= SEQUENCE {
    +                     forward		[0]	Certificate OPTIONAL,
    +                     reverse		[1]	Certificate OPTIONAL,
    +                     -- at least one of the pair shall be present -- }
    +             
    + + @return a DERObject +
    + + @return Returns the forward. + + + @return Returns the reverse. + + + Construct a CertificatePolicies object containing one PolicyInformation. + + @param name the name to be contained. + + + Produce an object suitable for an ASN1OutputStream. +
    +            CertificatePolicies ::= SEQUENCE SIZE {1..MAX} OF PolicyInformation
    +            
    +
    + + CertPolicyId, used in the CertificatePolicies and PolicyMappings + X509V3 Extensions. + +
    +                 CertPolicyId ::= OBJECT IDENTIFIER
    +             
    +
    + + Return the distribution points making up the sequence. + + @return DistributionPoint[] + + + Produce an object suitable for an Asn1OutputStream. +
    +            CrlDistPoint ::= Sequence SIZE {1..MAX} OF DistributionPoint
    +            
    +
    + + The CRLNumber object. +
    +            CRLNumber::= Integer(0..MAX)
    +            
    +
    + + The CRLReason enumeration. +
    +            CRLReason ::= Enumerated {
    +             unspecified             (0),
    +             keyCompromise           (1),
    +             cACompromise            (2),
    +             affiliationChanged      (3),
    +             superseded              (4),
    +             cessationOfOperation    (5),
    +             certificateHold         (6),
    +             removeFromCRL           (8),
    +             privilegeWithdrawn      (9),
    +             aACompromise           (10)
    +            }
    +            
    +
    + + The DigestInfo object. +
    +            DigestInfo::=Sequence{
    +                     digestAlgorithm  AlgorithmIdentifier,
    +                     digest OCTET STRING }
    +            
    +
    + + DisplayText class, used in + CertificatePolicies X509 V3 extensions (in policy qualifiers). + +

    It stores a string in a chosen encoding. +

    +             DisplayText ::= CHOICE {
    +                  ia5String        IA5String      (SIZE (1..200)),
    +                  visibleString    VisibleString  (SIZE (1..200)),
    +                  bmpString        BMPString      (SIZE (1..200)),
    +                  utf8String       UTF8String     (SIZE (1..200)) }
    +             

    + @see PolicyQualifierInfo + @see PolicyInformation +
    + + Constant corresponding to ia5String encoding. + + + + Constant corresponding to bmpString encoding. + + + + Constant corresponding to utf8String encoding. + + + + Constant corresponding to visibleString encoding. + + + + Describe constant DisplayTextMaximumSize here. + + + + Creates a new DisplayText instance. + + @param type the desired encoding type for the text. + @param text the text to store. Strings longer than 200 + characters are truncated. + + + Creates a new DisplayText instance. + + @param text the text to encapsulate. Strings longer than 200 + characters are truncated. + + + Creates a new DisplayText instance. +

    Useful when reading back a DisplayText class + from it's Asn1Encodable form.

    + + @param contents an Asn1Encodable instance. +
    + + Returns the stored string object. + + @return the stored text as a string. + + + The DistributionPoint object. +
    +            DistributionPoint ::= Sequence {
    +                 distributionPoint [0] DistributionPointName OPTIONAL,
    +                 reasons           [1] ReasonFlags OPTIONAL,
    +                 cRLIssuer         [2] GeneralNames OPTIONAL
    +            }
    +            
    +
    + + The DistributionPointName object. +
    +            DistributionPointName ::= CHOICE {
    +                fullName                 [0] GeneralNames,
    +                nameRelativeToCRLIssuer  [1] RDN
    +            }
    +            
    +
    + + The extendedKeyUsage object. +
    +                 extendedKeyUsage ::= Sequence SIZE (1..MAX) OF KeyPurposeId
    +            
    +
    + + Returns all extended key usages. + The returned ArrayList contains DerObjectIdentifier instances. + @return An ArrayList with all key purposes. + + + The GeneralName object. +
    +             GeneralName ::= CHOICE {
    +                  otherName                       [0]     OtherName,
    +                  rfc822Name                      [1]     IA5String,
    +                  dNSName                         [2]     IA5String,
    +                  x400Address                     [3]     ORAddress,
    +                  directoryName                   [4]     Name,
    +                  ediPartyName                    [5]     EDIPartyName,
    +                  uniformResourceIdentifier       [6]     IA5String,
    +                  iPAddress                       [7]     OCTET STRING,
    +                  registeredID                    [8]     OBJECT IDENTIFIER}
    +            
    +             OtherName ::= Sequence {
    +                  type-id    OBJECT IDENTIFIER,
    +                  value      [0] EXPLICIT ANY DEFINED BY type-id }
    +            
    +             EDIPartyName ::= Sequence {
    +                  nameAssigner            [0]     DirectoryString OPTIONAL,
    +                  partyName               [1]     DirectoryString }
    +             
    +
    + + When the subjectAltName extension contains an Internet mail address, + the address MUST be included as an rfc822Name. The format of an + rfc822Name is an "addr-spec" as defined in RFC 822 [RFC 822]. + + When the subjectAltName extension contains a domain name service + label, the domain name MUST be stored in the dNSName (an IA5String). + The name MUST be in the "preferred name syntax," as specified by RFC + 1034 [RFC 1034]. + + When the subjectAltName extension contains a URI, the name MUST be + stored in the uniformResourceIdentifier (an IA5String). The name MUST + be a non-relative URL, and MUST follow the URL syntax and encoding + rules specified in [RFC 1738]. The name must include both a scheme + (e.g., "http" or "ftp") and a scheme-specific-part. The scheme- + specific-part must include a fully qualified domain name or IP + address as the host. + + When the subjectAltName extension contains a iPAddress, the address + MUST be stored in the octet string in "network byte order," as + specified in RFC 791 [RFC 791]. The least significant bit (LSB) of + each octet is the LSB of the corresponding byte in the network + address. For IP Version 4, as specified in RFC 791, the octet string + MUST contain exactly four octets. For IP Version 6, as specified in + RFC 1883, the octet string MUST contain exactly sixteen octets [RFC + 1883]. + + + Create a GeneralName for the given tag from the passed in string. +

    + This constructor can handle: +

      +
    • rfc822Name
    • +
    • iPAddress
    • +
    • directoryName
    • +
    • dNSName
    • +
    • uniformResourceIdentifier
    • +
    • registeredID
    • +
    + For x400Address, otherName and ediPartyName there is no common string + format defined. +

    + Note: A directory name can be encoded in different ways into a byte + representation. Be aware of this if the byte representation is used for + comparing results. +

    + + @param tag tag number + @param name string representation of name + @throws ArgumentException if the string encoding is not correct or + not supported. +
    + + Construct a GeneralNames object containing one GeneralName. + The name to be contained. + + + Produce an object suitable for an Asn1OutputStream. +
    +            GeneralNames ::= Sequence SIZE {1..MAX} OF GeneralName
    +            
    +
    + + Class for containing a restriction object subtrees in NameConstraints. See + RFC 3280. + +
    +            
    +                   GeneralSubtree ::= SEQUENCE
    +                   {
    +                     baseName                    GeneralName,
    +                     minimum         [0]     BaseDistance DEFAULT 0,
    +                     maximum         [1]     BaseDistance OPTIONAL
    +                   }
    +             
    + + @see org.bouncycastle.asn1.x509.NameConstraints + +
    + + Constructor from a given details. + + According RFC 3280, the minimum and maximum fields are not used with any + name forms, thus minimum MUST be zero, and maximum MUST be absent. +

    + If minimum is null, zero is assumed, if + maximum is null, maximum is absent.

    + + @param baseName + A restriction. + @param minimum + Minimum + + @param maximum + Maximum +
    + + Produce an object suitable for an Asn1OutputStream. + + Returns: + +
    +                   GeneralSubtree ::= SEQUENCE
    +                   {
    +                     baseName                    GeneralName,
    +                     minimum         [0]     BaseDistance DEFAULT 0,
    +                     maximum         [1]     BaseDistance OPTIONAL
    +                   }
    +             
    + + @return a DERObject +
    + + The Holder object. +

    + For an v2 attribute certificate this is: + +

    +                       Holder ::= SEQUENCE {
    +                             baseCertificateID   [0] IssuerSerial OPTIONAL,
    +                                      -- the issuer and serial number of
    +                                      -- the holder's Public Key Certificate
    +                             entityName          [1] GeneralNames OPTIONAL,
    +                                      -- the name of the claimant or role
    +                             objectDigestInfo    [2] ObjectDigestInfo OPTIONAL
    +                                      -- used to directly authenticate the holder,
    +                                      -- for example, an executable
    +                       }
    +            
    +

    +

    + For an v1 attribute certificate this is: + +

    +                    subject CHOICE {
    +                     baseCertificateID [0] IssuerSerial,
    +                     -- associated with a Public Key Certificate
    +                     subjectName [1] GeneralNames },
    +                     -- associated with a name
    +            
    +

    +
    + + Constructor for a holder for an v1 attribute certificate. + + @param tagObj The ASN.1 tagged holder object. + + + Constructor for a holder for an v2 attribute certificate. * + + @param seq The ASN.1 sequence. + + + Constructs a holder from a IssuerSerial. + @param baseCertificateID The IssuerSerial. + @param version The version of the attribute certificate. + + + Constructs a holder with an entityName for v2 attribute certificates or + with a subjectName for v1 attribute certificates. + + @param entityName The entity or subject name. + + + Constructs a holder with an entityName for v2 attribute certificates or + with a subjectName for v1 attribute certificates. + + @param entityName The entity or subject name. + @param version The version of the attribute certificate. + + + Constructs a holder from an object digest info. + + @param objectDigestInfo The object digest info object. + + + The Holder object. +
    +             Holder ::= Sequence {
    +                   baseCertificateID   [0] IssuerSerial OPTIONAL,
    +                            -- the issuer and serial number of
    +                            -- the holder's Public Key Certificate
    +                   entityName          [1] GeneralNames OPTIONAL,
    +                            -- the name of the claimant or role
    +                   objectDigestInfo    [2] ObjectDigestInfo OPTIONAL
    +                            -- used to directly authenticate the holder,
    +                            -- for example, an executable
    +             }
    +            
    +
    + + Returns 1 for v2 attribute certificates or 0 for v1 attribute + certificates. + @return The version of the attribute certificate. + + + Returns the entityName for an v2 attribute certificate or the subjectName + for an v1 attribute certificate. + + @return The entityname or subjectname. + + + Implementation of IetfAttrSyntax as specified by RFC3281. + + + + + + +
    +            
    +              IetfAttrSyntax ::= Sequence {
    +                policyAuthority [0] GeneralNames OPTIONAL,
    +                values Sequence OF CHOICE {
    +                  octets OCTET STRING,
    +                  oid OBJECT IDENTIFIER,
    +                  string UTF8String
    +                }
    +              }
    +            
    +             
    +
    + + Produce an object suitable for an Asn1OutputStream. +
    +             IssuerSerial  ::=  Sequence {
    +                  issuer         GeneralNames,
    +                  serial         CertificateSerialNumber,
    +                  issuerUid      UniqueIdentifier OPTIONAL
    +             }
    +            
    +
    + +
    +            IssuingDistributionPoint ::= SEQUENCE { 
    +              distributionPoint          [0] DistributionPointName OPTIONAL, 
    +              onlyContainsUserCerts      [1] BOOLEAN DEFAULT FALSE, 
    +              onlyContainsCACerts        [2] BOOLEAN DEFAULT FALSE, 
    +              onlySomeReasons            [3] ReasonFlags OPTIONAL, 
    +              indirectCRL                [4] BOOLEAN DEFAULT FALSE,
    +              onlyContainsAttributeCerts [5] BOOLEAN DEFAULT FALSE }
    +            
    +
    + + Constructor from given details. + + @param distributionPoint + May contain an URI as pointer to most current CRL. + @param onlyContainsUserCerts Covers revocation information for end certificates. + @param onlyContainsCACerts Covers revocation information for CA certificates. + + @param onlySomeReasons + Which revocation reasons does this point cover. + @param indirectCRL + If true then the CRL contains revocation + information about certificates ssued by other CAs. + @param onlyContainsAttributeCerts Covers revocation information for attribute certificates. + + + Constructor from Asn1Sequence + + + @return Returns the distributionPoint. + + + @return Returns the onlySomeReasons. + + + The KeyPurposeID object. +
    +                KeyPurposeID ::= OBJECT IDENTIFIER
    +            
    +
    + + The KeyUsage object. +
    +                id-ce-keyUsage OBJECT IDENTIFIER ::=  { id-ce 15 }
    +            
    +                KeyUsage ::= BIT STRING {
    +                     digitalSignature        (0),
    +                     nonRepudiation          (1),
    +                     keyEncipherment         (2),
    +                     dataEncipherment        (3),
    +                     keyAgreement            (4),
    +                     keyCertSign             (5),
    +                     cRLSign                 (6),
    +                     encipherOnly            (7),
    +                     decipherOnly            (8) }
    +             
    +
    + + Basic constructor. + + @param usage - the bitwise OR of the Key Usage flags giving the + allowed uses for the key. + e.g. (KeyUsage.keyEncipherment | KeyUsage.dataEncipherment) + + + Constructor from a given details. + +

    permitted and excluded are Vectors of GeneralSubtree objects.

    + + @param permitted Permitted subtrees + @param excluded Excluded subtrees +
    + + NoticeReference class, used in + CertificatePolicies X509 V3 extensions + (in policy qualifiers). + +
    +              NoticeReference ::= Sequence {
    +                  organization     DisplayText,
    +                  noticeNumbers    Sequence OF Integer }
    +            
    +             
    + + @see PolicyQualifierInfo + @see PolicyInformation +
    + + Creates a new NoticeReference instance. + + @param organization a String value + @param numbers a Vector value + + + Creates a new NoticeReference instance. + + @param organization a String value + @param noticeNumbers an ASN1EncodableVector value + + + Creates a new NoticeReference instance. + + @param organization displayText + @param noticeNumbers an ASN1EncodableVector value + + + Creates a new NoticeReference instance. +

    Useful for reconstructing a NoticeReference + instance from its encodable/encoded form.

    + + @param as an Asn1Sequence value obtained from either + calling @{link ToAsn1Object()} for a NoticeReference + instance or from parsing it from a Der-encoded stream. +
    + + Describe ToAsn1Object method here. + + @return a Asn1Object value + + + ObjectDigestInfo ASN.1 structure used in v2 attribute certificates. + +
    +             
    +               ObjectDigestInfo ::= SEQUENCE {
    +                    digestedObjectType  ENUMERATED {
    +                            publicKey            (0),
    +                            publicKeyCert        (1),
    +                            otherObjectTypes     (2) },
    +                                    -- otherObjectTypes MUST NOT
    +                                    -- be used in this profile
    +                    otherObjectTypeID   OBJECT IDENTIFIER OPTIONAL,
    +                    digestAlgorithm     AlgorithmIdentifier,
    +                    objectDigest        BIT STRING
    +               }
    +              
    +            
    + +
    + + The public key is hashed. + + + The public key certificate is hashed. + + + An other object is hashed. + + + Constructor from given details. +

    + If digestedObjectType is not {@link #publicKeyCert} or + {@link #publicKey} otherObjectTypeID must be given, + otherwise it is ignored.

    + + @param digestedObjectType The digest object type. + @param otherObjectTypeID The object type ID for + otherObjectDigest. + @param digestAlgorithm The algorithm identifier for the hash. + @param objectDigest The hash value. +
    + + Produce an object suitable for an Asn1OutputStream. + +
    +             
    +               ObjectDigestInfo ::= SEQUENCE {
    +                    digestedObjectType  ENUMERATED {
    +                            publicKey            (0),
    +                            publicKeyCert        (1),
    +                            otherObjectTypes     (2) },
    +                                    -- otherObjectTypes MUST NOT
    +                                    -- be used in this profile
    +                    otherObjectTypeID   OBJECT IDENTIFIER OPTIONAL,
    +                    digestAlgorithm     AlgorithmIdentifier,
    +                    objectDigest        BIT STRING
    +               }
    +              
    +            
    +
    + + PolicyMappings V3 extension, described in RFC3280. +
    +                PolicyMappings ::= Sequence SIZE (1..MAX) OF Sequence {
    +                  issuerDomainPolicy      CertPolicyId,
    +                  subjectDomainPolicy     CertPolicyId }
    +             
    + + @see
    RFC 3280, section 4.2.1.6 + + + Creates a new PolicyMappings instance. + + @param seq an Asn1Sequence constructed as specified + in RFC 3280 + + + Creates a new PolicyMappings instance. + + @param mappings a HashMap value that maps + string oids + to other string oids. + + + PolicyQualifierId, used in the CertificatePolicies + X509V3 extension. + +
    +                id-qt          OBJECT IDENTIFIER ::=  { id-pkix 2 }
    +                id-qt-cps      OBJECT IDENTIFIER ::=  { id-qt 1 }
    +                id-qt-unotice  OBJECT IDENTIFIER ::=  { id-qt 2 }
    +              PolicyQualifierId ::=
    +                   OBJECT IDENTIFIER ( id-qt-cps | id-qt-unotice )
    +             
    +
    + + Policy qualifiers, used in the X509V3 CertificatePolicies + extension. + +
    +               PolicyQualifierInfo ::= Sequence {
    +                   policyQualifierId  PolicyQualifierId,
    +                   qualifier          ANY DEFINED BY policyQualifierId }
    +             
    +
    + + Creates a new PolicyQualifierInfo instance. + + @param policyQualifierId a PolicyQualifierId value + @param qualifier the qualifier, defined by the above field. + + + Creates a new PolicyQualifierInfo containing a + cPSuri qualifier. + + @param cps the CPS (certification practice statement) uri as a + string. + + + Creates a new PolicyQualifierInfo instance. + + @param as PolicyQualifierInfo X509 structure + encoded as an Asn1Sequence. + + + Returns a Der-encodable representation of this instance. + + @return a Asn1Object value + + + +
    +            PrivateKeyUsagePeriod ::= SEQUENCE
    +            {
    +            notBefore       [0]     GeneralizedTime OPTIONAL,
    +            notAfter        [1]     GeneralizedTime OPTIONAL }
    +            
    +
    +
    + + The BiometricData object. +
    +            BiometricData  ::=  SEQUENCE {
    +                  typeOfBiometricData  TypeOfBiometricData,
    +                  hashAlgorithm        AlgorithmIdentifier,
    +                  biometricDataHash    OCTET STRING,
    +                  sourceDataUri        IA5String OPTIONAL  }
    +            
    +
    + + The Iso4217CurrencyCode object. +
    +            Iso4217CurrencyCode  ::=  CHOICE {
    +                  alphabetic              PrintableString (SIZE 3), --Recommended
    +                  numeric              INTEGER (1..999) }
    +            -- Alphabetic or numeric currency code as defined in ISO 4217
    +            -- It is recommended that the Alphabetic form is used
    +            
    +
    + + The MonetaryValue object. +
    +            MonetaryValue  ::=  SEQUENCE {
    +                  currency              Iso4217CurrencyCode,
    +                  amount               INTEGER,
    +                  exponent             INTEGER }
    +            -- value = amount * 10^exponent
    +            
    +
    + + The QCStatement object. +
    +            QCStatement ::= SEQUENCE {
    +              statementId        OBJECT IDENTIFIER,
    +              statementInfo      ANY DEFINED BY statementId OPTIONAL}
    +            
    +
    + + The SemanticsInformation object. +
    +                   SemanticsInformation ::= SEQUENCE {
    +                     semanticsIdentifier        OBJECT IDENTIFIER   OPTIONAL,
    +                     nameRegistrationAuthorities NameRegistrationAuthorities
    +                                                                     OPTIONAL }
    +                     (WITH COMPONENTS {..., semanticsIdentifier PRESENT}|
    +                      WITH COMPONENTS {..., nameRegistrationAuthorities PRESENT})
    +            
    +                 NameRegistrationAuthorities ::=  SEQUENCE SIZE (1..MAX) OF
    +                     GeneralName
    +             
    +
    + + The TypeOfBiometricData object. +
    +             TypeOfBiometricData ::= CHOICE {
    +               predefinedBiometricType   PredefinedBiometricType,
    +               biometricDataOid          OBJECT IDENTIFIER }
    +            
    +             PredefinedBiometricType ::= INTEGER {
    +               picture(0),handwritten-signature(1)}
    +               (picture|handwritten-signature)
    +             
    +
    + + The ReasonFlags object. +
    +            ReasonFlags ::= BIT STRING {
    +               unused(0),
    +               keyCompromise(1),
    +               cACompromise(2),
    +               affiliationChanged(3),
    +               superseded(4),
    +               cessationOfOperation(5),
    +               certficateHold(6)
    +            }
    +            
    +
    + + @param reasons - the bitwise OR of the Key Reason flags giving the + allowed uses for the key. + + + Implementation of the RoleSyntax object as specified by the RFC3281. + +
    +             RoleSyntax ::= SEQUENCE {
    +                             roleAuthority  [0] GeneralNames OPTIONAL,
    +                             roleName       [1] GeneralName
    +                       }
    +             
    +
    + + RoleSyntax factory method. + @param obj the object used to construct an instance of + RoleSyntax. It must be an instance of RoleSyntax + or Asn1Sequence. + @return the instance of RoleSyntax built from the + supplied object. + @throws java.lang.ArgumentException if the object passed + to the factory is not an instance of RoleSyntax or + Asn1Sequence. + + + Constructor. + @param roleAuthority the role authority of this RoleSyntax. + @param roleName the role name of this RoleSyntax. + + + Constructor. Invoking this constructor is the same as invoking + new RoleSyntax(null, roleName). + @param roleName the role name of this RoleSyntax. + + + Utility constructor. Takes a string argument representing + the role name, builds a GeneralName to hold the role name + and calls the constructor that takes a GeneralName. + @param roleName + + + Constructor that builds an instance of RoleSyntax by + extracting the encoded elements from the Asn1Sequence + object supplied. + @param seq an instance of Asn1Sequence that holds + the encoded elements used to build this RoleSyntax. + + + Gets the role name as a java.lang.string object. + @return the role name of this RoleSyntax represented as a + string object. + + + Gets the role authority as a string[] object. + @return the role authority of this RoleSyntax represented as a + string[] array. + + + Implementation of the method ToAsn1Object as + required by the superclass ASN1Encodable. + +
    +             RoleSyntax ::= SEQUENCE {
    +                             roleAuthority  [0] GeneralNames OPTIONAL,
    +                             roleName       [1] GeneralName
    +                       }
    +             
    +
    + + Gets the role authority of this RoleSyntax. + @return an instance of GeneralNames holding the + role authority of this RoleSyntax. + + + Gets the role name of this RoleSyntax. + @return an instance of GeneralName holding the + role name of this RoleSyntax. + + + This outputs the key in Pkcs1v2 format. +
    +                 RSAPublicKey ::= Sequence {
    +                                     modulus Integer, -- n
    +                                     publicExponent Integer, -- e
    +                                 }
    +            
    +
    + + Structure for a name or pseudonym. + +
    +                  NameOrPseudonym ::= CHOICE {
    +                	   surAndGivenName SEQUENCE {
    +                	     surName DirectoryString,
    +                	     givenName SEQUENCE OF DirectoryString 
    +                    },
    +                	   pseudonym DirectoryString 
    +                  }
    +            
    + + @see org.bouncycastle.asn1.x509.sigi.PersonalData + +
    + + Constructor from DERString. +

    + The sequence is of type NameOrPseudonym: +

    +

    +                  NameOrPseudonym ::= CHOICE {
    +                	   surAndGivenName SEQUENCE {
    +                	     surName DirectoryString,
    +                	     givenName SEQUENCE OF DirectoryString
    +                    },
    +                	   pseudonym DirectoryString
    +                  }
    +            
    + @param pseudonym pseudonym value to use. +
    + + Constructor from Asn1Sequence. +

    + The sequence is of type NameOrPseudonym: +

    +

    +                   NameOrPseudonym ::= CHOICE {
    +                 	   surAndGivenName SEQUENCE {
    +                 	     surName DirectoryString,
    +                 	     givenName SEQUENCE OF DirectoryString
    +                     },
    +                 	   pseudonym DirectoryString
    +                   }
    +             
    + + @param seq The ASN.1 sequence. +
    + + Constructor from a given details. + + @param pseudonym The pseudonym. + + + Constructor from a given details. + + @param surname The surname. + @param givenName A sequence of directory strings making up the givenName + + + Produce an object suitable for an Asn1OutputStream. +

    + Returns: +

    +

    +                   NameOrPseudonym ::= CHOICE {
    +                 	   surAndGivenName SEQUENCE {
    +                 	     surName DirectoryString,
    +                 	     givenName SEQUENCE OF DirectoryString
    +                     },
    +                 	   pseudonym DirectoryString
    +                   }
    +             
    + + @return an Asn1Object +
    + + Contains personal data for the otherName field in the subjectAltNames + extension. +

    +

    +                 PersonalData ::= SEQUENCE {
    +                   nameOrPseudonym NameOrPseudonym,
    +                   nameDistinguisher [0] INTEGER OPTIONAL,
    +                   dateOfBirth [1] GeneralizedTime OPTIONAL,
    +                   placeOfBirth [2] DirectoryString OPTIONAL,
    +                   gender [3] PrintableString OPTIONAL,
    +                   postalAddress [4] DirectoryString OPTIONAL
    +                   }
    +             
    + + @see org.bouncycastle.asn1.x509.sigi.NameOrPseudonym + @see org.bouncycastle.asn1.x509.sigi.SigIObjectIdentifiers +
    + + Constructor from Asn1Sequence. +

    + The sequence is of type NameOrPseudonym: +

    +

    +                 PersonalData ::= SEQUENCE {
    +                   nameOrPseudonym NameOrPseudonym,
    +                   nameDistinguisher [0] INTEGER OPTIONAL,
    +                   dateOfBirth [1] GeneralizedTime OPTIONAL,
    +                   placeOfBirth [2] DirectoryString OPTIONAL,
    +                   gender [3] PrintableString OPTIONAL,
    +                   postalAddress [4] DirectoryString OPTIONAL
    +                   }
    +             
    + + @param seq The ASN.1 sequence. +
    + + Constructor from a given details. + + @param nameOrPseudonym Name or pseudonym. + @param nameDistinguisher Name distinguisher. + @param dateOfBirth Date of birth. + @param placeOfBirth Place of birth. + @param gender Gender. + @param postalAddress Postal Address. + + + Produce an object suitable for an Asn1OutputStream. +

    + Returns: +

    +

    +                 PersonalData ::= SEQUENCE {
    +                   nameOrPseudonym NameOrPseudonym,
    +                   nameDistinguisher [0] INTEGER OPTIONAL,
    +                   dateOfBirth [1] GeneralizedTime OPTIONAL,
    +                   placeOfBirth [2] DirectoryString OPTIONAL,
    +                   gender [3] PrintableString OPTIONAL,
    +                   postalAddress [4] DirectoryString OPTIONAL
    +                   }
    +             
    + + @return an Asn1Object +
    + + Object Identifiers of SigI specifciation (German Signature Law + Interoperability specification). + + + Key purpose IDs for German SigI (Signature Interoperability + Specification) + + + Certificate policy IDs for German SigI (Signature Interoperability + Specification) + + + Other Name IDs for German SigI (Signature Interoperability Specification) + + + To be used for for the generation of directory service certificates. + + + ID for PersonalData + + + Certificate is conform to german signature law. + + + This extension may contain further X.500 attributes of the subject. See also + RFC 3039. + +
    +                 SubjectDirectoryAttributes ::= Attributes
    +                 Attributes ::= SEQUENCE SIZE (1..MAX) OF Attribute
    +                 Attribute ::= SEQUENCE
    +                 {
    +                   type AttributeType
    +                   values SET OF AttributeValue
    +                 }
    +            
    +                 AttributeType ::= OBJECT IDENTIFIER
    +                 AttributeValue ::= ANY DEFINED BY AttributeType
    +             
    + + @see org.bouncycastle.asn1.x509.X509Name for AttributeType ObjectIdentifiers. +
    + + Constructor from Asn1Sequence. + + The sequence is of type SubjectDirectoryAttributes: + +
    +                  SubjectDirectoryAttributes ::= Attributes
    +                  Attributes ::= SEQUENCE SIZE (1..MAX) OF Attribute
    +                  Attribute ::= SEQUENCE
    +                  {
    +                    type AttributeType
    +                    values SET OF AttributeValue
    +                  }
    +            
    +                  AttributeType ::= OBJECT IDENTIFIER
    +                  AttributeValue ::= ANY DEFINED BY AttributeType
    +             
    + + @param seq + The ASN.1 sequence. +
    + + Constructor from an ArrayList of attributes. + + The ArrayList consists of attributes of type {@link Attribute Attribute} + + @param attributes The attributes. + + + + Produce an object suitable for an Asn1OutputStream. + + Returns: + +
    +                  SubjectDirectoryAttributes ::= Attributes
    +                  Attributes ::= SEQUENCE SIZE (1..MAX) OF Attribute
    +                  Attribute ::= SEQUENCE
    +                  {
    +                    type AttributeType
    +                    values SET OF AttributeValue
    +                  }
    +            
    +                  AttributeType ::= OBJECT IDENTIFIER
    +                  AttributeValue ::= ANY DEFINED BY AttributeType
    +             
    + + @return a DERObject +
    + + @return Returns the attributes. + + + The SubjectKeyIdentifier object. +
    +            SubjectKeyIdentifier::= OCTET STRING
    +            
    +
    + + Calculates the keyIdentifier using a SHA1 hash over the BIT STRING + from SubjectPublicKeyInfo as defined in RFC3280. + + @param spki the subject public key info. + + + Return a RFC 3280 type 1 key identifier. As in: +
    +            (1) The keyIdentifier is composed of the 160-bit SHA-1 hash of the
    +            value of the BIT STRING subjectPublicKey (excluding the tag,
    +            length, and number of unused bits).
    +            
    + @param keyInfo the key info object containing the subjectPublicKey field. + @return the key identifier. +
    + + Return a RFC 3280 type 2 key identifier. As in: +
    +            (2) The keyIdentifier is composed of a four bit type field with
    +            the value 0100 followed by the least significant 60 bits of the
    +            SHA-1 hash of the value of the BIT STRING subjectPublicKey.
    +            
    + @param keyInfo the key info object containing the subjectPublicKey field. + @return the key identifier. +
    + + The object that contains the public key stored in a certficate. +

    + The GetEncoded() method in the public keys in the JCE produces a DER + encoded one of these.

    +
    + + for when the public key is an encoded object - if the bitstring + can't be decoded this routine raises an IOException. + + @exception IOException - if the bit string doesn't represent a Der + encoded object. + + + Produce an object suitable for an Asn1OutputStream. +
    +            SubjectPublicKeyInfo ::= Sequence {
    +                                     algorithm AlgorithmIdentifier,
    +                                     publicKey BIT STRING }
    +            
    +
    + + for when the public key is raw bits... + + + Target structure used in target information extension for attribute + certificates from RFC 3281. + +
    +                Target  ::= CHOICE {
    +                  targetName          [0] GeneralName,
    +                  targetGroup         [1] GeneralName,
    +                  targetCert          [2] TargetCert
    +                }
    +            
    + +

    + The targetCert field is currently not supported and must not be used + according to RFC 3281.

    +
    + + Creates an instance of a Target from the given object. +

    + obj can be a Target or a {@link Asn1TaggedObject}

    + + @param obj The object. + @return A Target instance. + @throws ArgumentException if the given object cannot be + interpreted as Target. +
    + + Constructor from Asn1TaggedObject. + + @param tagObj The tagged object. + @throws ArgumentException if the encoding is wrong. + + + Constructor from given details. +

    + Exactly one of the parameters must be not null.

    + + @param type the choice type to apply to the name. + @param name the general name. + @throws ArgumentException if type is invalid. +
    + + Produce an object suitable for an Asn1OutputStream. + + Returns: + +
    +                Target  ::= CHOICE {
    +                  targetName          [0] GeneralName,
    +                  targetGroup         [1] GeneralName,
    +                  targetCert          [2] TargetCert
    +                }
    +            
    + + @return an Asn1Object +
    + + @return Returns the targetGroup. + + + @return Returns the targetName. + + + Target information extension for attributes certificates according to RFC + 3281. + +
    +                      SEQUENCE OF Targets
    +            
    + +
    + + Creates an instance of a TargetInformation from the given object. +

    + obj can be a TargetInformation or a {@link Asn1Sequence}

    + + @param obj The object. + @return A TargetInformation instance. + @throws ArgumentException if the given object cannot be interpreted as TargetInformation. +
    + + Constructor from a Asn1Sequence. + + @param seq The Asn1Sequence. + @throws ArgumentException if the sequence does not contain + correctly encoded Targets elements. + + + Returns the targets in this target information extension. +

    + The ArrayList is cloned before it is returned.

    + + @return Returns the targets. +
    + + Constructs a target information from a single targets element. + According to RFC 3281 only one targets element must be produced. + + @param targets A Targets instance. + + + According to RFC 3281 only one targets element must be produced. If + multiple targets are given they must be merged in + into one targets element. + + @param targets An array with {@link Targets}. + + + Produce an object suitable for an Asn1OutputStream. + + Returns: + +
    +                     SEQUENCE OF Targets
    +            
    + +

    + According to RFC 3281 only one targets element must be produced. If + multiple targets are given in the constructor they are merged into one + targets element. If this was produced from a + {@link Org.BouncyCastle.Asn1.Asn1Sequence} the encoding is kept.

    + + @return an Asn1Object +
    + + Targets structure used in target information extension for attribute + certificates from RFC 3281. + +
    +                       Targets ::= SEQUENCE OF Target
    +                      
    +                       Target  ::= CHOICE {
    +                         targetName          [0] GeneralName,
    +                         targetGroup         [1] GeneralName,
    +                         targetCert          [2] TargetCert
    +                       }
    +                      
    +                       TargetCert  ::= SEQUENCE {
    +                         targetCertificate    IssuerSerial,
    +                         targetName           GeneralName OPTIONAL,
    +                         certDigestInfo       ObjectDigestInfo OPTIONAL
    +                       }
    +            
    + + @see org.bouncycastle.asn1.x509.Target + @see org.bouncycastle.asn1.x509.TargetInformation +
    + + Creates an instance of a Targets from the given object. +

    + obj can be a Targets or a {@link Asn1Sequence}

    + + @param obj The object. + @return A Targets instance. + @throws ArgumentException if the given object cannot be interpreted as Target. +
    + + Constructor from Asn1Sequence. + + @param targets The ASN.1 SEQUENCE. + @throws ArgumentException if the contents of the sequence are + invalid. + + + Constructor from given targets. +

    + The ArrayList is copied.

    + + @param targets An ArrayList of {@link Target}s. + @see Target + @throws ArgumentException if the ArrayList contains not only Targets. +
    + + Returns the targets in an ArrayList. +

    + The ArrayList is cloned before it is returned.

    + + @return Returns the targets. +
    + + Produce an object suitable for an Asn1OutputStream. + + Returns: + +
    +                       Targets ::= SEQUENCE OF Target
    +            
    + + @return an Asn1Object +
    + + The TbsCertificate object. +
    +            TbsCertificate ::= Sequence {
    +                 version          [ 0 ]  Version DEFAULT v1(0),
    +                 serialNumber            CertificateSerialNumber,
    +                 signature               AlgorithmIdentifier,
    +                 issuer                  Name,
    +                 validity                Validity,
    +                 subject                 Name,
    +                 subjectPublicKeyInfo    SubjectPublicKeyInfo,
    +                 issuerUniqueID    [ 1 ] IMPLICIT UniqueIdentifier OPTIONAL,
    +                 subjectUniqueID   [ 2 ] IMPLICIT UniqueIdentifier OPTIONAL,
    +                 extensions        [ 3 ] Extensions OPTIONAL
    +                 }
    +            
    +

    + Note: issuerUniqueID and subjectUniqueID are both deprecated by the IETF. This class + will parse them, but you really shouldn't be creating new ones.

    +
    + + PKIX RFC-2459 - TbsCertList object. +
    +            TbsCertList  ::=  Sequence  {
    +                 version                 Version OPTIONAL,
    +                                              -- if present, shall be v2
    +                 signature               AlgorithmIdentifier,
    +                 issuer                  Name,
    +                 thisUpdate              Time,
    +                 nextUpdate              Time OPTIONAL,
    +                 revokedCertificates     Sequence OF Sequence  {
    +                      userCertificate         CertificateSerialNumber,
    +                      revocationDate          Time,
    +                      crlEntryExtensions      Extensions OPTIONAL
    +                                                    -- if present, shall be v2
    +                                           }  OPTIONAL,
    +                 crlExtensions           [0]  EXPLICIT Extensions OPTIONAL
    +                                                    -- if present, shall be v2
    +                                           }
    +            
    +
    + + creates a time object from a given date - if the date is between 1950 + and 2049 a UTCTime object is Generated, otherwise a GeneralizedTime + is used. + + + + Return our time as DateTime. + + A date time. + + + Produce an object suitable for an Asn1OutputStream. +
    +            Time ::= CHOICE {
    +                        utcTime        UTCTime,
    +                        generalTime    GeneralizedTime }
    +            
    +
    + + UserNotice class, used in + CertificatePolicies X509 extensions (in policy + qualifiers). +
    +             UserNotice ::= Sequence {
    +                  noticeRef        NoticeReference OPTIONAL,
    +                  explicitText     DisplayText OPTIONAL}
    +            
    +             
    + + @see PolicyQualifierId + @see PolicyInformation +
    + + Creates a new UserNotice instance. + + @param noticeRef a NoticeReference value + @param explicitText a DisplayText value + + + Creates a new UserNotice instance. + + @param noticeRef a NoticeReference value + @param str the explicitText field as a string. + + + Creates a new UserNotice instance. +

    Useful from reconstructing a UserNotice instance + from its encodable/encoded form. + + @param as an ASN1Sequence value obtained from either + calling @{link toASN1Object()} for a UserNotice + instance or from parsing it from a DER-encoded stream.

    +
    + + Generator for Version 1 TbsCertificateStructures. +
    +             TbsCertificate ::= Sequence {
    +                  version          [ 0 ]  Version DEFAULT v1(0),
    +                  serialNumber            CertificateSerialNumber,
    +                  signature               AlgorithmIdentifier,
    +                  issuer                  Name,
    +                  validity                Validity,
    +                  subject                 Name,
    +                  subjectPublicKeyInfo    SubjectPublicKeyInfo,
    +                  }
    +             
    + +
    + + Generator for Version 2 AttributeCertificateInfo +
    +             AttributeCertificateInfo ::= Sequence {
    +                   version              AttCertVersion -- version is v2,
    +                   holder               Holder,
    +                   issuer               AttCertIssuer,
    +                   signature            AlgorithmIdentifier,
    +                   serialNumber         CertificateSerialNumber,
    +                   attrCertValidityPeriod   AttCertValidityPeriod,
    +                   attributes           Sequence OF Attr,
    +                   issuerUniqueID       UniqueIdentifier OPTIONAL,
    +                   extensions           Extensions OPTIONAL
    +             }
    +             
    + +
    + + @param attribute + + + Produce an object suitable for an Asn1OutputStream. +
    +             V2Form ::= Sequence {
    +                  issuerName            GeneralNames  OPTIONAL,
    +                  baseCertificateID     [0] IssuerSerial  OPTIONAL,
    +                  objectDigestInfo      [1] ObjectDigestInfo  OPTIONAL
    +                    -- issuerName MUST be present in this profile
    +                    -- baseCertificateID and objectDigestInfo MUST NOT
    +                    -- be present in this profile
    +             }
    +            
    +
    + + Generator for Version 2 TbsCertList structures. +
    +              TbsCertList  ::=  Sequence  {
    +                   version                 Version OPTIONAL,
    +                                                -- if present, shall be v2
    +                   signature               AlgorithmIdentifier,
    +                   issuer                  Name,
    +                   thisUpdate              Time,
    +                   nextUpdate              Time OPTIONAL,
    +                   revokedCertificates     Sequence OF Sequence  {
    +                        userCertificate         CertificateSerialNumber,
    +                        revocationDate          Time,
    +                        crlEntryExtensions      Extensions OPTIONAL
    +                                                      -- if present, shall be v2
    +                                             }  OPTIONAL,
    +                   crlExtensions           [0]  EXPLICIT Extensions OPTIONAL
    +                                                      -- if present, shall be v2
    +                                             }
    +             
    + + Note: This class may be subject to change +
    + + Generator for Version 3 TbsCertificateStructures. +
    +             TbsCertificate ::= Sequence {
    +                  version          [ 0 ]  Version DEFAULT v1(0),
    +                  serialNumber            CertificateSerialNumber,
    +                  signature               AlgorithmIdentifier,
    +                  issuer                  Name,
    +                  validity                Validity,
    +                  subject                 Name,
    +                  subjectPublicKeyInfo    SubjectPublicKeyInfo,
    +                  issuerUniqueID    [ 1 ] IMPLICIT UniqueIdentifier OPTIONAL,
    +                  subjectUniqueID   [ 2 ] IMPLICIT UniqueIdentifier OPTIONAL,
    +                  extensions        [ 3 ] Extensions OPTIONAL
    +                  }
    +             
    + +
    + + an X509Certificate structure. +
    +             Certificate ::= Sequence {
    +                 tbsCertificate          TbsCertificate,
    +                 signatureAlgorithm      AlgorithmIdentifier,
    +                 signature               BIT STRING
    +             }
    +            
    +
    + + The default converter for X509 DN entries when going from their + string value to ASN.1 strings. + + + * It turns out that the number of standard ways the fields in a DN should be + * encoded into their ASN.1 counterparts is rapidly approaching the + * number of machines on the internet. By default the X509Name class + * will produce UTF8Strings in line with the current recommendations (RFC 3280). + *

    + * An example of an encoder look like below: + *

    +                 * public class X509DirEntryConverter
    +                 *     : X509NameEntryConverter
    +                 * {
    +                 *     public Asn1Object GetConvertedValue(
    +                 *         DerObjectIdentifier  oid,
    +                 *         string               value)
    +                 *     {
    +                 *         if (str.Length() != 0 && str.charAt(0) == '#')
    +                 *         {
    +                 *             return ConvertHexEncoded(str, 1);
    +                 *         }
    +                 *         if (oid.Equals(EmailAddress))
    +                 *         {
    +                 *             return new DerIA5String(str);
    +                 *         }
    +                 *         else if (CanBePrintable(str))
    +                 *         {
    +                 *             return new DerPrintableString(str);
    +                 *         }
    +                 *         else if (CanBeUTF8(str))
    +                 *         {
    +                 *             return new DerUtf8String(str);
    +                 *         }
    +                 *         else
    +                 *         {
    +                 *             return new DerBmpString(str);
    +                 *         }
    +                 *     }
    +                 * }
    +            	 * 
    + *

    +
    + + Convert an inline encoded hex string rendition of an ASN.1 + object back into its corresponding ASN.1 object. + + @param str the hex encoded object + @param off the index at which the encoding starts + @return the decoded object + + + return true if the passed in string can be represented without + loss as a PrintableString, false otherwise. + + + Convert the passed in string value into the appropriate ASN.1 + encoded object. + + @param oid the oid associated with the value in the DN. + @param value the value of the particular DN component. + @return the ASN.1 equivalent for the value. + + + Apply default conversion for the given value depending on the oid + and the character range of the value. + + @param oid the object identifier for the DN entry + @param value the value associated with it + @return the ASN.1 equivalent for the string value. + + + an object for the elements in the X.509 V3 extension block. + + + Convert the value of the passed in extension to an object. + The extension to parse. + The object the value string contains. + If conversion is not possible. + + + Subject Directory Attributes + + + Subject Key Identifier + + + Key Usage + + + Private Key Usage Period + + + Subject Alternative Name + + + Issuer Alternative Name + + + Basic Constraints + + + CRL Number + + + Reason code + + + Hold Instruction Code + + + Invalidity Date + + + Delta CRL indicator + + + Issuing Distribution Point + + + Certificate Issuer + + + Name Constraints + + + CRL Distribution Points + + + Certificate Policies + + + Policy Mappings + + + Authority Key Identifier + + + Policy Constraints + + + Extended Key Usage + + + Freshest CRL + + + Inhibit Any Policy + + + Authority Info Access + + + Subject Info Access + + + Logo Type + + + BiometricInfo + + + QCStatements + + + Audit identity extension in attribute certificates. + + + NoRevAvail extension in attribute certificates. + + + TargetInformation extension in attribute certificates. + + + Constructor from Asn1Sequence. + + the extensions are a list of constructed sequences, either with (Oid, OctetString) or (Oid, Boolean, OctetString) + + + constructor from a table of extensions. +

    + it's is assumed the table contains Oid/string pairs.

    +
    + + Constructor from a table of extensions with ordering. +

    + It's is assumed the table contains Oid/string pairs.

    +
    + + Constructor from two vectors + + @param objectIDs an ArrayList of the object identifiers. + @param values an ArrayList of the extension values. + + + constructor from a table of extensions. +

    + it's is assumed the table contains Oid/string pairs.

    +
    + + Constructor from a table of extensions with ordering. +

    + It's is assumed the table contains Oid/string pairs.

    +
    + + Constructor from two vectors + + @param objectIDs an ArrayList of the object identifiers. + @param values an ArrayList of the extension values. + + + return the extension represented by the object identifier + passed in. + + @return the extension if it's present, null otherwise. + + +
    +                 Extensions        ::=   SEQUENCE SIZE (1..MAX) OF Extension
    +            
    +                 Extension         ::=   SEQUENCE {
    +                    extnId            EXTENSION.&id ({ExtensionSet}),
    +                    critical          BOOLEAN DEFAULT FALSE,
    +                    extnValue         OCTET STRING }
    +             
    +
    + + return an Enumeration of the extension field's object ids. + + + Generator for X.509 extensions + + + Reset the generator + + + + Add an extension with the given oid and the passed in value to be included + in the OCTET STRING associated with the extension. + + OID for the extension. + True if critical, false otherwise. + The ASN.1 object to be included in the extension. + + + + Add an extension with the given oid and the passed in byte array to be wrapped + in the OCTET STRING associated with the extension. + + OID for the extension. + True if critical, false otherwise. + The byte array to be wrapped. + + + Generate an X509Extensions object based on the current state of the generator. + An X509Extensions object + + + Return true if there are no extension present in this generator. + True if empty, false otherwise + + +
    +                 RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
    +            
    +                 RelativeDistinguishedName ::= SET SIZE (1..MAX) OF AttributeTypeAndValue
    +            
    +                 AttributeTypeAndValue ::= SEQUENCE {
    +                                               type  OBJECT IDENTIFIER,
    +                                               value ANY }
    +             
    +
    + + country code - StringType(SIZE(2)) + + + organization - StringType(SIZE(1..64)) + + + organizational unit name - StringType(SIZE(1..64)) + + + Title + + + common name - StringType(SIZE(1..64)) + + + street - StringType(SIZE(1..64)) + + + device serial number name - StringType(SIZE(1..64)) + + + locality name - StringType(SIZE(1..64)) + + + state, or province name - StringType(SIZE(1..64)) + + + Naming attributes of type X520name + + + businessCategory - DirectoryString(SIZE(1..128) + + + postalCode - DirectoryString(SIZE(1..40) + + + dnQualifier - DirectoryString(SIZE(1..64) + + + RFC 3039 Pseudonym - DirectoryString(SIZE(1..64) + + + RFC 3039 DateOfBirth - GeneralizedTime - YYYYMMDD000000Z + + + RFC 3039 PlaceOfBirth - DirectoryString(SIZE(1..128) + + + RFC 3039 DateOfBirth - PrintableString (SIZE(1)) -- "M", "F", "m" or "f" + + + RFC 3039 CountryOfCitizenship - PrintableString (SIZE (2)) -- ISO 3166 + codes only + + + RFC 3039 CountryOfCitizenship - PrintableString (SIZE (2)) -- ISO 3166 + codes only + + + ISIS-MTT NameAtBirth - DirectoryString(SIZE(1..64) + + + RFC 3039 PostalAddress - SEQUENCE SIZE (1..6) OF + DirectoryString(SIZE(1..30)) + + + RFC 2256 dmdName + + + id-at-telephoneNumber + + + id-at-name + + + Email address (RSA PKCS#9 extension) - IA5String. +

    Note: if you're trying to be ultra orthodox, don't use this! It shouldn't be in here.

    +
    + + more from PKCS#9 + + + email address in Verisign certificates + + + LDAP User id. + + + default look up table translating OID values into their common symbols following + the convention in RFC 2253 with a few extras + + + look up table translating OID values into their common symbols following the convention in RFC 2253 + + + look up table translating OID values into their common symbols following the convention in RFC 1779 + + + + look up table translating common symbols into their OIDS. + + + Return a X509Name based on the passed in tagged object. + + @param obj tag object holding name. + @param explicitly true if explicitly tagged false otherwise. + @return the X509Name + + + Constructor from Asn1Sequence + + the principal will be a list of constructed sets, each containing an (OID, string) pair. + + + Constructor from a table of attributes with ordering. +

    + it's is assumed the table contains OID/string pairs, and the contents + of the table are copied into an internal table as part of the + construction process. The ordering ArrayList should contain the OIDs + in the order they are meant to be encoded or printed in ToString.

    +
    + + Constructor from a table of attributes with ordering. +

    + it's is assumed the table contains OID/string pairs, and the contents + of the table are copied into an internal table as part of the + construction process. The ordering ArrayList should contain the OIDs + in the order they are meant to be encoded or printed in ToString.

    +

    + The passed in converter will be used to convert the strings into their + ASN.1 counterparts.

    +
    + + Takes two vectors one of the oids and the other of the values. + + + Takes two vectors one of the oids and the other of the values. +

    + The passed in converter will be used to convert the strings into their + ASN.1 counterparts.

    +
    + + Takes an X509 dir name as a string of the format "C=AU, ST=Victoria", or + some such, converting it into an ordered set of name attributes. + + + Takes an X509 dir name as a string of the format "C=AU, ST=Victoria", or + some such, converting it into an ordered set of name attributes with each + string value being converted to its associated ASN.1 type using the passed + in converter. + + + Takes an X509 dir name as a string of the format "C=AU, ST=Victoria", or + some such, converting it into an ordered set of name attributes. If reverse + is true, create the encoded version of the sequence starting from the + last element in the string. + + + Takes an X509 dir name as a string of the format "C=AU, ST=Victoria", or + some such, converting it into an ordered set of name attributes with each + string value being converted to its associated ASN.1 type using the passed + in converter. If reverse is true the ASN.1 sequence representing the DN will + be built by starting at the end of the string, rather than the start. + + + Takes an X509 dir name as a string of the format "C=AU, ST=Victoria", or + some such, converting it into an ordered set of name attributes. lookUp + should provide a table of lookups, indexed by lowercase only strings and + yielding a DerObjectIdentifier, other than that OID. and numeric oids + will be processed automatically. +
    + If reverse is true, create the encoded version of the sequence + starting from the last element in the string. + @param reverse true if we should start scanning from the end (RFC 2553). + @param lookUp table of names and their oids. + @param dirName the X.500 string to be parsed. +
    + + Takes an X509 dir name as a string of the format "C=AU, ST=Victoria", or + some such, converting it into an ordered set of name attributes. lookUp + should provide a table of lookups, indexed by lowercase only strings and + yielding a DerObjectIdentifier, other than that OID. and numeric oids + will be processed automatically. The passed in converter is used to convert the + string values to the right of each equals sign to their ASN.1 counterparts. +
    + @param reverse true if we should start scanning from the end, false otherwise. + @param lookUp table of names and oids. + @param dirName the string dirName + @param converter the converter to convert string values into their ASN.1 equivalents +
    + + return an IList of the oids in the name, in the order they were found. + + + return an IList of the values found in the name, in the order they + were found. + + + return an IList of the values found in the name, in the order they + were found, with the DN label corresponding to passed in oid. + + + The X509Name object to test equivalency against. + If true, the order of elements must be the same, + as well as the values associated with each element. + + + test for equivalence - note: case is ignored. + + + convert the structure to a string - if reverse is true the + oids and values are listed out starting with the last element + in the sequence (ala RFC 2253), otherwise the string will begin + with the first element of the structure. If no string definition + for the oid is found in oidSymbols the string value of the oid is + added. Two standard symbol tables are provided DefaultSymbols, and + RFC2253Symbols as part of this class. + + @param reverse if true start at the end of the sequence and work back. + @param oidSymbols look up table strings for oids. + + + determines whether or not strings should be processed and printed + from back to front. + + + class for breaking up an X500 Name into it's component tokens, ala + java.util.StringTokenizer. We need this class as some of the + lightweight Java environment don't support classes like + StringTokenizer. + + + A general class that reads all X9.62 style EC curve tables. + + + return a X9ECParameters object representing the passed in named + curve. The routine returns null if the curve is not present. + + @param name the name of the curve requested + @return an X9ECParameters object or null if the curve is not available. + + + return the object identifier signified by the passed in name. Null + if there is no object identifier associated with name. + + @return the object identifier associated with name, if present. + + + return a X9ECParameters object representing the passed in named + curve. + + @param oid the object id of the curve requested + @return an X9ECParameters object or null if the curve is not available. + + + return an enumeration of the names of the available curves. + + @return an enumeration of the names of the available curves. + + + ASN.1 def for Diffie-Hellman key exchange KeySpecificInfo structure. See + RFC 2631, or X9.42, for further details. + + + Produce an object suitable for an Asn1OutputStream. +
    +             KeySpecificInfo ::= Sequence {
    +                 algorithm OBJECT IDENTIFIER,
    +                 counter OCTET STRING SIZE (4..4)
    +             }
    +            
    +
    + + ANS.1 def for Diffie-Hellman key exchange OtherInfo structure. See + RFC 2631, or X9.42, for further details. + + + Produce an object suitable for an Asn1OutputStream. +
    +             OtherInfo ::= Sequence {
    +                 keyInfo KeySpecificInfo,
    +                 partyAInfo [0] OCTET STRING OPTIONAL,
    +                 suppPubInfo [2] OCTET STRING
    +             }
    +            
    +
    + + table of the current named curves defined in X.962 EC-DSA. + + + return the X9ECParameters object for the named curve represented by + the passed in object identifier. Null if the curve isn't present. + + @param oid an object identifier representing a named curve, if present. + + + return the object identifier signified by the passed in name. Null + if there is no object identifier associated with name. + + @return the object identifier associated with name, if present. + + + return the named curve name represented by the given object identifier. + + + returns an enumeration containing the name strings for curves + contained in this structure. + + + Produce an object suitable for an Asn1OutputStream. +
    +            Parameters ::= CHOICE {
    +               ecParameters ECParameters,
    +               namedCurve   CURVES.&id({CurveNames}),
    +               implicitlyCA Null
    +            }
    +            
    +
    + + ASN.1 def for Elliptic-Curve Curve structure. See + X9.62, for further details. + + + Produce an object suitable for an Asn1OutputStream. +
    +             Curve ::= Sequence {
    +                 a               FieldElement,
    +                 b               FieldElement,
    +                 seed            BIT STRING      OPTIONAL
    +             }
    +            
    +
    + + ASN.1 def for Elliptic-Curve ECParameters structure. See + X9.62, for further details. + + + Produce an object suitable for an Asn1OutputStream. +
    +             ECParameters ::= Sequence {
    +                 version         Integer { ecpVer1(1) } (ecpVer1),
    +                 fieldID         FieldID {{FieldTypes}},
    +                 curve           X9Curve,
    +                 base            X9ECPoint,
    +                 order           Integer,
    +                 cofactor        Integer OPTIONAL
    +             }
    +            
    +
    + + Return the ASN.1 entry representing the Curve. + + @return the X9Curve for the curve in these parameters. + + + Return the ASN.1 entry representing the FieldID. + + @return the X9FieldID for the FieldID in these parameters. + + + Return the ASN.1 entry representing the base point G. + + @return the X9ECPoint for the base point in these parameters. + + + class for describing an ECPoint as a Der object. + + + Produce an object suitable for an Asn1OutputStream. +
    +             ECPoint ::= OCTET STRING
    +            
    +

    + Octet string produced using ECPoint.GetEncoded().

    +
    + + Class for processing an ECFieldElement as a DER object. + + + Produce an object suitable for an Asn1OutputStream. +
    +             FieldElement ::= OCTET STRING
    +            
    +

    +

      +
    1. if q is an odd prime then the field element is + processed as an Integer and converted to an octet string + according to x 9.62 4.3.1.
    2. +
    3. if q is 2m then the bit string + contained in the field element is converted into an octet + string with the same ordering padded at the front if necessary. +
    4. +
    +

    +
    + + ASN.1 def for Elliptic-Curve Field ID structure. See + X9.62, for further details. + + + Constructor for elliptic curves over prime fields + F2. + @param primeP The prime p defining the prime field. + + + Constructor for elliptic curves over binary fields + F2m. + @param m The exponent m of + F2m. + @param k1 The integer k1 where xm + + xk1 + 1 + represents the reduction polynomial f(z). + + + Constructor for elliptic curves over binary fields + F2m. + @param m The exponent m of + F2m. + @param k1 The integer k1 where xm + + xk3 + xk2 + xk1 + 1 + represents the reduction polynomial f(z). + @param k2 The integer k2 where xm + + xk3 + xk2 + xk1 + 1 + represents the reduction polynomial f(z). + @param k3 The integer k3 where xm + + xk3 + xk2 + xk1 + 1 + represents the reduction polynomial f(z).. + + + Produce a Der encoding of the following structure. +
    +             FieldID ::= Sequence {
    +                 fieldType       FIELD-ID.&id({IOSet}),
    +                 parameters      FIELD-ID.&Type({IOSet}{@fieldType})
    +             }
    +            
    +
    + + id-dsa-with-sha1 OBJECT IDENTIFIER ::= { iso(1) member-body(2) + us(840) x9-57 (10040) x9cm(4) 3 } + + + X9.63 + + + X9.42 + + + reader for Base64 armored objects - read the headers and then start returning + bytes when the data is reached. An IOException is thrown if the CRC check + fails. + + + decode the base 64 encoded input data. + + @return the offset the data starts in out. + + + Create a stream for reading a PGP armoured message, parsing up to a header + and then reading the data that follows. + + @param input + + + Create an armoured input stream which will assume the data starts + straight away, or parse for headers first depending on the value of + hasHeaders. + + @param input + @param hasHeaders true if headers are to be looked for, false otherwise. + + + @return true if we are inside the clear text section of a PGP + signed message. + + + @return true if the stream is actually at end of file. + + + Return the armor header line (if there is one) + @return the armor header line, null if none present. + + + Return the armor headers (the lines after the armor header line), + @return an array of armor headers, null if there aren't any. + + + Basic output stream. + + + encode the input data producing a base 64 encoded byte array. + + + Set an additional header entry. + + @param name the name of the header entry. + @param v the value of the header entry. + + + Reset the headers to only contain a Version string. + + + Start a clear text signed message. + @param hashAlgorithm + + + Note: Close() does not close the underlying stream. So it is possible to write + multiple objects using armoring to a single stream. + + + Basic type for a image attribute packet. + + + Basic type for a user attribute sub-packet. + + + return the generic data making up the packet. + + + Reader for PGP objects. + + + Returns the next packet tag in the stream. + + + + A stream that overlays our input stream, allowing the user to only read a segment of it. + NB: dataLength will be negative if the segment length is in the upper range above 2**31. + + + + Base class for a PGP object. + + + Basic output stream. + + + Create a stream representing a general packet. + Output stream to write to. + + + Create a stream representing an old style partial object. + Output stream to write to. + The packet tag for the object. + + + Create a stream representing a general packet. + Output stream to write to. + Packet tag. + Size of chunks making up the packet. + If true, the header is written out in old format. + + + Create a new style partial input stream buffered into chunks. + Output stream to write to. + Packet tag. + Size of chunks making up the packet. + + + Create a new style partial input stream buffered into chunks. + Output stream to write to. + Packet tag. + Buffer to use for collecting chunks. + + + Flush the underlying stream. + + + Finish writing out the current packet without closing the underlying stream. + + + Generic compressed data object. + + + Note: you can only read from this once... + + + The algorithm tag value. + + + Basic tags for compression algorithms. + + + Basic type for a PGP packet. + + + Base class for a DSA public key. + + + Base interface for a PGP key. + + + + The base format for this key - in the case of the symmetric keys it will generally + be raw indicating that the key is just a straight byte representation, for an asymmetric + key the format will be PGP, indicating the key is a string of MPIs encoded in PGP format. + + "RAW" or "PGP". + + + The stream to read the packet from. + + + Return the standard PGP encoding of the key. + + + The format, as a string, always "PGP". + + + Base class for a DSA secret key. + + + @param in + + + Return the standard PGP encoding of the key. + + + The format, as a string, always "PGP". + + + @return x + + + Base class for an ECDH Public Key. + + + Base class for an EC Public Key. + + + The stream to read the packet from. + + + Return the standard PGP encoding of the key. + + + The format, as a string, always "PGP". + + + The stream to read the packet from. + + + Base class for an ECDSA Public Key. + + + The stream to read the packet from. + + + Base class for an EC Secret Key. + + + Return the standard PGP encoding of the key. + + + The format, as a string, always "PGP". + + + Base class for an ElGamal public key. + + + Return the standard PGP encoding of the key. + + + The format, as a string, always "PGP". + + + Base class for an ElGamal secret key. + + + @param in + + + @param x + + + Return the standard PGP encoding of the key. + + + The format, as a string, always "PGP". + + + Basic packet for an experimental packet. + + + Basic tags for hash algorithms. + + + Generic literal data packet. + + + The format tag value. + + + The modification time of the file in milli-seconds (since Jan 1, 1970 UTC) + + + Basic type for a marker packet. + + + Basic packet for a modification detection code packet. + + + A multiple precision integer + + + Generic signature object + + + The encryption algorithm tag. + + + The hash algorithm tag. + + + Basic PGP packet tag types. + + + Public Key Algorithm tag numbers. + + + Basic packet for a PGP public key. + + + Basic packet for a PGP public key. + + + Construct a version 4 public key packet. + + + Basic packet for a PGP public subkey + + + Construct a version 4 public subkey packet. + + + Base class for an RSA public key. + + + Construct an RSA public key from the passed in stream. + + + The modulus. + The public exponent. + + + Return the standard PGP encoding of the key. + + + The format, as a string, always "PGP". + + + Base class for an RSA secret (or priate) key. + + + Return the standard PGP encoding of the key. + + + The format, as a string, always "PGP". + + + The string to key specifier class. + + + The IV for the key generation algorithm. + + + The hash algorithm. + + + The iteration count + + + The protection mode - only if GnuDummyS2K + + + Basic packet for a PGP secret key. + + + Basic packet for a PGP secret key. + + + Generic signature packet. + + + Generate a version 4 signature packet. + + @param signatureType + @param keyAlgorithm + @param hashAlgorithm + @param hashedData + @param unhashedData + @param fingerprint + @param signature + + + Generate a version 2/3 signature packet. + + @param signatureType + @param keyAlgorithm + @param hashAlgorithm + @param fingerprint + @param signature + + + return the signature trailer that must be included with the data + to reconstruct the signature + + @return byte[] + + + * return the signature as a set of integers - note this is normalised to be the + * ASN.1 encoding of what appears in the signature packet. + + + Return the byte encoding of the signature section. + @return uninterpreted signature bytes. + + + return the keyId + @return the keyId that created the signature. + + + Return the creation time in milliseconds since 1 Jan., 1970 UTC. + + + Basic type for a PGP Signature sub-packet. + + + Return the generic data making up the packet. + + + reader for signature sub-packets + + + Basic PGP signature sub-packet tag types. + + + Packet embedded signature + + + packet giving signature creation time. + + + packet giving signature expiration time. + + + Identifier for the modification detection feature + + + Returns if a particular feature is supported. + + + Sets support for a particular feature. + + + Returns if modification detection is supported. + + + packet giving signature creation time. + + + packet giving time after creation at which the key expires. + + + Return the number of seconds after creation time a key is valid for. + + @return second count for key validity. + + + Packet holding the key flag values. + + + + Return the flag values contained in the first 4 octets (note: at the moment + the standard only uses the first one). + + + + Class provided a NotationData object according to + RFC2440, Chapter 5.2.3.15. Notation Data + + + packet giving signature creation time. + + + packet giving whether or not the signature is signed using the primary user ID for the key. + + + packet giving whether or not is revocable. + + + + Represents revocation key OpenPGP signature sub packet. + + + + + Represents revocation reason OpenPGP signature sub packet. + + + + packet giving signature creation time. + + + packet giving signature expiration time. + + + return time in seconds before signature expires after creation time. + + + packet giving the User ID of the signer. + + + packet giving trust. + + + Basic type for a symmetric key encrypted packet. + + + Basic tags for symmetric key algorithms + + + Basic type for a symmetric encrypted session key packet + + + @return byte[] + + + @return int + + + @return S2k + + + @return int + + + Basic type for a trust packet. + + + Basic type for a user attribute packet. + + + reader for user attribute sub-packets + + + Basic PGP user attribute sub-packet tag types. + + + Basic type for a user ID packet. + + + Base class for both the compress and decompress classes. + Holds common arrays, and static data. + + @author Keiron Liddle + + + An input stream that decompresses from the BZip2 format (with the file + header chars) to be read as any other stream. + + @author Keiron Liddle + + NB: note this class has been modified to read the leading BZ from the + start of the BZIP2 stream to make it compatible with other PGP programs. + + + An output stream that compresses into the BZip2 format (with the file + header chars) into another stream. + + @author Keiron Liddle + + TODO: Update to BZip2 1.0.1 + NB: note this class has been modified to add a leading BZ to the + start of the BZIP2 stream to make it compatible with other PGP programs. + + + + modified by Oliver Merkel, 010128 + + + + A simple class the hold and calculate the CRC for sanity checking + of the data. + + @author Keiron Liddle + + + + The 'Signature' parameter is only available when generating unsigned attributes. + + + + containing class for an CMS Authenticated Data object + + + return a store of the intended recipients for this message + + + return a table of the digested attributes indexed by + the OID of the attribute. + + + return a table of the undigested attributes indexed by + the OID of the attribute. + + + return the ASN.1 encoded representation of this object. + + + return the object identifier for the content MAC algorithm. + + + return the ContentInfo + + + General class for generating a CMS authenticated-data message. + + A simple example of usage. + +
    +                  CMSAuthenticatedDataGenerator  fact = new CMSAuthenticatedDataGenerator();
    +            
    +                  fact.addKeyTransRecipient(cert);
    +            
    +                  CMSAuthenticatedData         data = fact.generate(content, algorithm, "BC");
    +             
    +
    + + General class for generating a CMS enveloped-data message. + + A simple example of usage. + +
    +                  CMSEnvelopedDataGenerator  fact = new CMSEnvelopedDataGenerator();
    +            
    +                  fact.addKeyTransRecipient(cert);
    +            
    +                  CMSEnvelopedData         data = fact.generate(content, algorithm, "BC");
    +             
    +
    + + Constructor allowing specific source of randomness + Instance of SecureRandom to use. + + + add a recipient. + + @param cert recipient's public key certificate + @exception ArgumentException if there is a problem with the certificate + + + add a recipient + + @param key the public key used by the recipient + @param subKeyId the identifier for the recipient's public key + @exception ArgumentException if there is a problem with the key + + + add a KEK recipient. + @param key the secret key to use for wrapping + @param keyIdentifier the byte string that identifies the key + + + add a KEK recipient. + @param key the secret key to use for wrapping + @param keyIdentifier the byte string that identifies the key + + + Add a key agreement based recipient. + + @param agreementAlgorithm key agreement algorithm to use. + @param senderPrivateKey private key to initialise sender side of agreement with. + @param senderPublicKey sender public key to include with message. + @param recipientCert recipient's public key certificate. + @param cekWrapAlgorithm OID for key wrapping algorithm to use. + @exception SecurityUtilityException if the algorithm requested cannot be found + @exception InvalidKeyException if the keys are inappropriate for the algorithm specified + + + Add multiple key agreement based recipients (sharing a single KeyAgreeRecipientInfo structure). + + @param agreementAlgorithm key agreement algorithm to use. + @param senderPrivateKey private key to initialise sender side of agreement with. + @param senderPublicKey sender public key to include with message. + @param recipientCerts recipients' public key certificates. + @param cekWrapAlgorithm OID for key wrapping algorithm to use. + @exception SecurityUtilityException if the algorithm requested cannot be found + @exception InvalidKeyException if the keys are inappropriate for the algorithm specified + + + base constructor + + + constructor allowing specific source of randomness + + @param rand instance of SecureRandom to use + + + base constructor + + + constructor allowing specific source of randomness + @param rand instance of SecureRandom to use + + + generate an enveloped object that contains an CMS Enveloped Data + object using the given provider and the passed in key generator. + + + generate an authenticated object that contains an CMS Authenticated Data object + + + Parsing class for an CMS Authenticated Data object from an input stream. +

    + Note: that because we are in a streaming mode only one recipient can be tried and it is important + that the methods on the parser are called in the appropriate order. +

    +

    + Example of use - assuming the first recipient matches the private key we have. +

    +                  CMSAuthenticatedDataParser     ad = new CMSAuthenticatedDataParser(inputStream);
    +            
    +                  RecipientInformationStore  recipients = ad.getRecipientInfos();
    +            
    +                  Collection  c = recipients.getRecipients();
    +                  Iterator    it = c.iterator();
    +            
    +                  if (it.hasNext())
    +                  {
    +                      RecipientInformation   recipient = (RecipientInformation)it.next();
    +            
    +                      CMSTypedStream recData = recipient.getContentStream(privateKey, "BC");
    +            
    +                      processDataStream(recData.getContentStream());
    +            
    +                      if (!Arrays.equals(ad.getMac(), recipient.getMac())
    +                      {
    +                          System.err.println("Data corrupted!!!!");
    +                      }
    +                  }
    +              
    + Note: this class does not introduce buffering - if you are processing large files you should create + the parser with: +
    +                      CMSAuthenticatedDataParser     ep = new CMSAuthenticatedDataParser(new BufferedInputStream(inputStream, bufSize));
    +              
    + where bufSize is a suitably large buffer size. +

    +
    + + Close the underlying data stream. + @throws IOException if the close fails. + + + return a store of the intended recipients for this message + + + return a table of the unauthenticated attributes indexed by + the OID of the attribute. + @exception java.io.IOException + + + return a table of the unauthenticated attributes indexed by + the OID of the attribute. + @exception java.io.IOException + + + return the object identifier for the mac algorithm. + + + return the ASN.1 encoded encryption algorithm parameters, or null if + there aren't any. + + + General class for generating a CMS authenticated-data message stream. +

    + A simple example of usage. +

    +                  CMSAuthenticatedDataStreamGenerator edGen = new CMSAuthenticatedDataStreamGenerator();
    +            
    +                  edGen.addKeyTransRecipient(cert);
    +            
    +                  ByteArrayOutputStream  bOut = new ByteArrayOutputStream();
    +            
    +                  OutputStream out = edGen.open(
    +                                          bOut, CMSAuthenticatedDataGenerator.AES128_CBC, "BC");*
    +                  out.write(data);
    +            
    +                  out.close();
    +             
    +

    +
    + + base constructor + + + constructor allowing specific source of randomness + @param rand instance of SecureRandom to use + + + Set the underlying string size for encapsulated data + + @param bufferSize length of octet strings to buffer the data. + + + Use a BER Set to store the recipient information + + + generate an enveloped object that contains an CMS Enveloped Data + object using the given provider and the passed in key generator. + @throws java.io.IOException + + + generate an enveloped object that contains an CMS Enveloped Data object + + + generate an enveloped object that contains an CMS Enveloped Data object + + + containing class for an CMS AuthEnveloped Data object + + + containing class for an CMS Compressed Data object + + + Return the uncompressed content. + + @return the uncompressed content + @throws CmsException if there is an exception uncompressing the data. + + + Return the uncompressed content, throwing an exception if the data size + is greater than the passed in limit. If the content is exceeded getCause() + on the CMSException will contain a StreamOverflowException + + @param limit maximum number of bytes to read + @return the content read + @throws CMSException if there is an exception uncompressing the data. + + + return the ASN.1 encoded representation of this object. + + + return the ContentInfo + + + * General class for generating a compressed CMS message. + *

    + * A simple example of usage.

    + *

    + *

    +                *      CMSCompressedDataGenerator fact = new CMSCompressedDataGenerator();
    +                *      CMSCompressedData data = fact.Generate(content, algorithm);
    +                * 
    + *

    +
    + + Generate an object that contains an CMS Compressed Data + + + Class for reading a CMS Compressed Data stream. +
    +                 CMSCompressedDataParser cp = new CMSCompressedDataParser(inputStream);
    +            
    +                 process(cp.GetContent().GetContentStream());
    +             
    + Note: this class does not introduce buffering - if you are processing large files you should create + the parser with: +
    +                  CMSCompressedDataParser     ep = new CMSCompressedDataParser(new BufferedInputStream(inputStream, bufSize));
    +              
    + where bufSize is a suitably large buffer size. +
    + + General class for generating a compressed CMS message stream. +

    + A simple example of usage. +

    +
    +                  CMSCompressedDataStreamGenerator gen = new CMSCompressedDataStreamGenerator();
    +            
    +                  Stream cOut = gen.Open(outputStream, CMSCompressedDataStreamGenerator.ZLIB);
    +            
    +                  cOut.Write(data);
    +            
    +                  cOut.Close();
    +             
    +
    + + base constructor + + + Set the underlying string size for encapsulated data + + @param bufferSize length of octet strings to buffer the data. + + + containing class for an CMS Enveloped Data object + + + return a store of the intended recipients for this message + + + return a table of the unprotected attributes indexed by + the OID of the attribute. + + + return the ASN.1 encoded representation of this object. + + + return the object identifier for the content encryption algorithm. + + + return the ContentInfo + + + + General class for generating a CMS enveloped-data message. + + A simple example of usage. + +
    +                  CmsEnvelopedDataGenerator  fact = new CmsEnvelopedDataGenerator();
    +            
    +                  fact.AddKeyTransRecipient(cert);
    +            
    +                  CmsEnvelopedData         data = fact.Generate(content, algorithm);
    +             
    +
    +
    + + Constructor allowing specific source of randomness + Instance of SecureRandom to use. + + + + Generate an enveloped object that contains a CMS Enveloped Data + object using the passed in key generator. + + + + Generate an enveloped object that contains an CMS Enveloped Data object. + + + Generate an enveloped object that contains an CMS Enveloped Data object. + + + Parsing class for an CMS Enveloped Data object from an input stream. +

    + Note: that because we are in a streaming mode only one recipient can be tried and it is important + that the methods on the parser are called in the appropriate order. +

    +

    + Example of use - assuming the first recipient matches the private key we have. +

    +                  CmsEnvelopedDataParser     ep = new CmsEnvelopedDataParser(inputStream);
    +            
    +                  RecipientInformationStore  recipients = ep.GetRecipientInfos();
    +            
    +                  Collection  c = recipients.getRecipients();
    +                  Iterator    it = c.iterator();
    +            
    +                  if (it.hasNext())
    +                  {
    +                      RecipientInformation   recipient = (RecipientInformation)it.next();
    +            
    +                      CMSTypedStream recData = recipient.getContentStream(privateKey);
    +            
    +                      processDataStream(recData.getContentStream());
    +                  }
    +              
    + Note: this class does not introduce buffering - if you are processing large files you should create + the parser with: +
    +                      CmsEnvelopedDataParser     ep = new CmsEnvelopedDataParser(new BufferedInputStream(inputStream, bufSize));
    +              
    + where bufSize is a suitably large buffer size. +

    +
    + + return a store of the intended recipients for this message + + + return a table of the unprotected attributes indexed by + the OID of the attribute. + @throws IOException + + + return the object identifier for the content encryption algorithm. + + + return the ASN.1 encoded encryption algorithm parameters, or null if + there aren't any. + + + General class for generating a CMS enveloped-data message stream. +

    + A simple example of usage. +

    +                  CmsEnvelopedDataStreamGenerator edGen = new CmsEnvelopedDataStreamGenerator();
    +            
    +                  edGen.AddKeyTransRecipient(cert);
    +            
    +                  MemoryStream  bOut = new MemoryStream();
    +            
    +                  Stream out = edGen.Open(
    +                                          bOut, CMSEnvelopedDataGenerator.AES128_CBC);*
    +                  out.Write(data);
    +            
    +                  out.Close();
    +             
    +

    +
    + + Constructor allowing specific source of randomness + Instance of SecureRandom to use. + + + Set the underlying string size for encapsulated data. + Length of octet strings to buffer the data. + + + Use a BER Set to store the recipient information. + + + + Generate an enveloped object that contains an CMS Enveloped Data + object using the passed in key generator. + + + + generate an enveloped object that contains an CMS Enveloped Data object + @throws IOException + + + generate an enveloped object that contains an CMS Enveloped Data object + @throws IOException + + + all parameter classes implement this. + + + + Generic routine to copy out the data we want processed. + + + This routine may be called multiple times. + + + + a holding class for a byte array of data to be processed. + + + A clone of the byte array + + + a holding class for a file of data to be processed. + + + The file handle + + + general class for handling a pkcs7-signature message. + + A simple example of usage - note, in the example below the validity of + the certificate isn't verified, just the fact that one of the certs + matches the given signer... + +
    +              IX509Store              certs = s.GetCertificates();
    +              SignerInformationStore  signers = s.GetSignerInfos();
    +            
    +              foreach (SignerInformation signer in signers.GetSigners())
    +              {
    +                  ArrayList       certList = new ArrayList(certs.GetMatches(signer.SignerID));
    +                  X509Certificate cert = (X509Certificate) certList[0];
    +            
    +                  if (signer.Verify(cert.GetPublicKey()))
    +                  {
    +                      verified++;
    +                  }
    +              }
    +             
    +
    + + Content with detached signature, digests precomputed + + @param hashes a map of precomputed digests for content indexed by name of hash. + @param sigBlock the signature object. + + + base constructor - content with detached signature. + + @param signedContent the content that was signed. + @param sigData the signature object. + + + base constructor - with encapsulated content + + + return the collection of signers that are associated with the + signatures for the message. + + + return a X509Store containing the attribute certificates, if any, contained + in this message. + + @param type type of store to create + @return a store of attribute certificates + @exception NoSuchStoreException if the store type isn't available. + @exception CmsException if a general exception prevents creation of the X509Store + + + return a X509Store containing the public key certificates, if any, contained + in this message. + + @param type type of store to create + @return a store of public key certificates + @exception NoSuchStoreException if the store type isn't available. + @exception CmsException if a general exception prevents creation of the X509Store + + + return a X509Store containing CRLs, if any, contained + in this message. + + @param type type of store to create + @return a store of CRLs + @exception NoSuchStoreException if the store type isn't available. + @exception CmsException if a general exception prevents creation of the X509Store + + + return the ASN.1 encoded representation of this object. + + + Replace the signerinformation store associated with this + CmsSignedData object with the new one passed in. You would + probably only want to do this if you wanted to change the unsigned + attributes associated with a signer, or perhaps delete one. + + @param signedData the signed data object to be used as a base. + @param signerInformationStore the new signer information store to use. + @return a new signed data object. + + + Replace the certificate and CRL information associated with this + CmsSignedData object with the new one passed in. + + @param signedData the signed data object to be used as a base. + @param x509Certs the new certificates to be used. + @param x509Crls the new CRLs to be used. + @return a new signed data object. + @exception CmsException if there is an error processing the stores + + + Return the version number for this object. + + + + Return the DerObjectIdentifier associated with the encapsulated + content info structure carried in the signed data. + + + + return the ContentInfo + + + * general class for generating a pkcs7-signature message. + *

    + * A simple example of usage. + * + *

    +                 *      IX509Store certs...
    +                 *      IX509Store crls...
    +                 *      CmsSignedDataGenerator gen = new CmsSignedDataGenerator();
    +                 *
    +                 *      gen.AddSigner(privKey, cert, CmsSignedGenerator.DigestSha1);
    +                 *      gen.AddCertificates(certs);
    +                 *      gen.AddCrls(crls);
    +                 *
    +                 *      CmsSignedData data = gen.Generate(content);
    +                 * 
    + *

    +
    + + Default type for the signed data. + + + Constructor allowing specific source of randomness + Instance of SecureRandom to use. + + + Add the attribute certificates contained in the passed in store to the + generator. + + @param store a store of Version 2 attribute certificates + @throws CmsException if an error occurse processing the store. + + + Add a store of precalculated signers to the generator. + + @param signerStore store of signers + + + Return a map of oids and byte arrays representing the digests calculated on the content during + the last generate. + + @return a map of oids (as String objects) and byte[] representing digests. + + + Constructor allowing specific source of randomness + Instance of SecureRandom to use. + + + * add a signer - no attributes other than the default ones will be + * provided here. + * + * @param key signing key to use + * @param cert certificate containing corresponding public key + * @param digestOID digest algorithm OID + + + add a signer, specifying the digest encryption algorithm to use - no attributes other than the default ones will be + provided here. + + @param key signing key to use + @param cert certificate containing corresponding public key + @param encryptionOID digest encryption algorithm OID + @param digestOID digest algorithm OID + + + add a signer - no attributes other than the default ones will be + provided here. + + + add a signer, specifying the digest encryption algorithm to use - no attributes other than the default ones will be + provided here. + + + * add a signer with extra signed/unsigned attributes. + * + * @param key signing key to use + * @param cert certificate containing corresponding public key + * @param digestOID digest algorithm OID + * @param signedAttr table of attributes to be included in signature + * @param unsignedAttr table of attributes to be included as unsigned + + + add a signer, specifying the digest encryption algorithm, with extra signed/unsigned attributes. + + @param key signing key to use + @param cert certificate containing corresponding public key + @param encryptionOID digest encryption algorithm OID + @param digestOID digest algorithm OID + @param signedAttr table of attributes to be included in signature + @param unsignedAttr table of attributes to be included as unsigned + + + * add a signer with extra signed/unsigned attributes. + * + * @param key signing key to use + * @param subjectKeyID subjectKeyID of corresponding public key + * @param digestOID digest algorithm OID + * @param signedAttr table of attributes to be included in signature + * @param unsignedAttr table of attributes to be included as unsigned + + + add a signer, specifying the digest encryption algorithm, with extra signed/unsigned attributes. + + @param key signing key to use + @param subjectKeyID subjectKeyID of corresponding public key + @param encryptionOID digest encryption algorithm OID + @param digestOID digest algorithm OID + @param signedAttr table of attributes to be included in signature + @param unsignedAttr table of attributes to be included as unsigned + + + add a signer with extra signed/unsigned attributes based on generators. + + + add a signer, specifying the digest encryption algorithm, with extra signed/unsigned attributes based on generators. + + + add a signer with extra signed/unsigned attributes based on generators. + + + add a signer, including digest encryption algorithm, with extra signed/unsigned attributes based on generators. + + + generate a signed object that for a CMS Signed Data object + + + generate a signed object that for a CMS Signed Data + object - if encapsulate is true a copy + of the message will be included in the signature. The content type + is set according to the OID represented by the string signedContentType. + + + generate a signed object that for a CMS Signed Data + object - if encapsulate is true a copy + of the message will be included in the signature with the + default content type "data". + + + generate a set of one or more SignerInformation objects representing counter signatures on + the passed in SignerInformation object. + + @param signer the signer to be countersigned + @param sigProvider the provider to be used for counter signing. + @return a store containing the signers. + + + Parsing class for an CMS Signed Data object from an input stream. +

    + Note: that because we are in a streaming mode only one signer can be tried and it is important + that the methods on the parser are called in the appropriate order. +

    +

    + A simple example of usage for an encapsulated signature. +

    +

    + Two notes: first, in the example below the validity of + the certificate isn't verified, just the fact that one of the certs + matches the given signer, and, second, because we are in a streaming + mode the order of the operations is important. +

    +
    +                  CmsSignedDataParser     sp = new CmsSignedDataParser(encapSigData);
    +            
    +                  sp.GetSignedContent().Drain();
    +            
    +                  IX509Store              certs = sp.GetCertificates();
    +                  SignerInformationStore  signers = sp.GetSignerInfos();
    +            
    +                  foreach (SignerInformation signer in signers.GetSigners())
    +                  {
    +                      ArrayList       certList = new ArrayList(certs.GetMatches(signer.SignerID));
    +                      X509Certificate cert = (X509Certificate) certList[0];
    +            
    +                      Console.WriteLine("verify returns: " + signer.Verify(cert));
    +                  }
    +             
    + Note also: this class does not introduce buffering - if you are processing large files you should create + the parser with: +
    +                      CmsSignedDataParser     ep = new CmsSignedDataParser(new BufferedInputStream(encapSigData, bufSize));
    +              
    + where bufSize is a suitably large buffer size. +
    + + base constructor - with encapsulated content + + + base constructor + + @param signedContent the content that was signed. + @param sigData the signature object. + + + return the collection of signers that are associated with the + signatures for the message. + @throws CmsException + + + return a X509Store containing the attribute certificates, if any, contained + in this message. + + @param type type of store to create + @return a store of attribute certificates + @exception org.bouncycastle.x509.NoSuchStoreException if the store type isn't available. + @exception CmsException if a general exception prevents creation of the X509Store + + + return a X509Store containing the public key certificates, if any, contained + in this message. + + @param type type of store to create + @return a store of public key certificates + @exception NoSuchStoreException if the store type isn't available. + @exception CmsException if a general exception prevents creation of the X509Store + + + return a X509Store containing CRLs, if any, contained + in this message. + + @param type type of store to create + @return a store of CRLs + @exception NoSuchStoreException if the store type isn't available. + @exception CmsException if a general exception prevents creation of the X509Store + + + Replace the signerinformation store associated with the passed + in message contained in the stream original with the new one passed in. + You would probably only want to do this if you wanted to change the unsigned + attributes associated with a signer, or perhaps delete one. +

    + The output stream is returned unclosed. +

    + @param original the signed data stream to be used as a base. + @param signerInformationStore the new signer information store to use. + @param out the stream to Write the new signed data object to. + @return out. +
    + + Replace the certificate and CRL information associated with this + CMSSignedData object with the new one passed in. +

    + The output stream is returned unclosed. +

    + @param original the signed data stream to be used as a base. + @param certsAndCrls the new certificates and CRLs to be used. + @param out the stream to Write the new signed data object to. + @return out. + @exception CmsException if there is an error processing the CertStore +
    + + Return the version number for the SignedData object + + @return the version number + + + + Return the DerObjectIdentifier associated with the encapsulated + content info structure carried in the signed data. + + + + General class for generating a pkcs7-signature message stream. +

    + A simple example of usage. +

    +
    +                  IX509Store                   certs...
    +                  CmsSignedDataStreamGenerator gen = new CmsSignedDataStreamGenerator();
    +            
    +                  gen.AddSigner(privateKey, cert, CmsSignedDataStreamGenerator.DIGEST_SHA1);
    +            
    +                  gen.AddCertificates(certs);
    +            
    +                  Stream sigOut = gen.Open(bOut);
    +            
    +                  sigOut.Write(Encoding.UTF8.GetBytes("Hello World!"));
    +            
    +                  sigOut.Close();
    +             
    +
    + + Constructor allowing specific source of randomness + Instance of SecureRandom to use. + + + Set the underlying string size for encapsulated data + + @param bufferSize length of octet strings to buffer the data. + + + add a signer - no attributes other than the default ones will be + provided here. + @throws NoSuchAlgorithmException + @throws InvalidKeyException + + + add a signer, specifying the digest encryption algorithm - no attributes other than the default ones will be + provided here. + @throws NoSuchProviderException + @throws NoSuchAlgorithmException + @throws InvalidKeyException + + + add a signer with extra signed/unsigned attributes. + @throws NoSuchAlgorithmException + @throws InvalidKeyException + + + add a signer with extra signed/unsigned attributes - specifying digest + encryption algorithm. + @throws NoSuchProviderException + @throws NoSuchAlgorithmException + @throws InvalidKeyException + + + add a signer - no attributes other than the default ones will be + provided here. + @throws NoSuchAlgorithmException + @throws InvalidKeyException + + + add a signer - no attributes other than the default ones will be + provided here. + @throws NoSuchProviderException + @throws NoSuchAlgorithmException + @throws InvalidKeyException + + + add a signer with extra signed/unsigned attributes. + @throws NoSuchAlgorithmException + @throws InvalidKeyException + + + generate a signed object that for a CMS Signed Data object + + + generate a signed object that for a CMS Signed Data + object - if encapsulate is true a copy + of the message will be included in the signature with the + default content type "data". + + + generate a signed object that for a CMS Signed Data + object using the given provider - if encapsulate is true a copy + of the message will be included in the signature with the + default content type "data". If dataOutputStream is non null the data + being signed will be written to the stream as it is processed. + @param out stream the CMS object is to be written to. + @param encapsulate true if data should be encapsulated. + @param dataOutputStream output stream to copy the data being signed to. + + + generate a signed object that for a CMS Signed Data + object - if encapsulate is true a copy + of the message will be included in the signature. The content type + is set according to the OID represented by the string signedContentType. + + + generate a signed object that for a CMS Signed Data + object using the given provider - if encapsulate is true a copy + of the message will be included in the signature. The content type + is set according to the OID represented by the string signedContentType. + @param out stream the CMS object is to be written to. + @param signedContentType OID for data to be signed. + @param encapsulate true if data should be encapsulated. + @param dataOutputStream output stream to copy the data being signed to. + + + Return the digest algorithm using one of the standard JCA string + representations rather than the algorithm identifier (if possible). + + + Return the digest encryption algorithm using one of the standard + JCA string representations rather than the algorithm identifier (if + possible). + + + Default authenticated attributes generator. + + + Initialise to use all defaults + + + Initialise with some extra attributes or overrides. + + @param attributeTable initial attribute table to use. + + + Create a standard attribute table from the passed in parameters - this will + normally include contentType and messageDigest. If the constructor + using an AttributeTable was used, entries in it for contentType and + messageDigest will override the generated ones. + + @param parameters source parameters for table generation. + + @return a filled in IDictionary of attributes. + + + @param parameters source parameters + @return the populated attribute table + + + Default signed attributes generator. + + + Initialise to use all defaults + + + Initialise with some extra attributes or overrides. + + @param attributeTable initial attribute table to use. + + + Create a standard attribute table from the passed in parameters - this will + normally include contentType, signingTime, and messageDigest. If the constructor + using an AttributeTable was used, entries in it for contentType, signingTime, and + messageDigest will override the generated ones. + + @param parameters source parameters for table generation. + + @return a filled in Hashtable of attributes. + + + @param parameters source parameters + @return the populated attribute table + + + + Generate a RecipientInfo object for the given key. + + + A + + + A + + + A + + + + + the RecipientInfo class for a recipient who has been sent a message + encrypted using a secret key known to the other side. + + + Return the MAC calculated for the content stream. Note: this call is only meaningful once all + the content has been read. + + @return byte array containing the mac. + + + * return the object identifier for the key encryption algorithm. + * + * @return OID for key encryption algorithm. + + + * return the ASN.1 encoded key encryption algorithm parameters, or null if + * there aren't any. + * + * @return ASN.1 encoding of key encryption algorithm parameters. + + + decrypt the content and return an input stream. + + + the RecipientInfo class for a recipient who has been sent a message + encrypted using key agreement. + + + decrypt the content and return an input stream. + + + the KeyTransRecipientInformation class for a recipient who has been sent a secret + key encrypted using their public key that needs to be used to + extract the message. + + + decrypt the content and return it as a byte array. + + + a basic index for an originator. + + + + An ISet of DerObjectIdentifier objects. + + + + Return the certificates stored in the underlying OriginatorInfo object. + + @return a Store of X509CertificateHolder objects. + + + Return the CRLs stored in the underlying OriginatorInfo object. + + @return a Store of X509CRLHolder objects. + + + Return the underlying ASN.1 object defining this SignerInformation object. + + @return a OriginatorInfo. + + + the RecipientInfo class for a recipient who has been sent a message + encrypted using a password. + + + decrypt the content and return an input stream. + + + return the object identifier for the key derivation algorithm, or null + if there is none present. + + @return OID for key derivation algorithm, if present. + + + + PKCS5 scheme-2 - password converted to bytes assuming ASCII. + + + + PKCS5 scheme-2 - password converted to bytes using UTF-8. + + + Return the first RecipientInformation object that matches the + passed in selector. Null if there are no matches. + + @param selector to identify a recipient + @return a single RecipientInformation object. Null if none matches. + + + Return all recipients in the collection + + @return a collection of recipients. + + + Return possible empty collection with recipients matching the passed in RecipientID + + @param selector a recipient id to select against. + @return a collection of RecipientInformation objects. + + + Return the number of recipients in the collection. + + @return number of recipients identified. + + + a basic index for a signer. + + + If the passed in flag is true, the signer signature will be based on the data, not + a collection of signed attributes, and no signed attributes will be included. + + @return the builder object + + + Provide a custom signed attribute generator. + + @param signedGen a generator of signed attributes. + @return the builder object + + + Provide a generator of unsigned attributes. + + @param unsignedGen a generator for signed attributes. + @return the builder object + + + Build a generator with the passed in certHolder issuer and serial number as the signerIdentifier. + + @param contentSigner operator for generating the final signature in the SignerInfo with. + @param certHolder carrier for the X.509 certificate related to the contentSigner. + @return a SignerInfoGenerator + @throws OperatorCreationException if the generator cannot be built. + + + Build a generator with the passed in subjectKeyIdentifier as the signerIdentifier. If used you should + try to follow the calculation described in RFC 5280 section 4.2.1.2. + + @param signerFactory operator factory for generating the final signature in the SignerInfo with. + @param subjectKeyIdentifier key identifier to identify the public key for verifying the signature. + @return a SignerInfoGenerator + + + an expanded SignerInfo block from a CMS Signed message + + + return the content digest that was calculated during verification. + + + return the encoded signature + + + Return a SignerInformationStore containing the counter signatures attached to this + signer. If no counter signatures are present an empty store is returned. + + + return the DER encoding of the signed attributes. + @throws IOException if an encoding error occurs. + + + verify that the given public key successfully handles and confirms the + signature associated with this signer. + + + verify that the given certificate successfully handles and confirms + the signature associated with this signer and, if a signingTime + attribute is available, that the certificate was valid at the time the + signature was generated. + + + Return the base ASN.1 CMS structure that this object contains. + + @return an object containing a CMS SignerInfo structure. + + + Return a signer information object with the passed in unsigned + attributes replacing the ones that are current associated with + the object passed in. + + @param signerInformation the signerInfo to be used as the basis. + @param unsignedAttributes the unsigned attributes to add. + @return a copy of the original SignerInformationObject with the changed attributes. + + + Return a signer information object with passed in SignerInformationStore representing counter + signatures attached as an unsigned attribute. + + @param signerInformation the signerInfo to be used as the basis. + @param counterSigners signer info objects carrying counter signature. + @return a copy of the original SignerInformationObject with the changed attributes. + + + return the version number for this objects underlying SignerInfo structure. + + + return the object identifier for the signature. + + + return the signature parameters, or null if there aren't any. + + + return the object identifier for the signature. + + + return the signature/encryption algorithm parameters, or null if + there aren't any. + + + return a table of the signed attributes - indexed by + the OID of the attribute. + + + return a table of the unsigned attributes indexed by + the OID of the attribute. + + + Create a store containing a single SignerInformation object. + + @param signerInfo the signer information to contain. + + + Create a store containing a collection of SignerInformation objects. + + @param signerInfos a collection signer information objects to contain. + + + Return the first SignerInformation object that matches the + passed in selector. Null if there are no matches. + + @param selector to identify a signer + @return a single SignerInformation object. Null if none matches. + + + An ICollection of all signers in the collection + + + Return possible empty collection with signers matching the passed in SignerID + + @param selector a signer id to select against. + @return a collection of SignerInformation objects. + + + The number of signers in the collection. + + + Basic generator that just returns a preconstructed attribute table + + + a Diffie-Hellman key exchange engine. +

    + note: This uses MTI/A0 key agreement in order to make the key agreement + secure against passive attacks. If you're doing Diffie-Hellman and both + parties have long term public keys you should look at using this. For + further information have a look at RFC 2631.

    +

    + It's possible to extend this to more than two parties as well, for the moment + that is left as an exercise for the reader.

    +
    + + calculate our initial message. + + + given a message from a given party and the corresponding public key + calculate the next message in the agreement sequence. In this case + this will represent the shared secret. + + + a Diffie-Hellman key agreement class. +

    + note: This is only the basic algorithm, it doesn't take advantage of + long term public keys if they are available. See the DHAgreement class + for a "better" implementation.

    +
    + + The basic interface that basic Diffie-Hellman implementations + conforms to. + + + initialise the agreement engine. + + + return the field size for the agreement algorithm in bytes. + + + given a public key from a given party calculate the next + message in the agreement sequence. + + + given a short term public key from a given party calculate the next + message in the agreement sequence. + + + Standard Diffie-Hellman groups from various IETF specifications. + + + P1363 7.2.1 ECSVDP-DH + + ECSVDP-DH is Elliptic Curve Secret Value Derivation Primitive, + Diffie-Hellman version. It is based on the work of [DH76], [Mil86], + and [Kob87]. This primitive derives a shared secret value from one + party's private key and another party's public key, where both have + the same set of EC domain parameters. If two parties correctly + execute this primitive, they will produce the same output. This + primitive can be invoked by a scheme to derive a shared secret key; + specifically, it may be used with the schemes ECKAS-DH1 and + DL/ECKAS-DH2. It assumes that the input keys are valid (see also + Section 7.2.2). + + + P1363 7.2.2 ECSVDP-DHC + + ECSVDP-DHC is Elliptic Curve Secret Value Derivation Primitive, + Diffie-Hellman version with cofactor multiplication. It is based on + the work of [DH76], [Mil86], [Kob87], [LMQ98] and [Kal98a]. This + primitive derives a shared secret value from one party's private key + and another party's public key, where both have the same set of EC + domain parameters. If two parties correctly execute this primitive, + they will produce the same output. This primitive can be invoked by a + scheme to derive a shared secret key; specifically, it may be used + with the schemes ECKAS-DH1 and DL/ECKAS-DH2. It does not assume the + validity of the input public key (see also Section 7.2.1). +

    + Note: As stated P1363 compatibility mode with ECDH can be preset, and + in this case the implementation doesn't have a ECDH compatibility mode + (if you want that just use ECDHBasicAgreement and note they both implement + BasicAgreement!).

    +
    + + + A participant in a Password Authenticated Key Exchange by Juggling (J-PAKE) exchange. + + The J-PAKE exchange is defined by Feng Hao and Peter Ryan in the paper + + "Password Authenticated Key Exchange by Juggling, 2008." + + The J-PAKE protocol is symmetric. + There is no notion of a client or server, but rather just two participants. + An instance of JPakeParticipant represents one participant, and + is the primary interface for executing the exchange. + + To execute an exchange, construct a JPakeParticipant on each end, + and call the following 7 methods + (once and only once, in the given order, for each participant, sending messages between them as described): + + CreateRound1PayloadToSend() - and send the payload to the other participant + ValidateRound1PayloadReceived(JPakeRound1Payload) - use the payload received from the other participant + CreateRound2PayloadToSend() - and send the payload to the other participant + ValidateRound2PayloadReceived(JPakeRound2Payload) - use the payload received from the other participant + CalculateKeyingMaterial() + CreateRound3PayloadToSend(BigInteger) - and send the payload to the other participant + ValidateRound3PayloadReceived(JPakeRound3Payload, BigInteger) - use the payload received from the other participant + + Each side should derive a session key from the keying material returned by CalculateKeyingMaterial(). + The caller is responsible for deriving the session key using a secure key derivation function (KDF). + + Round 3 is an optional key confirmation process. + If you do not execute round 3, then there is no assurance that both participants are using the same key. + (i.e. if the participants used different passwords, then their session keys will differ.) + + If the round 3 validation succeeds, then the keys are guaranteed to be the same on both sides. + + The symmetric design can easily support the asymmetric cases when one party initiates the communication. + e.g. Sometimes the round1 payload and round2 payload may be sent in one pass. + Also, in some cases, the key confirmation payload can be sent together with the round2 payload. + These are the trivial techniques to optimize the communication. + + The key confirmation process is implemented as specified in + NIST SP 800-56A Revision 1, + Section 8.2 Unilateral Key Confirmation for Key Agreement Schemes. + + This class is stateful and NOT threadsafe. + Each instance should only be used for ONE complete J-PAKE exchange + (i.e. a new JPakeParticipant should be constructed for each new J-PAKE exchange). + + + + + Convenience constructor for a new JPakeParticipant that uses + the JPakePrimeOrderGroups#NIST_3072 prime order group, + a SHA-256 digest, and a default SecureRandom implementation. + + After construction, the State state will be STATE_INITIALIZED. + + Throws NullReferenceException if any argument is null. Throws + ArgumentException if password is empty. + + Unique identifier of this participant. + The two participants in the exchange must NOT share the same id. + Shared secret. + A defensive copy of this array is made (and cleared once CalculateKeyingMaterial() is called). + Caller should clear the input password as soon as possible. + + + + Convenience constructor for a new JPakeParticipant that uses + a SHA-256 digest, and a default SecureRandom implementation. + + After construction, the State state will be STATE_INITIALIZED. + + Throws NullReferenceException if any argument is null. Throws + ArgumentException if password is empty. + + Unique identifier of this participant. + The two participants in the exchange must NOT share the same id. + Shared secret. + A defensive copy of this array is made (and cleared once CalculateKeyingMaterial() is called). + Caller should clear the input password as soon as possible. + Prime order group. See JPakePrimeOrderGroups for standard groups. + + + + Constructor for a new JPakeParticipant. + + After construction, the State state will be STATE_INITIALIZED. + + Throws NullReferenceException if any argument is null. Throws + ArgumentException if password is empty. + + Unique identifier of this participant. + The two participants in the exchange must NOT share the same id. + Shared secret. + A defensive copy of this array is made (and cleared once CalculateKeyingMaterial() is called). + Caller should clear the input password as soon as possible. + Prime order group. See JPakePrimeOrderGroups for standard groups. + Digest to use during zero knowledge proofs and key confirmation + (SHA-256 or stronger preferred). + Source of secure random data for x1 and x2, and for the zero knowledge proofs. + + + + Creates and returns the payload to send to the other participant during round 1. + + After execution, the State state} will be STATE_ROUND_1_CREATED}. + + + + + Validates the payload received from the other participant during round 1. + + Must be called prior to CreateRound2PayloadToSend(). + + After execution, the State state will be STATE_ROUND_1_VALIDATED. + + Throws CryptoException if validation fails. Throws InvalidOperationException + if called multiple times. + + + + + Creates and returns the payload to send to the other participant during round 2. + + ValidateRound1PayloadReceived(JPakeRound1Payload) must be called prior to this method. + + After execution, the State state will be STATE_ROUND_2_CREATED. + + Throws InvalidOperationException if called prior to ValidateRound1PayloadReceived(JPakeRound1Payload), or multiple times + + + + + Validates the payload received from the other participant during round 2. + Note that this DOES NOT detect a non-common password. + The only indication of a non-common password is through derivation + of different keys (which can be detected explicitly by executing round 3 and round 4) + + Must be called prior to CalculateKeyingMaterial(). + + After execution, the State state will be STATE_ROUND_2_VALIDATED. + + Throws CryptoException if validation fails. Throws + InvalidOperationException if called prior to ValidateRound1PayloadReceived(JPakeRound1Payload), or multiple times + + + + + Calculates and returns the key material. + A session key must be derived from this key material using a secure key derivation function (KDF). + The KDF used to derive the key is handled externally (i.e. not by JPakeParticipant). + + The keying material will be identical for each participant if and only if + each participant's password is the same. i.e. If the participants do not + share the same password, then each participant will derive a different key. + Therefore, if you immediately start using a key derived from + the keying material, then you must handle detection of incorrect keys. + If you want to handle this detection explicitly, you can optionally perform + rounds 3 and 4. See JPakeParticipant for details on how to execute + rounds 3 and 4. + + The keying material will be in the range [0, p-1]. + + ValidateRound2PayloadReceived(JPakeRound2Payload) must be called prior to this method. + + As a side effect, the internal password array is cleared, since it is no longer needed. + + After execution, the State state will be STATE_KEY_CALCULATED. + + Throws InvalidOperationException if called prior to ValidateRound2PayloadReceived(JPakeRound2Payload), + or if called multiple times. + + + + + Creates and returns the payload to send to the other participant during round 3. + + See JPakeParticipant for more details on round 3. + + After execution, the State state} will be STATE_ROUND_3_CREATED. + Throws InvalidOperationException if called prior to CalculateKeyingMaterial, or multiple + times. + + The keying material as returned from CalculateKeyingMaterial(). + + + + Validates the payload received from the other participant during round 3. + + See JPakeParticipant for more details on round 3. + + After execution, the State state will be STATE_ROUND_3_VALIDATED. + + Throws CryptoException if validation fails. Throws InvalidOperationException if called prior to + CalculateKeyingMaterial or multiple times + + The round 3 payload received from the other participant. + The keying material as returned from CalculateKeyingMaterial(). + + + + Gets the current state of this participant. + See the STATE_* constants for possible values. + + + + + A pre-computed prime order group for use during a J-PAKE exchange. + + Typically a Schnorr group is used. In general, J-PAKE can use any prime order group + that is suitable for public key cryptography, including elliptic curve cryptography. + + See JPakePrimeOrderGroups for convenient standard groups. + + NIST publishes + many groups that can be used for the desired level of security. + + + + + Constructs a new JPakePrimeOrderGroup. + + In general, you should use one of the pre-approved groups from + JPakePrimeOrderGroups, rather than manually constructing one. + + The following basic checks are performed: + + p-1 must be evenly divisible by q + g must be in [2, p-1] + g^q mod p must equal 1 + p must be prime (within reasonably certainty) + q must be prime (within reasonably certainty) + + The prime checks are performed using BigInteger#isProbablePrime(int), + and are therefore subject to the same probability guarantees. + + These checks prevent trivial mistakes. + However, due to the small uncertainties if p and q are not prime, + advanced attacks are not prevented. + Use it at your own risk. + + Throws NullReferenceException if any argument is null. Throws + InvalidOperationException is any of the above validations fail. + + + + + Constructor used by the pre-approved groups in JPakePrimeOrderGroups. + These pre-approved groups can avoid the expensive checks. + User-specified groups should not use this constructor. + + + + + Standard pre-computed prime order groups for use by J-PAKE. + (J-PAKE can use pre-computed prime order groups, same as DSA and Diffie-Hellman.) +

    + This class contains some convenient constants for use as input for + constructing {@link JPAKEParticipant}s. +

    + The prime order groups below are taken from Sun's JDK JavaDoc (docs/guide/security/CryptoSpec.html#AppB), + and from the prime order groups + published by NIST. +

    +
    + + + From Sun's JDK JavaDoc (docs/guide/security/CryptoSpec.html#AppB) + 1024-bit p, 160-bit q and 1024-bit g for 80-bit security. + + + + + From NIST. + 2048-bit p, 224-bit q and 2048-bit g for 112-bit security. + + + + + From NIST. + 3072-bit p, 256-bit q and 3072-bit g for 128-bit security. + + + + + The payload sent/received during the first round of a J-PAKE exchange. + + Each JPAKEParticipant creates and sends an instance of this payload to + the other. The payload to send should be created via + JPAKEParticipant.CreateRound1PayloadToSend(). + + Each participant must also validate the payload received from the other. + The received payload should be validated via + JPAKEParticipant.ValidateRound1PayloadReceived(JPakeRound1Payload). + + + + + The id of the JPAKEParticipant who created/sent this payload. + + + + + The value of g^x1 + + + + + The value of g^x2 + + + + + The zero knowledge proof for x1. + + This is a two element array, containing {g^v, r} for x1. + + + + + The zero knowledge proof for x2. + + This is a two element array, containing {g^v, r} for x2. + + + + + The payload sent/received during the second round of a J-PAKE exchange. + + Each JPAKEParticipant creates and sends an instance + of this payload to the other JPAKEParticipant. + The payload to send should be created via + JPAKEParticipant#createRound2PayloadToSend() + + Each JPAKEParticipant must also validate the payload + received from the other JPAKEParticipant. + The received payload should be validated via + JPAKEParticipant#validateRound2PayloadReceived(JPakeRound2Payload) + + + + + The id of the JPAKEParticipant who created/sent this payload. + + + + + The value of A, as computed during round 2. + + + + + The zero knowledge proof for x2 * s. + + This is a two element array, containing {g^v, r} for x2 * s. + + + + + The payload sent/received during the optional third round of a J-PAKE exchange, + which is for explicit key confirmation. + + Each JPAKEParticipant creates and sends an instance + of this payload to the other JPAKEParticipant. + The payload to send should be created via + JPAKEParticipant#createRound3PayloadToSend(BigInteger) + + Eeach JPAKEParticipant must also validate the payload + received from the other JPAKEParticipant. + The received payload should be validated via + JPAKEParticipant#validateRound3PayloadReceived(JPakeRound3Payload, BigInteger) + + + + + The id of the {@link JPAKEParticipant} who created/sent this payload. + + + + + The value of MacTag, as computed by round 3. + + See JPAKEUtil#calculateMacTag(string, string, BigInteger, BigInteger, BigInteger, BigInteger, BigInteger, org.bouncycastle.crypto.Digest) + + + + + Primitives needed for a J-PAKE exchange. + + The recommended way to perform a J-PAKE exchange is by using + two JPAKEParticipants. Internally, those participants + call these primitive operations in JPakeUtilities. + + The primitives, however, can be used without a JPAKEParticipant if needed. + + + + + Return a value that can be used as x1 or x3 during round 1. + The returned value is a random value in the range [0, q-1]. + + + + + Return a value that can be used as x2 or x4 during round 1. + The returned value is a random value in the range [1, q-1]. + + + + + Converts the given password to a BigInteger + for use in arithmetic calculations. + + + + + Calculate g^x mod p as done in round 1. + + + + + Calculate ga as done in round 2. + + + + + Calculate x2 * s as done in round 2. + + + + + Calculate A as done in round 2. + + + + + Calculate a zero knowledge proof of x using Schnorr's signature. + The returned array has two elements {g^v, r = v-x*h} for x. + + + + + Validates that g^x4 is not 1. + throws CryptoException if g^x4 is 1 + + + + + Validates that ga is not 1. + + As described by Feng Hao... + Alice could simply check ga != 1 to ensure it is a generator. + In fact, as we will explain in Section 3, (x1 + x3 + x4 ) is random over Zq even in the face of active attacks. + Hence, the probability for ga = 1 is extremely small - on the order of 2^160 for 160-bit q. + + throws CryptoException if ga is 1 + + + + + Validates the zero knowledge proof (generated by + calculateZeroKnowledgeProof(BigInteger, BigInteger, BigInteger, BigInteger, BigInteger, string, Digest, SecureRandom) + is correct. + + throws CryptoException if the zero knowledge proof is not correct + + + + + Calculates the keying material, which can be done after round 2 has completed. + A session key must be derived from this key material using a secure key derivation function (KDF). + The KDF used to derive the key is handled externally (i.e. not by JPAKEParticipant). + + KeyingMaterial = (B/g^{x2*x4*s})^x2 + + + + + Validates that the given participant ids are not equal. + (For the J-PAKE exchange, each participant must use a unique id.) + + Throws CryptoException if the participantId strings are equal. + + + + + Validates that the given participant ids are equal. + This is used to ensure that the payloads received from + each round all come from the same participant. + + + + + Validates that the given object is not null. + throws NullReferenceException if the object is null. + + object in question + name of the object (to be used in exception message) + + + + Calculates the MacTag (to be used for key confirmation), as defined by + NIST SP 800-56A Revision 1, + Section 8.2 Unilateral Key Confirmation for Key Agreement Schemes. + + MacTag = HMAC(MacKey, MacLen, MacData) + MacKey = H(K || "JPAKE_KC") + MacData = "KC_1_U" || participantId || partnerParticipantId || gx1 || gx2 || gx3 || gx4 + + Note that both participants use "KC_1_U" because the sender of the round 3 message + is always the initiator for key confirmation. + + HMAC = {@link HMac} used with the given {@link Digest} + H = The given {@link Digest} + MacLen = length of MacTag + + + + + Calculates the MacKey (i.e. the key to use when calculating the MagTag for key confirmation). + + MacKey = H(K || "JPAKE_KC") + + + + + Validates the MacTag received from the partner participant. + + throws CryptoException if the participantId strings are equal. + + + + Parameters for key/byte stream derivation classes + + + RFC 2631 Diffie-hellman KEK derivation function. + + + base interface for general purpose byte derivation functions. + + + return the message digest used as the basis for the function + + + X9.63 based key derivation function for ECDH CMS. + + + Implements the client side SRP-6a protocol. Note that this class is stateful, and therefore NOT threadsafe. + This implementation of SRP is based on the optimized message sequence put forth by Thomas Wu in the paper + "SRP-6: Improvements and Refinements to the Secure Remote Password Protocol, 2002" + + + Initialises the client to begin new authentication attempt + @param N The safe prime associated with the client's verifier + @param g The group parameter associated with the client's verifier + @param digest The digest algorithm associated with the client's verifier + @param random For key generation + + + Generates client's credentials given the client's salt, identity and password + @param salt The salt used in the client's verifier. + @param identity The user's identity (eg. username) + @param password The user's password + @return Client's public value to send to server + + + Generates client's verification message given the server's credentials + @param serverB The server's credentials + @return Client's verification message for the server + @throws CryptoException If server's credentials are invalid + + + Computes the client evidence message M1 using the previously received values. + To be called after calculating the secret S. + @return M1: the client side generated evidence message + @throws CryptoException + + + Authenticates the server evidence message M2 received and saves it only if correct. + @param M2: the server side generated evidence message + @return A boolean indicating if the server message M2 was the expected one. + @throws CryptoException + + + Computes the final session key as a result of the SRP successful mutual authentication + To be called after verifying the server evidence message M2. + @return Key: the mutually authenticated symmetric session key + @throws CryptoException + + + Implements the server side SRP-6a protocol. Note that this class is stateful, and therefore NOT threadsafe. + This implementation of SRP is based on the optimized message sequence put forth by Thomas Wu in the paper + "SRP-6: Improvements and Refinements to the Secure Remote Password Protocol, 2002" + + + Initialises the server to accept a new client authentication attempt + @param N The safe prime associated with the client's verifier + @param g The group parameter associated with the client's verifier + @param v The client's verifier + @param digest The digest algorithm associated with the client's verifier + @param random For key generation + + + Generates the server's credentials that are to be sent to the client. + @return The server's public value to the client + + + Processes the client's credentials. If valid the shared secret is generated and returned. + @param clientA The client's credentials + @return A shared secret BigInteger + @throws CryptoException If client's credentials are invalid + + + Authenticates the received client evidence message M1 and saves it only if correct. + To be called after calculating the secret S. + @param M1: the client side generated evidence message + @return A boolean indicating if the client message M1 was the expected one. + @throws CryptoException + + + Computes the server evidence message M2 using the previously verified values. + To be called after successfully verifying the client evidence message M1. + @return M2: the server side generated evidence message + @throws CryptoException + + + Computes the final session key as a result of the SRP successful mutual authentication + To be called after calculating the server evidence message M2. + @return Key: the mutual authenticated symmetric session key + @throws CryptoException + + + Computes the client evidence message (M1) according to the standard routine: + M1 = H( A | B | S ) + @param digest The Digest used as the hashing function H + @param N Modulus used to get the pad length + @param A The public client value + @param B The public server value + @param S The secret calculated by both sides + @return M1 The calculated client evidence message + + + Computes the server evidence message (M2) according to the standard routine: + M2 = H( A | M1 | S ) + @param digest The Digest used as the hashing function H + @param N Modulus used to get the pad length + @param A The public client value + @param M1 The client evidence message + @param S The secret calculated by both sides + @return M2 The calculated server evidence message + + + Computes the final Key according to the standard routine: Key = H(S) + @param digest The Digest used as the hashing function H + @param N Modulus used to get the pad length + @param S The secret calculated by both sides + @return + + + Generates new SRP verifier for user + + + Initialises generator to create new verifiers + @param N The safe prime to use (see DHParametersGenerator) + @param g The group parameter to use (see DHParametersGenerator) + @param digest The digest to use. The same digest type will need to be used later for the actual authentication + attempt. Also note that the final session key size is dependent on the chosen digest. + + + Creates a new SRP verifier + @param salt The salt to use, generally should be large and random + @param identity The user's identifying information (eg. username) + @param password The user's password + @return A new verifier for use in future SRP authentication + + + a holding class for public/private parameter pairs. + + + basic constructor. + + @param publicParam a public key parameters object. + @param privateParam the corresponding private key parameters. + + + return the public key parameters. + + @return the public key parameters. + + + return the private key parameters. + + @return the private key parameters. + + + The AEAD block ciphers already handle buffering internally, so this class + just takes care of implementing IBufferedCipher methods. + + + Block cipher engines are expected to conform to this interface. + + + Initialise the cipher. + If true the cipher is initialised for encryption, + if false for decryption. + The key and other data required by the cipher. + + + + Reset the cipher. After resetting the cipher is in the same state + as it was after the last init (if there was one). + + + + The name of the algorithm this cipher implements. + + + initialise the cipher. + + @param forEncryption if true the cipher is initialised for + encryption, if false for decryption. + @param param the key and other data required by the cipher. + @exception ArgumentException if the parameters argument is + inappropriate. + + + return the blocksize for the underlying cipher. + + @return the blocksize for the underlying cipher. + + + return the size of the output buffer required for an update + an input of len bytes. + + @param len the length of the input. + @return the space required to accommodate a call to update + with len bytes of input. + + + return the size of the output buffer required for an update plus a + doFinal with an input of len bytes. + + @param len the length of the input. + @return the space required to accommodate a call to update and doFinal + with len bytes of input. + + + process a single byte, producing an output block if necessary. + + @param in the input byte. + @param out the space for any output that might be produced. + @param outOff the offset from which the output will be copied. + @return the number of output bytes copied to out. + @exception DataLengthException if there isn't enough space in out. + @exception InvalidOperationException if the cipher isn't initialised. + + + process an array of bytes, producing output if necessary. + + @param in the input byte array. + @param inOff the offset at which the input data starts. + @param len the number of bytes to be copied out of the input array. + @param out the space for any output that might be produced. + @param outOff the offset from which the output will be copied. + @return the number of output bytes copied to out. + @exception DataLengthException if there isn't enough space in out. + @exception InvalidOperationException if the cipher isn't initialised. + + + Process the last block in the buffer. + + @param out the array the block currently being held is copied into. + @param outOff the offset at which the copying starts. + @return the number of output bytes copied to out. + @exception DataLengthException if there is insufficient space in out for + the output, or the input is not block size aligned and should be. + @exception InvalidOperationException if the underlying cipher is not + initialised. + @exception InvalidCipherTextException if padding is expected and not found. + @exception DataLengthException if the input is not block size + aligned. + + + Reset the buffer and cipher. After resetting the object is in the same + state as it was after the last init (if there was one). + + + a buffer wrapper for an asymmetric block cipher, allowing input + to be accumulated in a piecemeal fashion until final processing. + + + base constructor. + + @param cipher the cipher this buffering object wraps. + + + return the amount of data sitting in the buffer. + + @return the amount of data sitting in the buffer. + + + initialise the buffer and the underlying cipher. + + @param forEncryption if true the cipher is initialised for + encryption, if false for decryption. + @param param the key and other data required by the cipher. + + + process the contents of the buffer using the underlying + cipher. + + @return the result of the encryption/decryption process on the + buffer. + @exception InvalidCipherTextException if we are given a garbage block. + + + Reset the buffer + + + A wrapper class that allows block ciphers to be used to process data in + a piecemeal fashion. The BufferedBlockCipher outputs a block only when the + buffer is full and more data is being added, or on a doFinal. +

    + Note: in the case where the underlying cipher is either a CFB cipher or an + OFB one the last block may not be a multiple of the block size. +

    +
    + + constructor for subclasses + + + Create a buffered block cipher without padding. + + @param cipher the underlying block cipher this buffering object wraps. + false otherwise. + + + initialise the cipher. + + @param forEncryption if true the cipher is initialised for + encryption, if false for decryption. + @param param the key and other data required by the cipher. + @exception ArgumentException if the parameters argument is + inappropriate. + + + return the blocksize for the underlying cipher. + + @return the blocksize for the underlying cipher. + + + return the size of the output buffer required for an update + an input of len bytes. + + @param len the length of the input. + @return the space required to accommodate a call to update + with len bytes of input. + + + return the size of the output buffer required for an update plus a + doFinal with an input of len bytes. + + @param len the length of the input. + @return the space required to accommodate a call to update and doFinal + with len bytes of input. + + + process a single byte, producing an output block if necessary. + + @param in the input byte. + @param out the space for any output that might be produced. + @param outOff the offset from which the output will be copied. + @return the number of output bytes copied to out. + @exception DataLengthException if there isn't enough space in out. + @exception InvalidOperationException if the cipher isn't initialised. + + + process an array of bytes, producing output if necessary. + + @param in the input byte array. + @param inOff the offset at which the input data starts. + @param len the number of bytes to be copied out of the input array. + @param out the space for any output that might be produced. + @param outOff the offset from which the output will be copied. + @return the number of output bytes copied to out. + @exception DataLengthException if there isn't enough space in out. + @exception InvalidOperationException if the cipher isn't initialised. + + + Process the last block in the buffer. + + @param out the array the block currently being held is copied into. + @param outOff the offset at which the copying starts. + @return the number of output bytes copied to out. + @exception DataLengthException if there is insufficient space in out for + the output, or the input is not block size aligned and should be. + @exception InvalidOperationException if the underlying cipher is not + initialised. + @exception InvalidCipherTextException if padding is expected and not found. + @exception DataLengthException if the input is not block size + aligned. + + + Reset the buffer and cipher. After resetting the object is in the same + state as it was after the last init (if there was one). + + + The base class for symmetric, or secret, cipher key generators. + + + initialise the key generator. + + @param param the parameters to be used for key generation + + + Generate a secret key. + + @return a byte array containing the key value. + + + this exception is thrown if a buffer that is meant to have output + copied into it turns out to be too short, or if we've been given + insufficient input. In general this exception will Get thrown rather + than an ArrayOutOfBounds exception. + + + base constructor. + + + create a DataLengthException with the given message. + + @param message the message to be carried with the exception. + + + base implementation of MD4 family style digest as outlined in + "Handbook of Applied Cryptography", pages 344 - 347. + + + interface that a message digest conforms to. + + + return the size, in bytes, of the digest produced by this message digest. + + @return the size, in bytes, of the digest produced by this message digest. + + + return the size, in bytes, of the internal buffer used by this digest. + + @return the size, in bytes, of the internal buffer used by this digest. + + + update the message digest with a single byte. + + @param inByte the input byte to be entered. + + + update the message digest with a block of bytes. + + @param input the byte array containing the data. + @param inOff the offset into the byte array where the data starts. + @param len the length of the data. + + + Close the digest, producing the final digest value. The doFinal + call leaves the digest reset. + + @param output the array the digest is to be copied into. + @param outOff the offset into the out array the digest is to start at. + + + reset the digest back to it's initial state. + + + return the algorithm name + + @return the algorithm name + + + + Produce a copy of this object with its configuration and in its current state. + + + The returned object may be used simply to store the state, or may be used as a similar object + starting from the copied state. + + + + + Restore a copied object state into this object. + + + Implementations of this method should try to avoid or minimise memory allocation to perform the reset. + + an object originally {@link #copy() copied} from an object of the same type as this instance. + if the provided object is not of the correct type. + if the other parameter is in some other way invalid. + + + implementation of GOST R 34.11-94 + + + Standard constructor + + + Constructor to allow use of a particular sbox with GOST28147 + @see GOST28147Engine#getSBox(String) + + + Copy constructor. This will copy the state of the provided + message digest. + + + reset the chaining variables to the IV values. + + + + Implementation of Keccak based on following KeccakNISTInterface.c from http://keccak.noekeon.org/ + + + Following the naming conventions used in the C source code to enable easy review of the implementation. + + + + Return the size of block that the compression function is applied to in bytes. + + @return internal byte length of a block. + + + Base class for SHA-384 and SHA-512. + + + Constructor for variable length word + + + Copy constructor. We are using copy constructors in place + of the object.Clone() interface as this interface is not + supported by J2ME. + + + adjust the byte counts so that byteCount2 represents the + upper long (less 3 bits) word of the byte count. + + + implementation of MD2 + as outlined in RFC1319 by B.Kaliski from RSA Laboratories April 1992 + + + Close the digest, producing the final digest value. The doFinal + call leaves the digest reset. + + @param out the array the digest is to be copied into. + @param outOff the offset into the out array the digest is to start at. + + + reset the digest back to it's initial state. + + + update the message digest with a single byte. + + @param in the input byte to be entered. + + + update the message digest with a block of bytes. + + @param in the byte array containing the data. + @param inOff the offset into the byte array where the data starts. + @param len the length of the data. + + + return the algorithm name + + @return the algorithm name + + + implementation of MD4 as RFC 1320 by R. Rivest, MIT Laboratory for + Computer Science and RSA Data Security, Inc. +

    + NOTE: This algorithm is only included for backwards compatibility + with legacy applications, it's not secure, don't use it for anything new!

    +
    + + Standard constructor + + + Copy constructor. This will copy the state of the provided + message digest. + + + reset the chaining variables to the IV values. + + + implementation of MD5 as outlined in "Handbook of Applied Cryptography", pages 346 - 347. + + + Copy constructor. This will copy the state of the provided + message digest. + + + reset the chaining variables to the IV values. + + + Wrapper removes exposure to the IMemoable interface on an IDigest implementation. + + + Base constructor. + + @param baseDigest underlying digest to use. + @exception IllegalArgumentException if baseDigest is null + + + implementation of RipeMD128 + + + Standard constructor + + + Copy constructor. This will copy the state of the provided + message digest. + + + reset the chaining variables to the IV values. + + + implementation of RipeMD see, + http://www.esat.kuleuven.ac.be/~bosselae/ripemd160.html + + + Standard constructor + + + Copy constructor. This will copy the state of the provided + message digest. + + + reset the chaining variables to the IV values. + + + +

    Implementation of RipeMD256.

    +

    Note: this algorithm offers the same level of security as RipeMD128.

    +
    +
    + + Standard constructor + + + Copy constructor. This will copy the state of the provided + message digest. + + + + reset the chaining variables to the IV values. + + + +

    Implementation of RipeMD 320.

    +

    Note: this algorithm offers the same level of security as RipeMD160.

    +
    +
    + + Standard constructor + + + Copy constructor. This will copy the state of the provided + message digest. + + + + reset the chaining variables to the IV values. + + + implementation of SHA-1 as outlined in "Handbook of Applied Cryptography", pages 346 - 349. + + It is interesting to ponder why the, apart from the extra IV, the other difference here from MD5 + is the "endianness" of the word processing! + + + Copy constructor. This will copy the state of the provided + message digest. + + + reset the chaining variables + + + SHA-224 as described in RFC 3874 +
    +                    block  word  digest
    +            SHA-1   512    32    160
    +            SHA-224 512    32    224
    +            SHA-256 512    32    256
    +            SHA-384 1024   64    384
    +            SHA-512 1024   64    512
    +            
    +
    + + Standard constructor + + + Copy constructor. This will copy the state of the provided + message digest. + + + reset the chaining variables + + + Draft FIPS 180-2 implementation of SHA-256. Note: As this is + based on a draft this implementation is subject to change. + +
    +                     block  word  digest
    +             SHA-1   512    32    160
    +             SHA-256 512    32    256
    +             SHA-384 1024   64    384
    +             SHA-512 1024   64    512
    +             
    +
    + + Copy constructor. This will copy the state of the provided + message digest. + + + reset the chaining variables + + + Draft FIPS 180-2 implementation of SHA-384. Note: As this is + based on a draft this implementation is subject to change. + +
    +                     block  word  digest
    +             SHA-1   512    32    160
    +             SHA-256 512    32    256
    +             SHA-384 1024   64    384
    +             SHA-512 1024   64    512
    +             
    +
    + + Copy constructor. This will copy the state of the provided + message digest. + + + reset the chaining variables + + + + Implementation of SHA-3 based on following KeccakNISTInterface.c from http://keccak.noekeon.org/ + + + Following the naming conventions used in the C source code to enable easy review of the implementation. + + + + Draft FIPS 180-2 implementation of SHA-512. Note: As this is + based on a draft this implementation is subject to change. + +
    +                     block  word  digest
    +             SHA-1   512    32    160
    +             SHA-256 512    32    256
    +             SHA-384 1024   64    384
    +             SHA-512 1024   64    512
    +             
    +
    + + Copy constructor. This will copy the state of the provided + message digest. + + + reset the chaining variables + + + FIPS 180-4 implementation of SHA-512/t + + + Standard constructor + + + Copy constructor. This will copy the state of the provided + message digest. + + + reset the chaining variables + + + + Implementation of SHAKE based on following KeccakNISTInterface.c from http://keccak.noekeon.org/ + + + Following the naming conventions used in the C source code to enable easy review of the implementation. + + + + + With FIPS PUB 202 a new kind of message digest was announced which supported extendable output, or variable digest sizes. + This interface provides the extra method required to support variable output on a digest implementation. + + + + + Output the results of the final calculation for this digest to outLen number of bytes. + + output array to write the output bytes to. + offset to start writing the bytes at. + the number of output bytes requested. + the number of bytes written + + + + Start outputting the results of the final calculation for this digest. Unlike DoFinal, this method + will continue producing output until the Xof is explicitly reset, or signals otherwise. + + output array to write the output bytes to. + offset to start writing the bytes at. + the number of output bytes requested. + the number of bytes written + + + Wrapper class that reduces the output length of a particular digest to + only the first n bytes of the digest function. + + + Base constructor. + + @param baseDigest underlying digest to use. + @param length length in bytes of the output of doFinal. + @exception ArgumentException if baseDigest is null, or length is greater than baseDigest.GetDigestSize(). + + + + Implementation of the Skein parameterised hash function in 256, 512 and 1024 bit block sizes, + based on the Threefish tweakable block cipher. + + + This is the 1.3 version of Skein defined in the Skein hash function submission to the NIST SHA-3 + competition in October 2010. +

    + Skein was designed by Niels Ferguson - Stefan Lucks - Bruce Schneier - Doug Whiting - Mihir + Bellare - Tadayoshi Kohno - Jon Callas - Jesse Walker. + + + + + +

    + 256 bit block size - Skein-256 + +
    + + + 512 bit block size - Skein-512 + + + + + 1024 bit block size - Skein-1024 + + + + + Constructs a Skein digest with an internal state size and output size. + + the internal state size in bits - one of or + . + the output/digest size to produce in bits, which must be an integral number of + bytes. + + + + Optionally initialises the Skein digest with the provided parameters. + + See for details on the parameterisation of the Skein hash function. + the parameters to apply to this engine, or null to use no parameters. + + + + Implementation of the Skein family of parameterised hash functions in 256, 512 and 1024 bit block + sizes, based on the Threefish tweakable block cipher. + + + This is the 1.3 version of Skein defined in the Skein hash function submission to the NIST SHA-3 + competition in October 2010. +

    + Skein was designed by Niels Ferguson - Stefan Lucks - Bruce Schneier - Doug Whiting - Mihir + Bellare - Tadayoshi Kohno - Jon Callas - Jesse Walker. +

    + This implementation is the basis for and , implementing the + parameter based configuration system that allows Skein to be adapted to multiple applications.
    + Initialising the engine with allows standard and arbitrary parameters to + be applied during the Skein hash function. +

    + Implemented: +

      +
    • 256, 512 and 1024 bit internal states.
    • +
    • Full 96 bit input length.
    • +
    • Parameters defined in the Skein specification, and arbitrary other pre and post message + parameters.
    • +
    • Arbitrary output size in 1 byte intervals.
    • +
    +

    + Not implemented: +

      +
    • Sub-byte length input (bit padding).
    • +
    • Tree hashing.
    • +
    +
    + +
    + + + 256 bit block size - Skein-256 + + + + + 512 bit block size - Skein-512 + + + + + 1024 bit block size - Skein-1024 + + + + The parameter type for the Skein key. + + + The parameter type for the Skein configuration block. + + + The parameter type for the message. + + + The parameter type for the output transformation. + + + Precalculated UBI(CFG) states for common state/output combinations without key or other + pre-message params. + + + Underlying Threefish tweakable block cipher + + + Size of the digest output, in bytes + + + The current chaining/state value + + + The initial state value + + + The (optional) key parameter + + + Parameters to apply prior to the message + + + Parameters to apply after the message, but prior to output + + + The current UBI operation + + + Buffer for single byte update method + + + + Constructs a Skein digest with an internal state size and output size. + + the internal state size in bits - one of or + . + the output/digest size to produce in bits, which must be an integral number of + bytes. + + + + Creates a SkeinEngine as an exact copy of an existing instance. + + + + + Initialises the Skein engine with the provided parameters. See for + details on the parameterisation of the Skein hash function. + + the parameters to apply to this engine, or null to use no parameters. + + + Calculate the initial (pre message block) chaining state. + + + + Reset the engine to the initial state (with the key and any pre-message parameters , ready to + accept message input. + + + + Point at which position might overflow long, so switch to add with carry logic + + + Bit 127 = final + + + Bit 126 = first + + + UBI uses a 128 bit tweak + + + Whether 64 bit position exceeded + + + Advances the position in the tweak by the specified value. + + + The Unique Block Iteration chaining mode. + + + Buffer for the current block of message data + + + Offset into the current message block + + + Buffer for message words for feedback into encrypted block + + + + Implementation of Chinese SM3 digest as described at + http://tools.ietf.org/html/draft-shen-sm3-hash-00 + and at .... ( Chinese PDF ) + + + The specification says "process a bit stream", + but this is written to process bytes in blocks of 4, + meaning this will process 32-bit word groups. + But so do also most other digest specifications, + including the SHA-256 which was a origin for + this specification. + + + + + Standard constructor + + + + + Copy constructor. This will copy the state of the provided + message digest. + + + + + reset the chaining variables + + + + implementation of Tiger based on: + + http://www.cs.technion.ac.il/~biham/Reports/Tiger + + + Standard constructor + + + Copy constructor. This will copy the state of the provided + message digest. + + + reset the chaining variables + + + Implementation of WhirlpoolDigest, based on Java source published by Barreto + and Rijmen. + + + + Copy constructor. This will copy the state of the provided message + digest. + + + Reset the chaining variables + + + return the X9ECParameters object for the named curve represented by + the passed in object identifier. Null if the curve isn't present. + + @param oid an object identifier representing a named curve, if present. + + + return the object identifier signified by the passed in name. Null + if there is no object identifier associated with name. + + @return the object identifier associated with name, if present. + + + return the named curve name represented by the given object identifier. + + + returns an enumeration containing the name strings for curves + contained in this structure. + + + ISO 9796-1 padding. Note in the light of recent results you should + only use this with RSA (rather than the "simpler" Rabin keys) and you + should never use it with anything other than a hash (ie. even if the + message is small don't sign the message, sign it's hash) or some "random" + value. See your favorite search engine for details. + + + Base interface for a public/private key block cipher. + + + Initialise the cipher. + Initialise for encryption if true, for decryption if false. + The key or other data required by the cipher. + + + The maximum size, in bytes, an input block may be. + + + The maximum size, in bytes, an output block will be. + + + Process a block. + The input buffer. + The offset into inBuf that the input block begins. + The length of the input block. + Input decrypts improperly. + Input is too large for the cipher. + + + The name of the algorithm this cipher implements. + + + return the input block size. The largest message we can process + is (key_size_in_bits + 3)/16, which in our world comes to + key_size_in_bytes / 2. + + + return the maximum possible size for the output. + + + set the number of bits in the next message to be treated as + pad bits. + + + retrieve the number of pad bits in the last decoded message. + + + @exception InvalidCipherTextException if the decrypted block is not a valid ISO 9796 bit string + + + Optimal Asymmetric Encryption Padding (OAEP) - see PKCS 1 V 2. + + + @exception InvalidCipherTextException if the decrypted block turns out to + be badly formatted. + + + int to octet string. + + + mask generator function, as described in PKCS1v2. + + + this does your basic Pkcs 1 v1.5 padding - whether or not you should be using this + depends on your application - see Pkcs1 Version 2 for details. + + + some providers fail to include the leading zero in PKCS1 encoded blocks. If you need to + work with one of these set the system property Org.BouncyCastle.Pkcs1.Strict to false. + + + Basic constructor. + @param cipher + + + Constructor for decryption with a fixed plaintext length. + + @param cipher The cipher to use for cryptographic operation. + @param pLen Length of the expected plaintext. + + + Constructor for decryption with a fixed plaintext length and a fallback + value that is returned, if the padding is incorrect. + + @param cipher + The cipher to use for cryptographic operation. + @param fallback + The fallback value, we don't to a arraycopy here. + + + Checks if the argument is a correctly PKCS#1.5 encoded Plaintext + for encryption. + + @param encoded The Plaintext. + @param pLen Expected length of the plaintext. + @return Either 0, if the encoding is correct, or -1, if it is incorrect. + + + Decode PKCS#1.5 encoding, and return a random value if the padding is not correct. + + @param in The encrypted block. + @param inOff Offset in the encrypted block. + @param inLen Length of the encrypted block. + @param pLen Length of the desired output. + @return The plaintext without padding, or a random value if the padding was incorrect. + + @throws InvalidCipherTextException + + + @exception InvalidCipherTextException if the decrypted block is not in Pkcs1 format. + + + The same effect can be achieved by setting the static property directly +

    + The static property is checked during construction of the encoding object, it is set to + true by default. +

    +
    + + an implementation of the AES (Rijndael), from FIPS-197. +

    + For further details see: http://csrc.nist.gov/encryption/aes/. + + This implementation is based on optimizations from Dr. Brian Gladman's paper and C code at + http://fp.gladman.plus.com/cryptography_technology/rijndael/ + + There are three levels of tradeoff of speed vs memory + Because java has no preprocessor, they are written as three separate classes from which to choose + + The fastest uses 8Kbytes of static tables to precompute round calculations, 4 256 word tables for encryption + and 4 for decryption. + + The middle performance version uses only one 256 word table for each, for a total of 2Kbytes, + adding 12 rotate operations per round to compute the values contained in the other tables from + the contents of the first. + + The slowest version uses no static tables at all and computes the values in each round. +

    +

    + This file contains the middle performance version with 2Kbytes of static tables for round precomputation. +

    +
    + + Base interface for a symmetric key block cipher. + + + Initialise the cipher. + Initialise for encryption if true, for decryption if false. + The key or other data required by the cipher. + + + The block size for this cipher, in bytes. + + + Process a block. + The input buffer. + The offset into inBuf that the input block begins. + The output buffer. + The offset into outBuf to write the output block. + If input block is wrong size, or outBuf too small. + The number of bytes processed and produced. + + + + Reset the cipher to the same state as it was after the last init (if there was one). + + + + The name of the algorithm this cipher implements. + + + Indicates whether this cipher can handle partial blocks. + + + Calculate the necessary round keys + The number of calculations depends on key size and block size + AES specified a fixed block size of 128 bits and key sizes 128/192/256 bits + This code is written assuming those are the only possible values + + + default constructor - 128 bit block size. + + + initialise an AES cipher. + + @param forEncryption whether or not we are for encryption. + @param parameters the parameters required to set up the cipher. + @exception ArgumentException if the parameters argument is + inappropriate. + + + an implementation of the AES (Rijndael)), from FIPS-197. +

    + For further details see: http://csrc.nist.gov/encryption/aes/. + + This implementation is based on optimizations from Dr. Brian Gladman's paper and C code at + http://fp.gladman.plus.com/cryptography_technology/rijndael/ + + There are three levels of tradeoff of speed vs memory + Because java has no preprocessor), they are written as three separate classes from which to choose + + The fastest uses 8Kbytes of static tables to precompute round calculations), 4 256 word tables for encryption + and 4 for decryption. + + The middle performance version uses only one 256 word table for each), for a total of 2Kbytes), + adding 12 rotate operations per round to compute the values contained in the other tables from + the contents of the first + + The slowest version uses no static tables at all and computes the values in each round +

    +

    + This file contains the fast version with 8Kbytes of static tables for round precomputation +

    +
    + + Calculate the necessary round keys + The number of calculations depends on key size and block size + AES specified a fixed block size of 128 bits and key sizes 128/192/256 bits + This code is written assuming those are the only possible values + + + default constructor - 128 bit block size. + + + initialise an AES cipher. + + @param forEncryption whether or not we are for encryption. + @param parameters the parameters required to set up the cipher. + @exception ArgumentException if the parameters argument is + inappropriate. + + + an implementation of the AES (Rijndael), from FIPS-197. +

    + For further details see: http://csrc.nist.gov/encryption/aes/. + + This implementation is based on optimizations from Dr. Brian Gladman's paper and C code at + http://fp.gladman.plus.com/cryptography_technology/rijndael/ + + There are three levels of tradeoff of speed vs memory + Because java has no preprocessor, they are written as three separate classes from which to choose + + The fastest uses 8Kbytes of static tables to precompute round calculations, 4 256 word tables for encryption + and 4 for decryption. + + The middle performance version uses only one 256 word table for each, for a total of 2Kbytes, + adding 12 rotate operations per round to compute the values contained in the other tables from + the contents of the first + + The slowest version uses no static tables at all and computes the values + in each round. +

    +

    + This file contains the slowest performance version with no static tables + for round precomputation, but it has the smallest foot print. +

    +
    + + Calculate the necessary round keys + The number of calculations depends on key size and block size + AES specified a fixed block size of 128 bits and key sizes 128/192/256 bits + This code is written assuming those are the only possible values + + + default constructor - 128 bit block size. + + + initialise an AES cipher. + + @param forEncryption whether or not we are for encryption. + @param parameters the parameters required to set up the cipher. + @exception ArgumentException if the parameters argument is + inappropriate. + + + + An implementation of the AES Key Wrapper from the NIST Key Wrap Specification. +

    + For further details see: http://csrc.nist.gov/encryption/kms/key-wrap.pdf. + + + + + An implementation of the AES Key Wrapper from the NIST Key Wrap + Specification as described in RFC 3394. +

    + For further details see: http://www.ietf.org/rfc/rfc3394.txt + and http://csrc.nist.gov/encryption/kms/key-wrap.pdf. + + + +

    The name of the algorithm this cipher implements. +
    + + A class that provides Blowfish key encryption operations, + such as encoding data and generating keys. + All the algorithms herein are from Applied Cryptography + and implement a simplified cryptography interface. + + + initialise a Blowfish cipher. + + @param forEncryption whether or not we are for encryption. + @param parameters the parameters required to set up the cipher. + @exception ArgumentException if the parameters argument is + inappropriate. + + + apply the encryption cycle to each value pair in the table. + + + Encrypt the given input starting at the given offset and place + the result in the provided buffer starting at the given offset. + The input will be an exact multiple of our blocksize. + + + Decrypt the given input starting at the given offset and place + the result in the provided buffer starting at the given offset. + The input will be an exact multiple of our blocksize. + + + Camellia - based on RFC 3713. + + + Camellia - based on RFC 3713, smaller implementation, about half the size of CamelliaEngine. + + + + An implementation of the Camellia key wrapper based on RFC 3657/RFC 3394. +

    + For further details see: http://www.ietf.org/rfc/rfc3657.txt. + + + + A class that provides CAST key encryption operations, + such as encoding data and generating keys. + + All the algorithms herein are from the Internet RFC's + + RFC2144 - Cast5 (64bit block, 40-128bit key) + RFC2612 - CAST6 (128bit block, 128-256bit key) + + and implement a simplified cryptography interface. + + + initialise a CAST cipher. + + @param forEncryption whether or not we are for encryption. + @param parameters the parameters required to set up the cipher. + @exception ArgumentException if the parameters argument is + inappropriate. + + + Encrypt the given input starting at the given offset and place + the result in the provided buffer starting at the given offset. + + @param src The plaintext buffer + @param srcIndex An offset into src + @param dst The ciphertext buffer + @param dstIndex An offset into dst + + + Decrypt the given input starting at the given offset and place + the result in the provided buffer starting at the given offset. + + @param src The plaintext buffer + @param srcIndex An offset into src + @param dst The ciphertext buffer + @param dstIndex An offset into dst + + + The first of the three processing functions for the + encryption and decryption. + + @param D the input to be processed + @param Kmi the mask to be used from Km[n] + @param Kri the rotation value to be used + + + + The second of the three processing functions for the + encryption and decryption. + + @param D the input to be processed + @param Kmi the mask to be used from Km[n] + @param Kri the rotation value to be used + + + + The third of the three processing functions for the + encryption and decryption. + + @param D the input to be processed + @param Kmi the mask to be used from Km[n] + @param Kri the rotation value to be used + + + + Does the 16 rounds to encrypt the block. + + @param L0 the LH-32bits of the plaintext block + @param R0 the RH-32bits of the plaintext block + + + A class that provides CAST6 key encryption operations, + such as encoding data and generating keys. + + All the algorithms herein are from the Internet RFC + + RFC2612 - CAST6 (128bit block, 128-256bit key) + + and implement a simplified cryptography interface. + + + Encrypt the given input starting at the given offset and place + the result in the provided buffer starting at the given offset. + + @param src The plaintext buffer + @param srcIndex An offset into src + @param dst The ciphertext buffer + @param dstIndex An offset into dst + + + Decrypt the given input starting at the given offset and place + the result in the provided buffer starting at the given offset. + + @param src The plaintext buffer + @param srcIndex An offset into src + @param dst The ciphertext buffer + @param dstIndex An offset into dst + + + Does the 12 quad rounds rounds to encrypt the block. + + @param A the 00-31 bits of the plaintext block + @param B the 32-63 bits of the plaintext block + @param C the 64-95 bits of the plaintext block + @param D the 96-127 bits of the plaintext block + @param result the resulting ciphertext + + + Does the 12 quad rounds rounds to decrypt the block. + + @param A the 00-31 bits of the ciphertext block + @param B the 32-63 bits of the ciphertext block + @param C the 64-95 bits of the ciphertext block + @param D the 96-127 bits of the ciphertext block + @param result the resulting plaintext + + +

    + Implementation of Daniel J. Bernstein's ChaCha stream cipher. + +
    + + + Implementation of Daniel J. Bernstein's Salsa20 stream cipher, Snuffle 2005 + + + + The interface stream ciphers conform to. + + + Initialise the cipher. + If true the cipher is initialised for encryption, + if false for decryption. + The key and other data required by the cipher. + + If the parameters argument is inappropriate. + + + + encrypt/decrypt a single byte returning the result. + the byte to be processed. + the result of processing the input byte. + + + + Process a block of bytes from input putting the result into output. + + The input byte array. + + The offset into input where the data to be processed starts. + + The number of bytes to be processed. + The output buffer the processed bytes go into. + + The offset into output the processed data starts at. + + If the output buffer is too small. + + + + Reset the cipher to the same state as it was after the last init (if there was one). + + + + The name of the algorithm this cipher implements. + + + Constants + + + + Creates a 20 round Salsa20 engine. + + + + + Creates a Salsa20 engine with a specific number of rounds. + + the number of rounds (must be an even number). + + + Rotate left + + @param x value to rotate + @param y amount to rotate x + + @return rotated x + + + + Creates a 20 rounds ChaCha engine. + + + + + Implementation of Daniel J. Bernstein's ChaCha stream cipher. + + + + + Creates a 20 rounds ChaCha engine. + + + + + Creates a ChaCha engine with a specific number of rounds. + + the number of rounds (must be an even number). + + + + ChaCha function. + + The number of ChaCha rounds to execute + The input words. + The ChaCha state to modify. + + + A class that provides a basic DESede (or Triple DES) engine. + + + A class that provides a basic DES engine. + + + initialise a DES cipher. + + @param forEncryption whether or not we are for encryption. + @param parameters the parameters required to set up the cipher. + @exception ArgumentException if the parameters argument is + inappropriate. + + + what follows is mainly taken from "Applied Cryptography", by + Bruce Schneier, however it also bears great resemblance to Richard + Outerbridge's D3DES... + + + Generate an integer based working key based on our secret key + and what we processing we are planning to do. + + Acknowledgements for this routine go to James Gillogly and Phil Karn. + (whoever, and wherever they are!). + + + the DES engine. + + + initialise a DESede cipher. + + @param forEncryption whether or not we are for encryption. + @param parameters the parameters required to set up the cipher. + @exception ArgumentException if the parameters argument is + inappropriate. + + + * Wrap keys according to + * + * draft-ietf-smime-key-wrap-01.txt. + *

    + * Note: + *

      + *
    • this is based on a draft, and as such is subject to change - don't use this class for anything requiring long term storage.
    • + *
    • if you are using this to wrap triple-des keys you need to set the + * parity bits on the key and, if it's a two-key triple-des key, pad it + * yourself.
    • + *
    + *

    +
    + + Field engine + + + Field param + + + Field paramPlusIV + + + Field iv + + + Field forWrapping + + + Field IV2 + + + Method init + + @param forWrapping + @param param + + + Method wrap + + @param in + @param inOff + @param inLen + @return + + + Method unwrap + + @param in + @param inOff + @param inLen + @return + @throws InvalidCipherTextException + + + Some key wrap algorithms make use of the Key Checksum defined + in CMS [CMS-Algorithms]. This is used to provide an integrity + check value for the key being wrapped. The algorithm is + + - Compute the 20 octet SHA-1 hash on the key being wrapped. + - Use the first 8 octets of this hash as the checksum value. + + @param key + @return + @throws Exception + @see http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum + + + @param key + @param checksum + @return + @see http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum + + + Method GetAlgorithmName + + @return + + + this does your basic ElGamal algorithm. + + + initialise the ElGamal engine. + + @param forEncryption true if we are encrypting, false otherwise. + @param param the necessary ElGamal key parameters. + + + Return the maximum size for an input block to this engine. + For ElGamal this is always one byte less than the size of P on + encryption, and twice the length as the size of P on decryption. + + @return maximum size for an input block. + + + Return the maximum size for an output block to this engine. + For ElGamal this is always one byte less than the size of P on + decryption, and twice the length as the size of P on encryption. + + @return maximum size for an output block. + + + Process a single block using the basic ElGamal algorithm. + + @param in the input array. + @param inOff the offset into the input buffer where the data starts. + @param length the length of the data to be processed. + @return the result of the ElGamal process. + @exception DataLengthException the input block is too large. + + + implementation of GOST 28147-89 + + + standard constructor. + + + initialise an Gost28147 cipher. + + @param forEncryption whether or not we are for encryption. + @param parameters the parameters required to set up the cipher. + @exception ArgumentException if the parameters argument is inappropriate. + + + Return the S-Box associated with SBoxName + @param sBoxName name of the S-Box + @return byte array representing the S-Box + + + HC-128 is a software-efficient stream cipher created by Hongjun Wu. It + generates keystream from a 128-bit secret key and a 128-bit initialization + vector. +

    + http://www.ecrypt.eu.org/stream/p3ciphers/hc/hc128_p3.pdf +

    + It is a third phase candidate in the eStream contest, and is patent-free. + No attacks are known as of today (April 2007). See + + http://www.ecrypt.eu.org/stream/hcp3.html +

    +
    + + Initialise a HC-128 cipher. + + @param forEncryption whether or not we are for encryption. Irrelevant, as + encryption and decryption are the same. + @param params the parameters required to set up the cipher. + @throws ArgumentException if the params argument is + inappropriate (ie. the key is not 128 bit long). + + + HC-256 is a software-efficient stream cipher created by Hongjun Wu. It + generates keystream from a 256-bit secret key and a 256-bit initialization + vector. +

    + http://www.ecrypt.eu.org/stream/p3ciphers/hc/hc256_p3.pdf +

    + Its brother, HC-128, is a third phase candidate in the eStream contest. + The algorithm is patent-free. No attacks are known as of today (April 2007). + See + + http://www.ecrypt.eu.org/stream/hcp3.html +

    +
    + + Initialise a HC-256 cipher. + + @param forEncryption whether or not we are for encryption. Irrelevant, as + encryption and decryption are the same. + @param params the parameters required to set up the cipher. + @throws ArgumentException if the params argument is + inappropriate (ie. the key is not 256 bit long). + + + A class that provides a basic International Data Encryption Algorithm (IDEA) engine. +

    + This implementation is based on the "HOWTO: INTERNATIONAL DATA ENCRYPTION ALGORITHM" + implementation summary by Fauzan Mirza (F.U.Mirza@sheffield.ac.uk). (baring 1 typo at the + end of the mulinv function!). +

    +

    + It can be found at ftp://ftp.funet.fi/pub/crypt/cryptography/symmetric/idea/ +

    +

    + Note 1: This algorithm is patented in the USA, Japan, and Europe including + at least Austria, France, Germany, Italy, Netherlands, Spain, Sweden, Switzerland + and the United Kingdom. Non-commercial use is free, however any commercial + products are liable for royalties. Please see + www.mediacrypt.com for + further details. This announcement has been included at the request of + the patent holders. +

    +

    + Note 2: Due to the requests concerning the above, this algorithm is now only + included in the extended assembly. It is not included in the default distributions. +

    +
    + + standard constructor. + + + initialise an IDEA cipher. + + @param forEncryption whether or not we are for encryption. + @param parameters the parameters required to set up the cipher. + @exception ArgumentException if the parameters argument is + inappropriate. + + + return x = x * y where the multiplication is done modulo + 65537 (0x10001) (as defined in the IDEA specification) and + a zero input is taken to be 65536 (0x10000). + + @param x the x value + @param y the y value + @return x = x * y + + + The following function is used to expand the user key to the encryption + subkey. The first 16 bytes are the user key, and the rest of the subkey + is calculated by rotating the previous 16 bytes by 25 bits to the left, + and so on until the subkey is completed. + + + This function computes multiplicative inverse using Euclid's Greatest + Common Divisor algorithm. Zero and one are self inverse. +

    + i.e. x * MulInv(x) == 1 (modulo BASE) +

    +
    + + Return the additive inverse of x. +

    + i.e. x + AddInv(x) == 0 +

    +
    + + The function to invert the encryption subkey to the decryption subkey. + It also involves the multiplicative inverse and the additive inverse functions. + + + support class for constructing intergrated encryption ciphers + for doing basic message exchanges on top of key agreement ciphers + + + set up for use with stream mode, where the key derivation function + is used to provide a stream of bytes to xor with the message. + + @param agree the key agreement used as the basis for the encryption + @param kdf the key derivation function used for byte generation + @param mac the message authentication code generator for the message + + + set up for use in conjunction with a block cipher to handle the + message. + + @param agree the key agreement used as the basis for the encryption + @param kdf the key derivation function used for byte generation + @param mac the message authentication code generator for the message + @param cipher the cipher to used for encrypting the message + + + Initialise the encryptor. + + @param forEncryption whether or not this is encryption/decryption. + @param privParam our private key parameters + @param pubParam the recipient's/sender's public key parameters + @param param encoding and derivation parameters. + + + Implementation of Bob Jenkin's ISAAC (Indirection Shift Accumulate Add and Count). + see: http://www.burtleburtle.net/bob/rand/isaacafa.html + + + initialise an ISAAC cipher. + + @param forEncryption whether or not we are for encryption. + @param params the parameters required to set up the cipher. + @exception ArgumentException if the params argument is + inappropriate. + + + NaccacheStern Engine. For details on this cipher, please see + http://www.gemplus.com/smart/rd/publications/pdf/NS98pkcs.pdf + + + Initializes this algorithm. Must be called before all other Functions. + + @see org.bouncycastle.crypto.AsymmetricBlockCipher#init(bool, + org.bouncycastle.crypto.CipherParameters) + + + Returns the input block size of this algorithm. + + @see org.bouncycastle.crypto.AsymmetricBlockCipher#GetInputBlockSize() + + + Returns the output block size of this algorithm. + + @see org.bouncycastle.crypto.AsymmetricBlockCipher#GetOutputBlockSize() + + + Process a single Block using the Naccache-Stern algorithm. + + @see org.bouncycastle.crypto.AsymmetricBlockCipher#ProcessBlock(byte[], + int, int) + + + Encrypts a BigInteger aka Plaintext with the public key. + + @param plain + The BigInteger to encrypt + @return The byte[] representation of the encrypted BigInteger (i.e. + crypted.toByteArray()) + + + Adds the contents of two encrypted blocks mod sigma + + @param block1 + the first encrypted block + @param block2 + the second encrypted block + @return encrypt((block1 + block2) mod sigma) + @throws InvalidCipherTextException + + + Convenience Method for data exchange with the cipher. + + Determines blocksize and splits data to blocksize. + + @param data the data to be processed + @return the data after it went through the NaccacheSternEngine. + @throws InvalidCipherTextException + + + Computes the integer x that is expressed through the given primes and the + congruences with the chinese remainder theorem (CRT). + + @param congruences + the congruences c_i + @param primes + the primes p_i + @return an integer x for that x % p_i == c_i + + + A Noekeon engine, using direct-key mode. + + + Create an instance of the Noekeon encryption algorithm + and set some defaults + + + initialise + + @param forEncryption whether or not we are for encryption. + @param params the parameters required to set up the cipher. + @exception ArgumentException if the params argument is + inappropriate. + + + Re-key the cipher. + + @param key the key to be used + + + The no-op engine that just copies bytes through, irrespective of whether encrypting and decrypting. + Provided for the sake of completeness. + + + an implementation of RC2 as described in RFC 2268 + "A Description of the RC2(r) Encryption Algorithm" R. Rivest. + + + initialise a RC2 cipher. + + @param forEncryption whether or not we are for encryption. + @param parameters the parameters required to set up the cipher. + @exception ArgumentException if the parameters argument is + inappropriate. + + + return the result rotating the 16 bit number in x left by y + + + Wrap keys according to RFC 3217 - RC2 mechanism + + + Field engine + + + Field param + + + Field paramPlusIV + + + Field iv + + + Field forWrapping + + + Field IV2 + + + Method init + + @param forWrapping + @param param + + + Method wrap + + @param in + @param inOff + @param inLen + @return + + + Method unwrap + + @param in + @param inOff + @param inLen + @return + @throws InvalidCipherTextException + + + Some key wrap algorithms make use of the Key Checksum defined + in CMS [CMS-Algorithms]. This is used to provide an integrity + check value for the key being wrapped. The algorithm is + + - Compute the 20 octet SHA-1 hash on the key being wrapped. + - Use the first 8 octets of this hash as the checksum value. + + @param key + @return + @throws Exception + @see http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum + + + @param key + @param checksum + @return + @see http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum + + + Method GetAlgorithmName + + @return + + + initialise a RC4 cipher. + + @param forEncryption whether or not we are for encryption. + @param parameters the parameters required to set up the cipher. + @exception ArgumentException if the parameters argument is + inappropriate. + + + The specification for RC5 came from the RC5 Encryption Algorithm + publication in RSA CryptoBytes, Spring of 1995. + http://www.rsasecurity.com/rsalabs/cryptobytes. +

    + This implementation has a word size of 32 bits.

    +
    + + Create an instance of the RC5 encryption algorithm + and set some defaults + + + initialise a RC5-32 cipher. + + @param forEncryption whether or not we are for encryption. + @param parameters the parameters required to set up the cipher. + @exception ArgumentException if the parameters argument is + inappropriate. + + + Re-key the cipher. + + @param key the key to be used + + + Encrypt the given block starting at the given offset and place + the result in the provided buffer starting at the given offset. + + @param in in byte buffer containing data to encrypt + @param inOff offset into src buffer + @param out out buffer where encrypted data is written + @param outOff offset into out buffer + + + Perform a left "spin" of the word. The rotation of the given + word x is rotated left by y bits. + Only the lg(32) low-order bits of y + are used to determine the rotation amount. Here it is + assumed that the wordsize used is a power of 2. + + @param x word to rotate + @param y number of bits to rotate % 32 + + + Perform a right "spin" of the word. The rotation of the given + word x is rotated left by y bits. + Only the lg(32) low-order bits of y + are used to determine the rotation amount. Here it is + assumed that the wordsize used is a power of 2. + + @param x word to rotate + @param y number of bits to rotate % 32 + + + The specification for RC5 came from the RC5 Encryption Algorithm + publication in RSA CryptoBytes, Spring of 1995. + http://www.rsasecurity.com/rsalabs/cryptobytes. +

    + This implementation is set to work with a 64 bit word size.

    +
    + + Create an instance of the RC5 encryption algorithm + and set some defaults + + + initialise a RC5-64 cipher. + + @param forEncryption whether or not we are for encryption. + @param parameters the parameters required to set up the cipher. + @exception ArgumentException if the parameters argument is + inappropriate. + + + Re-key the cipher. + + @param key the key to be used + + + Encrypt the given block starting at the given offset and place + the result in the provided buffer starting at the given offset. + + @param in in byte buffer containing data to encrypt + @param inOff offset into src buffer + @param out out buffer where encrypted data is written + @param outOff offset into out buffer + + + Perform a left "spin" of the word. The rotation of the given + word x is rotated left by y bits. + Only the lg(wordSize) low-order bits of y + are used to determine the rotation amount. Here it is + assumed that the wordsize used is a power of 2. + + @param x word to rotate + @param y number of bits to rotate % wordSize + + + Perform a right "spin" of the word. The rotation of the given + word x is rotated left by y bits. + Only the lg(wordSize) low-order bits of y + are used to determine the rotation amount. Here it is + assumed that the wordsize used is a power of 2. + + @param x word to rotate + @param y number of bits to rotate % wordSize + + + An RC6 engine. + + + Create an instance of the RC6 encryption algorithm + and set some defaults + + + initialise a RC5-32 cipher. + + @param forEncryption whether or not we are for encryption. + @param parameters the parameters required to set up the cipher. + @exception ArgumentException if the parameters argument is + inappropriate. + + + Re-key the cipher. + + @param inKey the key to be used + + + Perform a left "spin" of the word. The rotation of the given + word x is rotated left by y bits. + Only the lg(wordSize) low-order bits of y + are used to determine the rotation amount. Here it is + assumed that the wordsize used is a power of 2. + + @param x word to rotate + @param y number of bits to rotate % wordSize + + + Perform a right "spin" of the word. The rotation of the given + word x is rotated left by y bits. + Only the lg(wordSize) low-order bits of y + are used to determine the rotation amount. Here it is + assumed that the wordsize used is a power of 2. + + @param x word to rotate + @param y number of bits to rotate % wordSize + + + an implementation of the RFC 3211 Key Wrap + Specification. + + + an implementation of Rijndael, based on the documentation and reference implementation + by Paulo Barreto, Vincent Rijmen, for v2.0 August '99. +

    + Note: this implementation is based on information prior to readonly NIST publication. +

    +
    + + multiply two elements of GF(2^m) + needed for MixColumn and InvMixColumn + + + xor corresponding text input and round key input bytes + + + Row 0 remains unchanged + The other three rows are shifted a variable amount + + + Replace every byte of the input by the byte at that place + in the nonlinear S-box + + + Mix the bytes of every column in a linear way + + + Mix the bytes of every column in a linear way + This is the opposite operation of Mixcolumn + + + Calculate the necessary round keys + The number of calculations depends on keyBits and blockBits + + + default constructor - 128 bit block size. + + + basic constructor - set the cipher up for a given blocksize + + @param blocksize the blocksize in bits, must be 128, 192, or 256. + + + initialise a Rijndael cipher. + + @param forEncryption whether or not we are for encryption. + @param parameters the parameters required to set up the cipher. + @exception ArgumentException if the parameters argument is + inappropriate. + + + this does your basic RSA algorithm with blinding + + + initialise the RSA engine. + + @param forEncryption true if we are encrypting, false otherwise. + @param param the necessary RSA key parameters. + + + Return the maximum size for an input block to this engine. + For RSA this is always one byte less than the key size on + encryption, and the same length as the key size on decryption. + + @return maximum size for an input block. + + + Return the maximum size for an output block to this engine. + For RSA this is always one byte less than the key size on + decryption, and the same length as the key size on encryption. + + @return maximum size for an output block. + + + Process a single block using the basic RSA algorithm. + + @param inBuf the input array. + @param inOff the offset into the input buffer where the data starts. + @param inLen the length of the data to be processed. + @return the result of the RSA process. + @exception DataLengthException the input block is too large. + + + This does your basic RSA Chaum's blinding and unblinding as outlined in + "Handbook of Applied Cryptography", page 475. You need to use this if you are + trying to get another party to generate signatures without them being aware + of the message they are signing. + + + Initialise the blinding engine. + + @param forEncryption true if we are encrypting (blinding), false otherwise. + @param param the necessary RSA key parameters. + + + Return the maximum size for an input block to this engine. + For RSA this is always one byte less than the key size on + encryption, and the same length as the key size on decryption. + + @return maximum size for an input block. + + + Return the maximum size for an output block to this engine. + For RSA this is always one byte less than the key size on + decryption, and the same length as the key size on encryption. + + @return maximum size for an output block. + + + Process a single block using the RSA blinding algorithm. + + @param in the input array. + @param inOff the offset into the input buffer where the data starts. + @param inLen the length of the data to be processed. + @return the result of the RSA process. + @throws DataLengthException the input block is too large. + + + this does your basic RSA algorithm. + + + initialise the RSA engine. + + @param forEncryption true if we are encrypting, false otherwise. + @param param the necessary RSA key parameters. + + + Return the maximum size for an input block to this engine. + For RSA this is always one byte less than the key size on + encryption, and the same length as the key size on decryption. + + @return maximum size for an input block. + + + Return the maximum size for an output block to this engine. + For RSA this is always one byte less than the key size on + decryption, and the same length as the key size on encryption. + + @return maximum size for an output block. + + + this does your basic RSA algorithm. + + + initialise the RSA engine. + + @param forEncryption true if we are encrypting, false otherwise. + @param param the necessary RSA key parameters. + + + Return the maximum size for an input block to this engine. + For RSA this is always one byte less than the key size on + encryption, and the same length as the key size on decryption. + + @return maximum size for an input block. + + + Return the maximum size for an output block to this engine. + For RSA this is always one byte less than the key size on + decryption, and the same length as the key size on encryption. + + @return maximum size for an output block. + + + Process a single block using the basic RSA algorithm. + + @param inBuf the input array. + @param inOff the offset into the input buffer where the data starts. + @param inLen the length of the data to be processed. + @return the result of the RSA process. + @exception DataLengthException the input block is too large. + + + Implementation of the SEED algorithm as described in RFC 4009 + + + + An implementation of the SEED key wrapper based on RFC 4010/RFC 3394. +

    + For further details see: http://www.ietf.org/rfc/rfc4010.txt. + + + + * Serpent is a 128-bit 32-round block cipher with variable key lengths, + * including 128, 192 and 256 bit keys conjectured to be at least as + * secure as three-key triple-DES. + *

    + * Serpent was designed by Ross Anderson, Eli Biham and Lars Knudsen as a + * candidate algorithm for the NIST AES Quest. + *

    + *

    + * For full details see The Serpent home page + *

    +
    + + initialise a Serpent cipher. + + @param encrypting whether or not we are for encryption. + @param params the parameters required to set up the cipher. + @throws IllegalArgumentException if the params argument is + inappropriate. + + + Process one block of input from the array in and write it to + the out array. + + @param in the array containing the input data. + @param inOff offset into the in array the data starts at. + @param out the array the output data will be copied into. + @param outOff the offset into the out array the output will start at. + @return the number of bytes processed and produced. + @throws DataLengthException if there isn't enough data in in, or + space in out. + @throws IllegalStateException if the cipher isn't initialised. + + + InvSO - {13, 3,11, 0,10, 6, 5,12, 1,14, 4, 7,15, 9, 8, 2 } - 15 terms. + + + S1 - {15,12, 2, 7, 9, 0, 5,10, 1,11,14, 8, 6,13, 3, 4 } - 14 terms. + + + InvS1 - { 5, 8, 2,14,15, 6,12, 3,11, 4, 7, 9, 1,13,10, 0 } - 14 steps. + + + S2 - { 8, 6, 7, 9, 3,12,10,15,13, 1,14, 4, 0,11, 5, 2 } - 16 terms. + + + InvS2 - {12, 9,15, 4,11,14, 1, 2, 0, 3, 6,13, 5, 8,10, 7 } - 16 steps. + + + S3 - { 0,15,11, 8,12, 9, 6, 3,13, 1, 2, 4,10, 7, 5,14 } - 16 terms. + + + InvS3 - { 0, 9,10, 7,11,14, 6,13, 3, 5,12, 2, 4, 8,15, 1 } - 15 terms + + + S4 - { 1,15, 8, 3,12, 0,11, 6, 2, 5, 4,10, 9,14, 7,13 } - 15 terms. + + + InvS4 - { 5, 0, 8, 3,10, 9, 7,14, 2,12,11, 6, 4,15,13, 1 } - 15 terms. + + + S5 - {15, 5, 2,11, 4,10, 9,12, 0, 3,14, 8,13, 6, 7, 1 } - 16 terms. + + + InvS5 - { 8,15, 2, 9, 4, 1,13,14,11, 6, 5, 3, 7,12,10, 0 } - 16 terms. + + + S6 - { 7, 2,12, 5, 8, 4, 6,11,14, 9, 1,15,13, 3,10, 0 } - 15 terms. + + + InvS6 - {15,10, 1,13, 5, 3, 6, 0, 4, 9,14, 7, 2,12, 8,11 } - 15 terms. + + + S7 - { 1,13,15, 0,14, 8, 2,11, 7, 4,12,10, 9, 3, 5, 6 } - 16 terms. + + + InvS7 - { 3, 0, 6,13, 9,14,15, 8, 5,12,11, 7,10, 1, 4, 2 } - 17 terms. + + + Apply the linear transformation to the register set. + + + Apply the inverse of the linear transformation to the register set. + + + Expand a user-supplied key material into a session key. + + @param key The user-key bytes (multiples of 4) to use. + @exception ArgumentException + + + Encrypt one block of plaintext. + + @param input the array containing the input data. + @param inOff offset into the in array the data starts at. + @param output the array the output data will be copied into. + @param outOff the offset into the out array the output will start at. + + + Decrypt one block of ciphertext. + + @param input the array containing the input data. + @param inOff offset into the in array the data starts at. + @param output the array the output data will be copied into. + @param outOff the offset into the out array the output will start at. + + + a class that provides a basic SKIPJACK engine. + + + initialise a SKIPJACK cipher. + + @param forEncryption whether or not we are for encryption. + @param parameters the parameters required to set up the cipher. + @exception ArgumentException if the parameters argument is + inappropriate. + + + The G permutation + + + the inverse of the G permutation. + + + An TEA engine. + + + Create an instance of the TEA encryption algorithm + and set some defaults + + + initialise + + @param forEncryption whether or not we are for encryption. + @param params the parameters required to set up the cipher. + @exception ArgumentException if the params argument is + inappropriate. + + + Re-key the cipher. + + @param key the key to be used + + + + Implementation of the Threefish tweakable large block cipher in 256, 512 and 1024 bit block + sizes. + + + This is the 1.3 version of Threefish defined in the Skein hash function submission to the NIST + SHA-3 competition in October 2010. +

    + Threefish was designed by Niels Ferguson - Stefan Lucks - Bruce Schneier - Doug Whiting - Mihir + Bellare - Tadayoshi Kohno - Jon Callas - Jesse Walker. +

    + This implementation inlines all round functions, unrolls 8 rounds, and uses 1.2k of static tables + to speed up key schedule injection.
    + 2 x block size state is retained by each cipher instance. + + + +

    + 256 bit block size - Threefish-256 + +
    + + + 512 bit block size - Threefish-512 + + + + + 1024 bit block size - Threefish-1024 + + + + Size of the tweak in bytes (always 128 bit/16 bytes) + + + Rounds in Threefish-256 + + + Rounds in Threefish-512 + + + Rounds in Threefish-1024 + + + Max rounds of any of the variants + + + Key schedule parity constant + + + Block size in bytes + + + Block size in 64 bit words + + + Buffer for byte oriented processBytes to call internal word API + + + Tweak bytes (2 byte t1,t2, calculated t3 and repeat of t1,t2 for modulo free lookup + + + Key schedule words + + + The internal cipher implementation (varies by blocksize) + + + + Constructs a new Threefish cipher, with a specified block size. + + the block size in bits, one of , , + . + + + + Initialise the engine. + + Initialise for encryption if true, for decryption if false. + an instance of or (to + use a 0 tweak) + + + + Initialise the engine, specifying the key and tweak directly. + + the cipher mode. + the words of the key, or null to use the current key. + the 2 word (128 bit) tweak, or null to use the current tweak. + + + + Process a block of data represented as 64 bit words. + + the number of 8 byte words processed (which will be the same as the block size). + a block sized buffer of words to process. + a block sized buffer of words to receive the output of the operation. + if either the input or output is not block sized + if this engine is not initialised + + + + Read a single 64 bit word from input in LSB first order. + + + + + Write a 64 bit word to output in LSB first order. + + + + Rotate left + xor part of the mix operation. + + + Rotate xor + rotate right part of the unmix operation. + + + The extended + repeated tweak words + + + The extended + repeated key words + + + Mix rotation constants defined in Skein 1.3 specification + + + Mix rotation constants defined in Skein 1.3 specification + + + Mix rotation constants defined in Skein 1.3 specification + + + Mix rotation constants defined in Skein 1.3 specification + + + Mix rotation constants defined in Skein 1.3 specification + + + Mix rotation constants defined in Skein 1.3 specification + + + Mix rotation constants defined in Skein 1.3 specification + + + Mix rotation constants defined in Skein 1.3 specification + + + Mix rotation constants defined in Skein 1.3 specification + + + Mix rotation constants defined in Skein 1.3 specification + + + Tnepres is a 128-bit 32-round block cipher with variable key lengths, + including 128, 192 and 256 bit keys conjectured to be at least as + secure as three-key triple-DES. +

    + Tnepres is based on Serpent which was designed by Ross Anderson, Eli Biham and Lars Knudsen as a + candidate algorithm for the NIST AES Quest. Unfortunately there was an endianness issue + with test vectors in the AES submission and the resulting confusion lead to the Tnepres cipher + as well, which is a byte swapped version of Serpent. +

    +

    + For full details see The Serpent home page +

    +
    + + Expand a user-supplied key material into a session key. + + @param key The user-key bytes (multiples of 4) to use. + @exception ArgumentException + + + Encrypt one block of plaintext. + + @param input the array containing the input data. + @param inOff offset into the in array the data starts at. + @param output the array the output data will be copied into. + @param outOff the offset into the out array the output will start at. + + + Decrypt one block of ciphertext. + + @param input the array containing the input data. + @param inOff offset into the in array the data starts at. + @param output the array the output data will be copied into. + @param outOff the offset into the out array the output will start at. + + + A class that provides Twofish encryption operations. + + This Java implementation is based on the Java reference + implementation provided by Bruce Schneier and developed + by Raif S. Naffah. + + + Define the fixed p0/p1 permutations used in keyed S-box lookup. + By changing the following constant definitions, the S-boxes will + automatically Get changed in the Twofish engine. + + + gSubKeys[] and gSBox[] are eventually used in the + encryption and decryption methods. + + + initialise a Twofish cipher. + + @param forEncryption whether or not we are for encryption. + @param parameters the parameters required to set up the cipher. + @exception ArgumentException if the parameters argument is + inappropriate. + + + Encrypt the given input starting at the given offset and place + the result in the provided buffer starting at the given offset. + The input will be an exact multiple of our blocksize. + + encryptBlock uses the pre-calculated gSBox[] and subKey[] + arrays. + + + Decrypt the given input starting at the given offset and place + the result in the provided buffer starting at the given offset. + The input will be an exact multiple of our blocksize. + + + Use (12, 8) Reed-Solomon code over GF(256) to produce + a key S-box 32-bit entity from 2 key material 32-bit + entities. + + @param k0 first 32-bit entity + @param k1 second 32-bit entity + @return Remainder polynomial Generated using RS code + + + * Reed-Solomon code parameters: (12,8) reversible code: + *

    + *

    +                    * G(x) = x^4 + (a+1/a)x^3 + ax^2 + (a+1/a)x + 1
    +                    * 
    + * where a = primitive root of field generator 0x14D + *

    +
    + + initialise a VMPC cipher. + + @param forEncryption + whether or not we are for encryption. + @param params + the parameters required to set up the cipher. + @exception ArgumentException + if the params argument is inappropriate. + + + + Implementation of Daniel J. Bernstein's XSalsa20 stream cipher - Salsa20 with an extended nonce. + + + XSalsa20 requires a 256 bit key, and a 192 bit nonce. + + + + + XSalsa20 key generation: process 256 bit input key and 128 bits of the input nonce + using a core Salsa20 function without input addition to produce 256 bit working key + and use that with the remaining 64 bits of nonce to initialize a standard Salsa20 engine state. + + + + An XTEA engine. + + + Create an instance of the TEA encryption algorithm + and set some defaults + + + initialise + + @param forEncryption whether or not we are for encryption. + @param params the parameters required to set up the cipher. + @exception ArgumentException if the params argument is + inappropriate. + + + Re-key the cipher. + + @param key the key to be used + + + Basic KDF generator for derived keys and ivs as defined by IEEE P1363a/ISO 18033 +
    + This implementation is based on ISO 18033/P1363a. +
    + + Construct a KDF Parameters generator. + + @param counterStart value of counter. + @param digest the digest to be used as the source of derived keys. + + + fill len bytes of the output buffer with bytes generated from + the derivation function. + + @throws ArgumentException if the size of the request will cause an overflow. + @throws DataLengthException if the out buffer is too small. + + + return the underlying digest. + + + Core of password hashing scheme Bcrypt, + designed by Niels Provos and David Mazières, + corresponds to the C reference implementation. +

    + This implementation does not correspondent to the 1999 published paper + "A Future-Adaptable Password Scheme" of Niels Provos and David Mazières, + see: https://www.usenix.org/legacy/events/usenix99/provos/provos_html/node1.html. + In contrast to the paper, the order of key setup and salt setup is reversed: + state <- ExpandKey(state, 0, key) + state %lt;- ExpandKey(state, 0, salt) + This corresponds to the OpenBSD reference implementation of Bcrypt. +

    + Note: + There is no successful cryptanalysis (status 2015), but + the amount of memory and the band width of Bcrypt + may be insufficient to effectively prevent attacks + with custom hardware like FPGAs, ASICs +

    + This implementation uses some parts of Bouncy Castle's BlowfishEngine. +

    +
    + + Size of the salt parameter in bytes + + + Minimum value of cost parameter, equal to log2(bytes of salt) + + + Maximum value of cost parameter (31 == 2,147,483,648) + + + Maximum size of password == max (unrestricted) size of Blowfish key + + + Derives a raw 192 bit Bcrypt key + + @param cost the cost factor, treated as an exponent of 2 + @param salt a 16 byte salt + @param psw the password + @return a 192 bit key + + + Calculates the bcrypt hash of a password. +

    + This implements the raw bcrypt function as defined in the bcrypt specification, not + the crypt encoded version implemented in OpenBSD. +

    + @param password the password bytes (up to 72 bytes) to use for this invocation. + @param salt the 128 bit salt to use for this invocation. + @param cost the bcrypt cost parameter. The cost of the bcrypt function grows as + 2^cost. Legal values are 4..31 inclusive. + @return the output of the raw bcrypt operation: a 192 bit (24 byte) hash. +
    + + initialise the key generator - if strength is set to zero + the key generated will be 64 bits in size, otherwise + strength can be 64 or 56 bits (if you don't count the parity bits). + + @param param the parameters to be used for key generation + + + initialise the key generator - if strength is set to zero + the key Generated will be 192 bits in size, otherwise + strength can be 128 or 192 (or 112 or 168 if you don't count + parity bits), depending on whether you wish to do 2-key or 3-key + triple DES. + + @param param the parameters to be used for key generation + + + a basic Diffie-Hellman key pair generator. + + This generates keys consistent for use with the basic algorithm for + Diffie-Hellman. + + + interface that a public/private key pair generator should conform to. + + + intialise the key pair generator. + + @param the parameters the key pair is to be initialised with. + + + return an AsymmetricCipherKeyPair containing the Generated keys. + + @return an AsymmetricCipherKeyPair containing the Generated keys. + + + a Diffie-Hellman key pair generator. + + This generates keys consistent for use in the MTI/A0 key agreement protocol + as described in "Handbook of Applied Cryptography", Pages 516-519. + + + which Generates the p and g values from the given parameters, + returning the DHParameters object. +

    + Note: can take a while...

    +
    + + a DSA key pair generator. + + This Generates DSA keys in line with the method described + in FIPS 186-3 B.1 FFC Key Pair Generation. + + + Generate suitable parameters for DSA, in line with FIPS 186-2, or FIPS 186-3. + + + Initialise the generator + This form can only be used for older DSA (pre-DSA2) parameters + the size of keys in bits (from 512 up to 1024, and a multiple of 64) + measure of robustness of primes (at least 80 for FIPS 186-2 compliance) + the source of randomness to use + + + Initialise the generator for DSA 2 + You must use this Init method if you need to generate parameters for DSA 2 keys + An instance of DsaParameterGenerationParameters used to configure this generator + + + Generates a set of DsaParameters + Can take a while... + + + generate suitable parameters for DSA, in line with + FIPS 186-3 A.1 Generation of the FFC Primes p and q. + + + Given the domain parameters this routine generates an EC key + pair in accordance with X9.62 section 5.2.1 pages 26, 27. + + + a ElGamal key pair generator. +

    + This Generates keys consistent for use with ElGamal as described in + page 164 of "Handbook of Applied Cryptography".

    +
    + + * which Generates the p and g values from the given parameters, + * returning the ElGamalParameters object. + *

    + * Note: can take a while... + *

    +
    + + a GOST3410 key pair generator. + This generates GOST3410 keys in line with the method described + in GOST R 34.10-94. + + + generate suitable parameters for GOST3410. + + + initialise the key generator. + + @param size size of the key + @param typeProcedure type procedure A,B = 1; A',B' - else + @param random random byte source. + + + Procedure C + procedure generates the a value from the given p,q, + returning the a value. + + + which generates the p , q and a values from the given parameters, + returning the Gost3410Parameters object. + + + HMAC-based Extract-and-Expand Key Derivation Function (HKDF) implemented + according to IETF RFC 5869, May 2010 as specified by H. Krawczyk, IBM + Research & P. Eronen, Nokia. It uses a HMac internally to compute de OKM + (output keying material) and is likely to have better security properties + than KDF's based on just a hash function. + + + Creates a HKDFBytesGenerator based on the given hash function. + + @param hash the digest to be used as the source of generatedBytes bytes + + + Performs the extract part of the key derivation function. + + @param salt the salt to use + @param ikm the input keying material + @return the PRK as KeyParameter + + + Performs the expand part of the key derivation function, using currentT + as input and output buffer. + + @throws DataLengthException if the total number of bytes generated is larger than the one + specified by RFC 5869 (255 * HashLen) + + + KFD2 generator for derived keys and ivs as defined by IEEE P1363a/ISO 18033 +
    + This implementation is based on IEEE P1363/ISO 18033. +
    + + Construct a KDF1 byte generator. + + @param digest the digest to be used as the source of derived keys. + + + KDF2 generator for derived keys and ivs as defined by IEEE P1363a/ISO 18033 +
    + This implementation is based on IEEE P1363/ISO 18033. +
    + + Construct a KDF2 bytes generator. Generates key material + according to IEEE P1363 or ISO 18033 depending on the initialisation. + + @param digest the digest to be used as the source of derived keys. + + + Generator for MGF1 as defined in Pkcs 1v2 + + + @param digest the digest to be used as the source of Generated bytes + + + int to octet string. + + + fill len bytes of the output buffer with bytes Generated from + the derivation function. + + @throws DataLengthException if the out buffer is too small. + + + return the underlying digest. + + + Key generation parameters for NaccacheStern cipher. For details on this cipher, please see + + http://www.gemplus.com/smart/rd/publications/pdf/NS98pkcs.pdf + + + Generates a permuted ArrayList from the original one. The original List + is not modified + + @param arr + the ArrayList to be permuted + @param rand + the source of Randomness for permutation + @return a new IList with the permuted elements. + + + Finds the first 'count' primes starting with 3 + + @param count + the number of primes to find + @return a vector containing the found primes as Integer + + + Password hashing scheme BCrypt, + designed by Niels Provos and David Mazières, using the + String format and the Base64 encoding + of the reference implementation on OpenBSD + + + Creates a 60 character Bcrypt String, including + version, cost factor, salt and hash, separated by '$' + + @param cost the cost factor, treated as an exponent of 2 + @param salt a 16 byte salt + @param password the password + @return a 60 character Bcrypt String + + + Creates a 60 character Bcrypt String, including + version, cost factor, salt and hash, separated by '$' + + @param cost the cost factor, treated as an exponent of 2 + @param salt a 16 byte salt + @param password the password + @return a 60 character Bcrypt String + + + Checks if a password corresponds to a 60 character Bcrypt String + + @param bcryptString a 60 character Bcrypt String, including + version, cost factor, salt and hash, + separated by '$' + @param password the password as an array of chars + @return true if the password corresponds to the + Bcrypt String, otherwise false + + + Generator for PBE derived keys and ivs as usd by OpenSSL. +

    + The scheme is a simple extension of PKCS 5 V2.0 Scheme 1 using MD5 with an + iteration count of 1. +

    +
    + + super class for all Password Based Encyrption (Pbe) parameter generator classes. + + + base constructor. + + + initialise the Pbe generator. + + @param password the password converted into bytes (see below). + @param salt the salt to be mixed with the password. + @param iterationCount the number of iterations the "mixing" function + is to be applied for. + + + return the password byte array. + + @return the password byte array. + + + return the salt byte array. + + @return the salt byte array. + + + Generate derived parameters for a key of length keySize. + + @param keySize the length, in bits, of the key required. + @return a parameters object representing a key. + + + Generate derived parameters for a key of length keySize, and + an initialisation vector (IV) of length ivSize. + + @param keySize the length, in bits, of the key required. + @param ivSize the length, in bits, of the iv required. + @return a parameters object representing a key and an IV. + + + Generate derived parameters for a key of length keySize, specifically + for use with a MAC. + + @param keySize the length, in bits, of the key required. + @return a parameters object representing a key. + + + converts a password to a byte array according to the scheme in + Pkcs5 (ascii, no padding) + + @param password a character array representing the password. + @return a byte array representing the password. + + + converts a password to a byte array according to the scheme in + PKCS5 (UTF-8, no padding) + + @param password a character array representing the password. + @return a byte array representing the password. + + + converts a password to a byte array according to the scheme in + Pkcs12 (unicode, big endian, 2 zero pad bytes at the end). + + @param password a character array representing the password. + @return a byte array representing the password. + + + return the iteration count. + + @return the iteration count. + + + Construct a OpenSSL Parameters generator. + + + Initialise - note the iteration count for this algorithm is fixed at 1. + + @param password password to use. + @param salt salt to use. + + + the derived key function, the ith hash of the password and the salt. + + + Generate a key parameter derived from the password, salt, and iteration + count we are currently initialised with. + + @param keySize the size of the key we want (in bits) + @return a KeyParameter object. + @exception ArgumentException if the key length larger than the base hash size. + + + Generate a key with initialisation vector parameter derived from + the password, salt, and iteration count we are currently initialised + with. + + @param keySize the size of the key we want (in bits) + @param ivSize the size of the iv we want (in bits) + @return a ParametersWithIV object. + @exception ArgumentException if keySize + ivSize is larger than the base hash size. + + + Generate a key parameter for use with a MAC derived from the password, + salt, and iteration count we are currently initialised with. + + @param keySize the size of the key we want (in bits) + @return a KeyParameter object. + @exception ArgumentException if the key length larger than the base hash size. + + + Generator for Pbe derived keys and ivs as defined by Pkcs 12 V1.0. +

    + The document this implementation is based on can be found at + + RSA's Pkcs12 Page +

    +
    + + Construct a Pkcs 12 Parameters generator. + + @param digest the digest to be used as the source of derived keys. + @exception ArgumentException if an unknown digest is passed in. + + + add a + b + 1, returning the result in a. The a value is treated + as a BigInteger of length (b.Length * 8) bits. The result is + modulo 2^b.Length in case of overflow. + + + generation of a derived key ala Pkcs12 V1.0. + + + Generate a key parameter derived from the password, salt, and iteration + count we are currently initialised with. + + @param keySize the size of the key we want (in bits) + @return a KeyParameter object. + + + Generate a key with initialisation vector parameter derived from + the password, salt, and iteration count we are currently initialised + with. + + @param keySize the size of the key we want (in bits) + @param ivSize the size of the iv we want (in bits) + @return a ParametersWithIV object. + + + Generate a key parameter for use with a MAC derived from the password, + salt, and iteration count we are currently initialised with. + + @param keySize the size of the key we want (in bits) + @return a KeyParameter object. + + + Generator for Pbe derived keys and ivs as defined by Pkcs 5 V2.0 Scheme 1. + Note this generator is limited to the size of the hash produced by the + digest used to drive it. +

    + The document this implementation is based on can be found at + + RSA's Pkcs5 Page +

    +
    + + Construct a Pkcs 5 Scheme 1 Parameters generator. + + @param digest the digest to be used as the source of derived keys. + + + the derived key function, the ith hash of the mPassword and the mSalt. + + + Generate a key parameter derived from the mPassword, mSalt, and iteration + count we are currently initialised with. + + @param keySize the size of the key we want (in bits) + @return a KeyParameter object. + @exception ArgumentException if the key length larger than the base hash size. + + + Generate a key with initialisation vector parameter derived from + the mPassword, mSalt, and iteration count we are currently initialised + with. + + @param keySize the size of the key we want (in bits) + @param ivSize the size of the iv we want (in bits) + @return a ParametersWithIV object. + @exception ArgumentException if keySize + ivSize is larger than the base hash size. + + + Generate a key parameter for use with a MAC derived from the mPassword, + mSalt, and iteration count we are currently initialised with. + + @param keySize the size of the key we want (in bits) + @return a KeyParameter object. + @exception ArgumentException if the key length larger than the base hash size. + + + Generator for Pbe derived keys and ivs as defined by Pkcs 5 V2.0 Scheme 2. + This generator uses a SHA-1 HMac as the calculation function. +

    + The document this implementation is based on can be found at + + RSA's Pkcs5 Page

    +
    + + construct a Pkcs5 Scheme 2 Parameters generator. + + + Generate a key parameter derived from the password, salt, and iteration + count we are currently initialised with. + + @param keySize the size of the key we want (in bits) + @return a KeyParameter object. + + + Generate a key with initialisation vector parameter derived from + the password, salt, and iteration count we are currently initialised + with. + + @param keySize the size of the key we want (in bits) + @param ivSize the size of the iv we want (in bits) + @return a ParametersWithIV object. + + + Generate a key parameter for use with a MAC derived from the password, + salt, and iteration count we are currently initialised with. + + @param keySize the size of the key we want (in bits) + @return a KeyParameter object. + + + + Generates keys for the Poly1305 MAC. + + + Poly1305 keys are 256 bit keys consisting of a 128 bit secret key used for the underlying block + cipher followed by a 128 bit {@code r} value used for the polynomial portion of the Mac.
    + The {@code r} value has a specific format with some bits required to be cleared, resulting in an + effective 106 bit key.
    + A separately generated 256 bit key can be modified to fit the Poly1305 key format by using the + {@link #clamp(byte[])} method to clear the required bits. +
    + +
    + + + Initialises the key generator. + + + Poly1305 keys are always 256 bits, so the key length in the provided parameters is ignored. + + + + + Generates a 256 bit key in the format required for Poly1305 - e.g. + k[0] ... k[15], r[0] ... r[15] with the required bits in r cleared + as per . + + + + + Modifies an existing 32 byte key value to comply with the requirements of the Poly1305 key by + clearing required bits in the r (second 16 bytes) portion of the key.
    + Specifically: +
      +
    • r[3], r[7], r[11], r[15] have top four bits clear (i.e., are {0, 1, . . . , 15})
    • +
    • r[4], r[8], r[12] have bottom two bits clear (i.e., are in {0, 4, 8, . . . , 252})
    • +
    +
    + a 32 byte key value k[0] ... k[15], r[0] ... r[15] +
    + + + Checks a 32 byte key for compliance with the Poly1305 key requirements, e.g. + k[0] ... k[15], r[0] ... r[15] with the required bits in r cleared + as per . + + Key. + if the key is of the wrong length, or has invalid bits set + in the r portion of the key. + + + Generate a random factor suitable for use with RSA blind signatures + as outlined in Chaum's blinding and unblinding as outlined in + "Handbook of Applied Cryptography", page 475. + + + Initialise the factor generator + + @param param the necessary RSA key parameters. + + + Generate a suitable blind factor for the public key the generator was initialised with. + + @return a random blind factor + + + an RSA key pair generator. + + + Choose a random prime value for use with RSA + the bit-length of the returned prime + the RSA public exponent + a prime p, with (p-1) relatively prime to e + + + + Operators that reduce their input to a single block return an object + of this type. + + + + + Return the final result of the operation. + + A block of bytes, representing the result of an operation. + + + + Store the final result of the operation by copying it into the destination array. + + The number of bytes copied into destination. + The byte array to copy the result into. + The offset into destination to start copying the result at. + + + interface for classes implementing the Digital Signature Algorithm + + + initialise the signer for signature generation or signature + verification. + + @param forSigning true if we are generating a signature, false + otherwise. + @param param key parameters for signature generation. + + + sign the passed in message (usually the output of a hash function). + + @param message the message to be signed. + @return two big integers representing the r and s values respectively. + + + verify the message message against the signature values r and s. + + @param message the message that was supposed to have been signed. + @param r the r signature value. + @param s the s signature value. + + + + Base interface describing an entropy source for a DRBG. + + + + + Return a byte array of entropy. + + The entropy bytes. + + + + Return whether or not this entropy source is regarded as prediction resistant. + + true if this instance is prediction resistant; otherwise, false. + + + + Return the number of bits of entropy this source can produce. + + The size, in bits, of the return value of getEntropy. + + + + Base interface describing a provider of entropy sources. + + + + + Return an entropy source providing a block of entropy. + + The size of the block of entropy required. + An entropy source providing bitsRequired blocks of entropy. + + + The base interface for implementations of message authentication codes (MACs). + + + Initialise the MAC. + + @param param the key and other data required by the MAC. + @exception ArgumentException if the parameters argument is + inappropriate. + + + Return the block size for this MAC (in bytes). + + @return the block size for this MAC in bytes. + + + add a single byte to the mac for processing. + + @param in the byte to be processed. + @exception InvalidOperationException if the MAC is not initialised. + + + @param in the array containing the input. + @param inOff the index in the array the data begins at. + @param len the length of the input starting at inOff. + @exception InvalidOperationException if the MAC is not initialised. + @exception DataLengthException if there isn't enough data in in. + + + Compute the final stage of the MAC writing the output to the out + parameter. +

    + doFinal leaves the MAC in the same state it was after the last init. +

    + @param out the array the MAC is to be output to. + @param outOff the offset into the out buffer the output is to start at. + @exception DataLengthException if there isn't enough space in out. + @exception InvalidOperationException if the MAC is not initialised. +
    + + Reset the MAC. At the end of resetting the MAC should be in the + in the same state it was after the last init (if there was one). + + + Return the name of the algorithm the MAC implements. + + @return the name of the algorithm the MAC implements. + + + this exception is thrown whenever we find something we don't expect in a + message. + + + base constructor. + + + create a InvalidCipherTextException with the given message. + + @param message the message to be carried with the exception. + + + + Base interface for operators that serve as stream-based signature calculators. + + + + + Create a stream calculator for this signature calculator. The stream + calculator is used for the actual operation of entering the data to be signed + and producing the signature block. + + A calculator producing an IBlockResult with a signature in it. + + + The algorithm details object for this calculator. + + + Initialise the signer for signing or verification. + + @param forSigning true if for signing, false otherwise + @param param necessary parameters. + + + update the internal digest with the byte b + + + update the internal digest with the byte array in + + + Generate a signature for the message we've been loaded with using + the key we were initialised with. + + + return true if the internal state represents the signature described + in the passed in array. + + + reset the internal state + + + Return the name of the algorithm the signer implements. + + @return the name of the algorithm the signer implements. + + + Signer with message recovery. + + + Returns true if the signer has recovered the full message as + part of signature verification. + + @return true if full message recovered. + + + Returns a reference to what message was recovered (if any). + + @return full/partial message, null if nothing. + + + Perform an update with the recovered message before adding any other data. This must + be the first update method called, and calling it will result in the signer assuming + that further calls to update will include message content past what is recoverable. + + @param signature the signature that we are in the process of verifying. + @throws IllegalStateException + + + + Base interface for cryptographic operations such as Hashes, MACs, and Signatures which reduce a stream of data + to a single value. + + + + + Return the result of processing the stream. This value is only available once the stream + has been closed. + + The result of processing the stream. + + + Return a "sink" stream which only exists to update the implementing object. + A stream to write to in order to update the implementing object. + + + + Operators that reduce their input to the validation of a signature produce this type. + + + + + Return true if the passed in data matches what is expected by the verification result. + + The bytes representing the signature. + true if the signature verifies, false otherwise. + + + + Return true if the length bytes from off in the source array match the signature + expected by the verification result. + + Byte array containing the signature. + The offset into the source array where the signature starts. + The number of bytes in source making up the signature. + true if the signature verifies, false otherwise. + + + + Base interface for operators that serve as stream-based signature verifiers. + + + + + Create a stream calculator for this verifier. The stream + calculator is used for the actual operation of entering the data to be verified + and producing a result which can be used to verify the original signature. + + A calculator producing an IVerifier which can verify the signature. + + + The algorithm details object for this verifier. + + + + Base interface for a provider to support the dynamic creation of signature verifiers. + + + + + Return a signature verfier for signature algorithm described in the passed in algorithm details object. + + The details of the signature algorithm verification is required for. + A new signature verifier. + + + The base class for parameters to key generators. + + + initialise the generator with a source of randomness + and a strength (in bits). + + @param random the random byte source. + @param strength the size, in bits, of the keys we want to produce. + + + return the random source associated with this + generator. + + @return the generators random source. + + + return the bit strength for keys produced by this generator, + + @return the strength of the keys this generator produces (in bits). + + + standard CBC Block Cipher MAC - if no padding is specified the default of + pad of zeroes is used. + + + create a standard MAC based on a CBC block cipher. This will produce an + authentication code half the length of the block size of the cipher. + + @param cipher the cipher to be used as the basis of the MAC generation. + + + create a standard MAC based on a CBC block cipher. This will produce an + authentication code half the length of the block size of the cipher. + + @param cipher the cipher to be used as the basis of the MAC generation. + @param padding the padding to be used to complete the last block. + + + create a standard MAC based on a block cipher with the size of the + MAC been given in bits. This class uses CBC mode as the basis for the + MAC generation. +

    + Note: the size of the MAC must be at least 24 bits (FIPS Publication 81), + or 16 bits if being used as a data authenticator (FIPS Publication 113), + and in general should be less than the size of the block cipher as it reduces + the chance of an exhaustive attack (see Handbook of Applied Cryptography). +

    + @param cipher the cipher to be used as the basis of the MAC generation. + @param macSizeInBits the size of the MAC in bits, must be a multiple of 8. +
    + + create a standard MAC based on a block cipher with the size of the + MAC been given in bits. This class uses CBC mode as the basis for the + MAC generation. +

    + Note: the size of the MAC must be at least 24 bits (FIPS Publication 81), + or 16 bits if being used as a data authenticator (FIPS Publication 113), + and in general should be less than the size of the block cipher as it reduces + the chance of an exhaustive attack (see Handbook of Applied Cryptography). +

    + @param cipher the cipher to be used as the basis of the MAC generation. + @param macSizeInBits the size of the MAC in bits, must be a multiple of 8. + @param padding the padding to be used to complete the last block. +
    + + Reset the mac generator. + + + implements a Cipher-FeedBack (CFB) mode on top of a simple cipher. + + + Basic constructor. + + @param cipher the block cipher to be used as the basis of the + feedback mode. + @param blockSize the block size in bits (note: a multiple of 8) + + + Initialise the cipher and, possibly, the initialisation vector (IV). + If an IV isn't passed as part of the parameter, the IV will be all zeros. + An IV which is too short is handled in FIPS compliant fashion. + + @param param the key and other data required by the cipher. + @exception ArgumentException if the parameters argument is + inappropriate. + + + return the block size we are operating at. + + @return the block size we are operating at (in bytes). + + + Process one block of input from the array in and write it to + the out array. + + @param in the array containing the input data. + @param inOff offset into the in array the data starts at. + @param out the array the output data will be copied into. + @param outOff the offset into the out array the output will start at. + @exception DataLengthException if there isn't enough data in in, or + space in out. + @exception InvalidOperationException if the cipher isn't initialised. + @return the number of bytes processed and produced. + + + reset the chaining vector back to the IV and reset the underlying + cipher. + + + return the algorithm name and mode. + + @return the name of the underlying algorithm followed by "/CFB" + and the block size in bits. + + + create a standard MAC based on a CFB block cipher. This will produce an + authentication code half the length of the block size of the cipher, with + the CFB mode set to 8 bits. + + @param cipher the cipher to be used as the basis of the MAC generation. + + + create a standard MAC based on a CFB block cipher. This will produce an + authentication code half the length of the block size of the cipher, with + the CFB mode set to 8 bits. + + @param cipher the cipher to be used as the basis of the MAC generation. + @param padding the padding to be used. + + + create a standard MAC based on a block cipher with the size of the + MAC been given in bits. This class uses CFB mode as the basis for the + MAC generation. +

    + Note: the size of the MAC must be at least 24 bits (FIPS Publication 81), + or 16 bits if being used as a data authenticator (FIPS Publication 113), + and in general should be less than the size of the block cipher as it reduces + the chance of an exhaustive attack (see Handbook of Applied Cryptography). +

    + @param cipher the cipher to be used as the basis of the MAC generation. + @param cfbBitSize the size of an output block produced by the CFB mode. + @param macSizeInBits the size of the MAC in bits, must be a multiple of 8. +
    + + create a standard MAC based on a block cipher with the size of the + MAC been given in bits. This class uses CFB mode as the basis for the + MAC generation. +

    + Note: the size of the MAC must be at least 24 bits (FIPS Publication 81), + or 16 bits if being used as a data authenticator (FIPS Publication 113), + and in general should be less than the size of the block cipher as it reduces + the chance of an exhaustive attack (see Handbook of Applied Cryptography). +

    + @param cipher the cipher to be used as the basis of the MAC generation. + @param cfbBitSize the size of an output block produced by the CFB mode. + @param macSizeInBits the size of the MAC in bits, must be a multiple of 8. + @param padding a padding to be used. +
    + + Reset the mac generator. + + + CMAC - as specified at www.nuee.nagoya-u.ac.jp/labs/tiwata/omac/omac.html +

    + CMAC is analogous to OMAC1 - see also en.wikipedia.org/wiki/CMAC +

    + CMAC is a NIST recomendation - see + csrc.nist.gov/CryptoToolkit/modes/800-38_Series_Publications/SP800-38B.pdf +

    + CMAC/OMAC1 is a blockcipher-based message authentication code designed and + analyzed by Tetsu Iwata and Kaoru Kurosawa. +

    + CMAC/OMAC1 is a simple variant of the CBC MAC (Cipher Block Chaining Message + Authentication Code). OMAC stands for One-Key CBC MAC. +

    + It supports 128- or 64-bits block ciphers, with any key size, and returns + a MAC with dimension less or equal to the block size of the underlying + cipher. +

    +
    + + create a standard MAC based on a CBC block cipher (64 or 128 bit block). + This will produce an authentication code the length of the block size + of the cipher. + + @param cipher the cipher to be used as the basis of the MAC generation. + + + create a standard MAC based on a block cipher with the size of the + MAC been given in bits. +

    + Note: the size of the MAC must be at least 24 bits (FIPS Publication 81), + or 16 bits if being used as a data authenticator (FIPS Publication 113), + and in general should be less than the size of the block cipher as it reduces + the chance of an exhaustive attack (see Handbook of Applied Cryptography). + + @param cipher the cipher to be used as the basis of the MAC generation. + @param macSizeInBits the size of the MAC in bits, must be a multiple of 8 and @lt;= 128. + + + Reset the mac generator. + + +

    + The GMAC specialisation of Galois/Counter mode (GCM) detailed in NIST Special Publication + 800-38D. + + + GMac is an invocation of the GCM mode where no data is encrypted (i.e. all input data to the Mac + is processed as additional authenticated data with the underlying GCM block cipher). + +
    + + + Creates a GMAC based on the operation of a block cipher in GCM mode. + + + This will produce an authentication code the length of the block size of the cipher. + + the cipher to be used in GCM mode to generate the MAC. + + + + Creates a GMAC based on the operation of a 128 bit block cipher in GCM mode. + + + This will produce an authentication code the length of the block size of the cipher. + + the cipher to be used in GCM mode to generate the MAC. + the mac size to generate, in bits. Must be a multiple of 8, between 32 and 128 (inclusive). + Sizes less than 96 are not recommended, but are supported for specialized applications. + + + + Initialises the GMAC - requires a + providing a and a nonce. + + + + implementation of GOST 28147-89 MAC + + + HMAC implementation based on RFC2104 + + H(K XOR opad, H(K XOR ipad, text)) + + + Reset the mac generator. + + + DES based CBC Block Cipher MAC according to ISO9797, algorithm 3 (ANSI X9.19 Retail MAC) + + This could as well be derived from CBCBlockCipherMac, but then the property mac in the base + class must be changed to protected + + + create a Retail-MAC based on a CBC block cipher. This will produce an + authentication code of the length of the block size of the cipher. + + @param cipher the cipher to be used as the basis of the MAC generation. This must + be DESEngine. + + + create a Retail-MAC based on a CBC block cipher. This will produce an + authentication code of the length of the block size of the cipher. + + @param cipher the cipher to be used as the basis of the MAC generation. + @param padding the padding to be used to complete the last block. + + + create a Retail-MAC based on a block cipher with the size of the + MAC been given in bits. This class uses single DES CBC mode as the basis for the + MAC generation. +

    + Note: the size of the MAC must be at least 24 bits (FIPS Publication 81), + or 16 bits if being used as a data authenticator (FIPS Publication 113), + and in general should be less than the size of the block cipher as it reduces + the chance of an exhaustive attack (see Handbook of Applied Cryptography). +

    + @param cipher the cipher to be used as the basis of the MAC generation. + @param macSizeInBits the size of the MAC in bits, must be a multiple of 8. +
    + + create a standard MAC based on a block cipher with the size of the + MAC been given in bits. This class uses single DES CBC mode as the basis for the + MAC generation. The final block is decrypted and then encrypted using the + middle and right part of the key. +

    + Note: the size of the MAC must be at least 24 bits (FIPS Publication 81), + or 16 bits if being used as a data authenticator (FIPS Publication 113), + and in general should be less than the size of the block cipher as it reduces + the chance of an exhaustive attack (see Handbook of Applied Cryptography). +

    + @param cipher the cipher to be used as the basis of the MAC generation. + @param macSizeInBits the size of the MAC in bits, must be a multiple of 8. + @param padding the padding to be used to complete the last block. +
    + + Reset the mac generator. + + + + Poly1305 message authentication code, designed by D. J. Bernstein. + + + Poly1305 computes a 128-bit (16 bytes) authenticator, using a 128 bit nonce and a 256 bit key + consisting of a 128 bit key applied to an underlying cipher, and a 128 bit key (with 106 + effective key bits) used in the authenticator. + + The polynomial calculation in this implementation is adapted from the public domain poly1305-donna-unrolled C implementation + by Andrew M (@floodyberry). + + + + + Polynomial key + + + Polynomial key + + + Polynomial key + + + Polynomial key + + + Polynomial key + + + Precomputed 5 * r[1..4] + + + Precomputed 5 * r[1..4] + + + Precomputed 5 * r[1..4] + + + Precomputed 5 * r[1..4] + + + Encrypted nonce + + + Encrypted nonce + + + Encrypted nonce + + + Encrypted nonce + + + Current block of buffered input + + + Current offset in input buffer + + + Polynomial accumulator + + + Polynomial accumulator + + + Polynomial accumulator + + + Polynomial accumulator + + + Polynomial accumulator + + + Constructs a Poly1305 MAC, where the key passed to init() will be used directly. + + + Constructs a Poly1305 MAC, using a 128 bit block cipher. + + + + Initialises the Poly1305 MAC. + + a {@link ParametersWithIV} containing a 128 bit nonce and a {@link KeyParameter} with + a 256 bit key complying to the {@link Poly1305KeyGenerator Poly1305 key format}. + + + + Implementation of SipHash as specified in "SipHash: a fast short-input PRF", by Jean-Philippe + Aumasson and Daniel J. Bernstein (https://131002.net/siphash/siphash.pdf). + + + "SipHash is a family of PRFs SipHash-c-d where the integer parameters c and d are the number of + compression rounds and the number of finalization rounds. A compression round is identical to a + finalization round and this round function is called SipRound. Given a 128-bit key k and a + (possibly empty) byte string m, SipHash-c-d returns a 64-bit value..." + + + + SipHash-2-4 + + + SipHash-c-d + the number of compression rounds + the number of finalization rounds + + + + Implementation of the Skein parameterised MAC function in 256, 512 and 1024 bit block sizes, + based on the Threefish tweakable block cipher. + + + This is the 1.3 version of Skein defined in the Skein hash function submission to the NIST SHA-3 + competition in October 2010. +

    + Skein was designed by Niels Ferguson - Stefan Lucks - Bruce Schneier - Doug Whiting - Mihir + Bellare - Tadayoshi Kohno - Jon Callas - Jesse Walker. + + + + + +

    + 256 bit block size - Skein-256 + +
    + + + 512 bit block size - Skein-512 + + + + + 1024 bit block size - Skein-1024 + + + + + Constructs a Skein MAC with an internal state size and output size. + + the internal state size in bits - one of or + . + the output/MAC size to produce in bits, which must be an integral number of + bytes. + + + + Optionally initialises the Skein digest with the provided parameters. + + See for details on the parameterisation of the Skein hash function. + the parameters to apply to this engine, or null to use no parameters. + + + + This exception is thrown whenever a cipher requires a change of key, iv + or similar after x amount of bytes enciphered + + + + implements Cipher-Block-Chaining (CBC) mode on top of a simple cipher. + + + Basic constructor. + + @param cipher the block cipher to be used as the basis of chaining. + + + return the underlying block cipher that we are wrapping. + + @return the underlying block cipher that we are wrapping. + + + Initialise the cipher and, possibly, the initialisation vector (IV). + If an IV isn't passed as part of the parameter, the IV will be all zeros. + + @param forEncryption if true the cipher is initialised for + encryption, if false for decryption. + @param param the key and other data required by the cipher. + @exception ArgumentException if the parameters argument is + inappropriate. + + + return the block size of the underlying cipher. + + @return the block size of the underlying cipher. + + + Process one block of input from the array in and write it to + the out array. + + @param in the array containing the input data. + @param inOff offset into the in array the data starts at. + @param out the array the output data will be copied into. + @param outOff the offset into the out array the output will start at. + @exception DataLengthException if there isn't enough data in in, or + space in out. + @exception InvalidOperationException if the cipher isn't initialised. + @return the number of bytes processed and produced. + + + reset the chaining vector back to the IV and reset the underlying + cipher. + + + Do the appropriate chaining step for CBC mode encryption. + + @param in the array containing the data to be encrypted. + @param inOff offset into the in array the data starts at. + @param out the array the encrypted data will be copied into. + @param outOff the offset into the out array the output will start at. + @exception DataLengthException if there isn't enough data in in, or + space in out. + @exception InvalidOperationException if the cipher isn't initialised. + @return the number of bytes processed and produced. + + + Do the appropriate chaining step for CBC mode decryption. + + @param in the array containing the data to be decrypted. + @param inOff offset into the in array the data starts at. + @param out the array the decrypted data will be copied into. + @param outOff the offset into the out array the output will start at. + @exception DataLengthException if there isn't enough data in in, or + space in out. + @exception InvalidOperationException if the cipher isn't initialised. + @return the number of bytes processed and produced. + + + return the algorithm name and mode. + + @return the name of the underlying algorithm followed by "/CBC". + + + Implements the Counter with Cipher Block Chaining mode (CCM) detailed in + NIST Special Publication 800-38C. +

    + Note: this mode is a packet mode - it needs all the data up front. +

    +
    + + + A block cipher mode that includes authenticated encryption with a streaming mode + and optional associated data. + + + + The block cipher underlying this algorithm. + + + Initialise the cipher. + Parameter can either be an AeadParameters or a ParametersWithIV object. + Initialise for encryption if true, for decryption if false. + The key or other data required by the cipher. + + + The block size for this cipher, in bytes. + + + Add a single byte to the associated data check. + If the implementation supports it, this will be an online operation and will not retain the associated data. + The byte to be processed. + + + Add a sequence of bytes to the associated data check. + If the implementation supports it, this will be an online operation and will not retain the associated data. + The input byte array. + The offset into the input array where the data to be processed starts. + The number of bytes to be processed. + + + Encrypt/decrypt a single byte. + + @param input the byte to be processed. + @param outBytes the output buffer the processed byte goes into. + @param outOff the offset into the output byte array the processed data starts at. + @return the number of bytes written to out. + @exception DataLengthException if the output buffer is too small. + + + Process a block of bytes from in putting the result into out. + + @param inBytes the input byte array. + @param inOff the offset into the in array where the data to be processed starts. + @param len the number of bytes to be processed. + @param outBytes the output buffer the processed bytes go into. + @param outOff the offset into the output byte array the processed data starts at. + @return the number of bytes written to out. + @exception DataLengthException if the output buffer is too small. + + + Finish the operation either appending or verifying the MAC at the end of the data. + + @param outBytes space for any resulting output data. + @param outOff offset into out to start copying the data at. + @return number of bytes written into out. + @throws InvalidOperationException if the cipher is in an inappropriate state. + @throws InvalidCipherTextException if the MAC fails to match. + + + Return the value of the MAC associated with the last stream processed. + + @return MAC for plaintext data. + + + Return the size of the output buffer required for a ProcessBytes + an input of len bytes. + + @param len the length of the input. + @return the space required to accommodate a call to ProcessBytes + with len bytes of input. + + + Return the size of the output buffer required for a ProcessBytes plus a + DoFinal with an input of len bytes. + + @param len the length of the input. + @return the space required to accommodate a call to ProcessBytes and DoFinal + with len bytes of input. + + + + Reset the cipher to the same state as it was after the last init (if there was one). + + + + The name of the algorithm this cipher implements. + + + Basic constructor. + + @param cipher the block cipher to be used. + + + return the underlying block cipher that we are wrapping. + + @return the underlying block cipher that we are wrapping. + + + Returns a byte array containing the mac calculated as part of the + last encrypt or decrypt operation. + + @return the last mac calculated. + + + Process a packet of data for either CCM decryption or encryption. + + @param in data for processing. + @param inOff offset at which data starts in the input array. + @param inLen length of the data in the input array. + @return a byte array containing the processed input.. + @throws IllegalStateException if the cipher is not appropriately set up. + @throws InvalidCipherTextException if the input data is truncated or the mac check fails. + + + Process a packet of data for either CCM decryption or encryption. + + @param in data for processing. + @param inOff offset at which data starts in the input array. + @param inLen length of the data in the input array. + @param output output array. + @param outOff offset into output array to start putting processed bytes. + @return the number of bytes added to output. + @throws IllegalStateException if the cipher is not appropriately set up. + @throws InvalidCipherTextException if the input data is truncated or the mac check fails. + @throws DataLengthException if output buffer too short. + + + implements a Cipher-FeedBack (CFB) mode on top of a simple cipher. + + + Basic constructor. + + @param cipher the block cipher to be used as the basis of the + feedback mode. + @param blockSize the block size in bits (note: a multiple of 8) + + + return the underlying block cipher that we are wrapping. + + @return the underlying block cipher that we are wrapping. + + + Initialise the cipher and, possibly, the initialisation vector (IV). + If an IV isn't passed as part of the parameter, the IV will be all zeros. + An IV which is too short is handled in FIPS compliant fashion. + + @param forEncryption if true the cipher is initialised for + encryption, if false for decryption. + @param param the key and other data required by the cipher. + @exception ArgumentException if the parameters argument is + inappropriate. + + + return the block size we are operating at. + + @return the block size we are operating at (in bytes). + + + Process one block of input from the array in and write it to + the out array. + + @param in the array containing the input data. + @param inOff offset into the in array the data starts at. + @param out the array the output data will be copied into. + @param outOff the offset into the out array the output will start at. + @exception DataLengthException if there isn't enough data in in, or + space in out. + @exception InvalidOperationException if the cipher isn't initialised. + @return the number of bytes processed and produced. + + + Do the appropriate processing for CFB mode encryption. + + @param in the array containing the data to be encrypted. + @param inOff offset into the in array the data starts at. + @param out the array the encrypted data will be copied into. + @param outOff the offset into the out array the output will start at. + @exception DataLengthException if there isn't enough data in in, or + space in out. + @exception InvalidOperationException if the cipher isn't initialised. + @return the number of bytes processed and produced. + + + Do the appropriate processing for CFB mode decryption. + + @param in the array containing the data to be decrypted. + @param inOff offset into the in array the data starts at. + @param out the array the encrypted data will be copied into. + @param outOff the offset into the out array the output will start at. + @exception DataLengthException if there isn't enough data in in, or + space in out. + @exception InvalidOperationException if the cipher isn't initialised. + @return the number of bytes processed and produced. + + + reset the chaining vector back to the IV and reset the underlying + cipher. + + + return the algorithm name and mode. + + @return the name of the underlying algorithm followed by "/CFB" + and the block size in bits. + + + A Cipher Text Stealing (CTS) mode cipher. CTS allows block ciphers to + be used to produce cipher text which is the same outLength as the plain text. + + + Create a buffered block cipher that uses Cipher Text Stealing + + @param cipher the underlying block cipher this buffering object wraps. + + + return the size of the output buffer required for an update of 'length' bytes. + + @param length the outLength of the input. + @return the space required to accommodate a call to update + with length bytes of input. + + + return the size of the output buffer required for an update plus a + doFinal with an input of length bytes. + + @param length the outLength of the input. + @return the space required to accommodate a call to update and doFinal + with length bytes of input. + + + process a single byte, producing an output block if necessary. + + @param in the input byte. + @param out the space for any output that might be produced. + @param outOff the offset from which the output will be copied. + @return the number of output bytes copied to out. + @exception DataLengthException if there isn't enough space in out. + @exception InvalidOperationException if the cipher isn't initialised. + + + process an array of bytes, producing output if necessary. + + @param in the input byte array. + @param inOff the offset at which the input data starts. + @param length the number of bytes to be copied out of the input array. + @param out the space for any output that might be produced. + @param outOff the offset from which the output will be copied. + @return the number of output bytes copied to out. + @exception DataLengthException if there isn't enough space in out. + @exception InvalidOperationException if the cipher isn't initialised. + + + Process the last block in the buffer. + + @param out the array the block currently being held is copied into. + @param outOff the offset at which the copying starts. + @return the number of output bytes copied to out. + @exception DataLengthException if there is insufficient space in out for + the output. + @exception InvalidOperationException if the underlying cipher is not + initialised. + @exception InvalidCipherTextException if cipher text decrypts wrongly (in + case the exception will never Get thrown). + + + A Two-Pass Authenticated-Encryption Scheme Optimized for Simplicity and + Efficiency - by M. Bellare, P. Rogaway, D. Wagner. + + http://www.cs.ucdavis.edu/~rogaway/papers/eax.pdf + + EAX is an AEAD scheme based on CTR and OMAC1/CMAC, that uses a single block + cipher to encrypt and authenticate data. It's on-line (the length of a + message isn't needed to begin processing it), has good performances, it's + simple and provably secure (provided the underlying block cipher is secure). + + Of course, this implementations is NOT thread-safe. + + + Constructor that accepts an instance of a block cipher engine. + + @param cipher the engine to use + + + + Implements the Galois/Counter mode (GCM) detailed in + NIST Special Publication 800-38D. + + + + + MAC sizes from 32 bits to 128 bits (must be a multiple of 8) are supported. The default is 128 bits. + Sizes less than 96 are not recommended, but are supported for specialized applications. + + + + implements the GOST 28147 OFB counter mode (GCTR). + + + Basic constructor. + + @param cipher the block cipher to be used as the basis of the + counter mode (must have a 64 bit block size). + + + return the underlying block cipher that we are wrapping. + + @return the underlying block cipher that we are wrapping. + + + Initialise the cipher and, possibly, the initialisation vector (IV). + If an IV isn't passed as part of the parameter, the IV will be all zeros. + An IV which is too short is handled in FIPS compliant fashion. + + @param encrypting if true the cipher is initialised for + encryption, if false for decryption. + @param parameters the key and other data required by the cipher. + @exception ArgumentException if the parameters argument is inappropriate. + + + return the block size we are operating at (in bytes). + + @return the block size we are operating at (in bytes). + + + Process one block of input from the array in and write it to + the out array. + + @param in the array containing the input data. + @param inOff offset into the in array the data starts at. + @param out the array the output data will be copied into. + @param outOff the offset into the out array the output will start at. + @exception DataLengthException if there isn't enough data in in, or + space in out. + @exception InvalidOperationException if the cipher isn't initialised. + @return the number of bytes processed and produced. + + + reset the feedback vector back to the IV and reset the underlying + cipher. + + + return the algorithm name and mode. + + @return the name of the underlying algorithm followed by "/GCTR" + and the block size in bits + + + An implementation of RFC 7253 on The OCB + Authenticated-Encryption Algorithm, licensed per: + +

    License for + Open-Source Software Implementations of OCB (Jan 9, 2013) - 'License 1'
    + Under this license, you are authorized to make, use, and distribute open-source software + implementations of OCB. This license terminates for you if you sue someone over their open-source + software implementation of OCB claiming that you have a patent covering their implementation. +

    + This is a non-binding summary of a legal document (the link above). The parameters of the license + are specified in the license document and that document is controlling.

    +
    + + implements a Output-FeedBack (OFB) mode on top of a simple cipher. + + + Basic constructor. + + @param cipher the block cipher to be used as the basis of the + feedback mode. + @param blockSize the block size in bits (note: a multiple of 8) + + + return the underlying block cipher that we are wrapping. + + @return the underlying block cipher that we are wrapping. + + + Initialise the cipher and, possibly, the initialisation vector (IV). + If an IV isn't passed as part of the parameter, the IV will be all zeros. + An IV which is too short is handled in FIPS compliant fashion. + + @param forEncryption if true the cipher is initialised for + encryption, if false for decryption. + @param param the key and other data required by the cipher. + @exception ArgumentException if the parameters argument is + inappropriate. + + + return the block size we are operating at (in bytes). + + @return the block size we are operating at (in bytes). + + + Process one block of input from the array in and write it to + the out array. + + @param in the array containing the input data. + @param inOff offset into the in array the data starts at. + @param out the array the output data will be copied into. + @param outOff the offset into the out array the output will start at. + @exception DataLengthException if there isn't enough data in in, or + space in out. + @exception InvalidOperationException if the cipher isn't initialised. + @return the number of bytes processed and produced. + + + reset the feedback vector back to the IV and reset the underlying + cipher. + + + return the algorithm name and mode. + + @return the name of the underlying algorithm followed by "/OFB" + and the block size in bits + + + * Implements OpenPGP's rather strange version of Cipher-FeedBack (CFB) mode + * on top of a simple cipher. This class assumes the IV has been prepended + * to the data stream already, and just accomodates the reset after + * (blockSize + 2) bytes have been read. + *

    + * For further info see RFC 2440. + *

    +
    + + Basic constructor. + + @param cipher the block cipher to be used as the basis of the + feedback mode. + + + return the underlying block cipher that we are wrapping. + + @return the underlying block cipher that we are wrapping. + + + return the block size we are operating at. + + @return the block size we are operating at (in bytes). + + + Process one block of input from the array in and write it to + the out array. + + @param in the array containing the input data. + @param inOff offset into the in array the data starts at. + @param out the array the output data will be copied into. + @param outOff the offset into the out array the output will start at. + @exception DataLengthException if there isn't enough data in in, or + space in out. + @exception InvalidOperationException if the cipher isn't initialised. + @return the number of bytes processed and produced. + + + reset the chaining vector back to the IV and reset the underlying + cipher. + + + Initialise the cipher and, possibly, the initialisation vector (IV). + If an IV isn't passed as part of the parameter, the IV will be all zeros. + An IV which is too short is handled in FIPS compliant fashion. + + @param forEncryption if true the cipher is initialised for + encryption, if false for decryption. + @param parameters the key and other data required by the cipher. + @exception ArgumentException if the parameters argument is + inappropriate. + + + Encrypt one byte of data according to CFB mode. + @param data the byte to encrypt + @param blockOff offset in the current block + @returns the encrypted byte + + + Do the appropriate processing for CFB IV mode encryption. + + @param in the array containing the data to be encrypted. + @param inOff offset into the in array the data starts at. + @param out the array the encrypted data will be copied into. + @param outOff the offset into the out array the output will start at. + @exception DataLengthException if there isn't enough data in in, or + space in out. + @exception InvalidOperationException if the cipher isn't initialised. + @return the number of bytes processed and produced. + + + Do the appropriate processing for CFB IV mode decryption. + + @param in the array containing the data to be decrypted. + @param inOff offset into the in array the data starts at. + @param out the array the encrypted data will be copied into. + @param outOff the offset into the out array the output will start at. + @exception DataLengthException if there isn't enough data in in, or + space in out. + @exception InvalidOperationException if the cipher isn't initialised. + @return the number of bytes processed and produced. + + + return the algorithm name and mode. + + @return the name of the underlying algorithm followed by "/PGPCFB" + and the block size in bits. + + + Implements the Segmented Integer Counter (SIC) mode on top of a simple + block cipher. + + + Basic constructor. + + @param c the block cipher to be used. + + + return the underlying block cipher that we are wrapping. + + @return the underlying block cipher that we are wrapping. + + + Return the digest algorithm using one of the standard JCA string + representations rather than the algorithm identifier (if possible). + + + + Calculator factory class for signature generation in ASN.1 based profiles that use an AlgorithmIdentifier to preserve + signature algorithm details. + + + + + Base constructor. + + The name of the signature algorithm to use. + The private key to be used in the signing operation. + + + + Constructor which also specifies a source of randomness to be used if one is required. + + The name of the signature algorithm to use. + The private key to be used in the signing operation. + The source of randomness to be used in signature calculation. + + + + Allows enumeration of the signature names supported by the verifier provider. + + + + + Verifier class for signature verification in ASN.1 based profiles that use an AlgorithmIdentifier to preserve + signature algorithm details. + + + + + Base constructor. + + The name of the signature algorithm to use. + The public key to be used in the verification operation. + + + + Provider class which supports dynamic creation of signature verifiers. + + + + + Base constructor - specify the public key to be used in verification. + + The public key to be used in creating verifiers provided by this object. + + + + Allows enumeration of the signature names supported by the verifier provider. + + + + Block cipher padders are expected to conform to this interface + + + Initialise the padder. + + @param param parameters, if any required. + + + add the pad bytes to the passed in block, returning the + number of bytes added. + + + return the number of pad bytes present in the block. + @exception InvalidCipherTextException if the padding is badly formed + or invalid. + + + Return the name of the algorithm the cipher implements. + + @return the name of the algorithm the cipher implements. + + + A padder that adds ISO10126-2 padding to a block. + + + Initialise the padder. + + @param random a SecureRandom if available. + + + add the pad bytes to the passed in block, returning the + number of bytes added. + + + return the number of pad bytes present in the block. + + + Return the name of the algorithm the cipher implements. + + @return the name of the algorithm the cipher implements. + + + A padder that adds the padding according to the scheme referenced in + ISO 7814-4 - scheme 2 from ISO 9797-1. The first byte is 0x80, rest is 0x00 + + + Initialise the padder. + + @param random - a SecureRandom if available. + + + add the pad bytes to the passed in block, returning the + number of bytes added. + + + return the number of pad bytes present in the block. + + + Return the name of the algorithm the padder implements. + + @return the name of the algorithm the padder implements. + + + A wrapper class that allows block ciphers to be used to process data in + a piecemeal fashion with padding. The PaddedBufferedBlockCipher + outputs a block only when the buffer is full and more data is being added, + or on a doFinal (unless the current block in the buffer is a pad block). + The default padding mechanism used is the one outlined in Pkcs5/Pkcs7. + + + Create a buffered block cipher with the desired padding. + + @param cipher the underlying block cipher this buffering object wraps. + @param padding the padding type. + + + Create a buffered block cipher Pkcs7 padding + + @param cipher the underlying block cipher this buffering object wraps. + + + initialise the cipher. + + @param forEncryption if true the cipher is initialised for + encryption, if false for decryption. + @param param the key and other data required by the cipher. + @exception ArgumentException if the parameters argument is + inappropriate. + + + return the minimum size of the output buffer required for an update + plus a doFinal with an input of len bytes. + + @param len the length of the input. + @return the space required to accommodate a call to update and doFinal + with len bytes of input. + + + return the size of the output buffer required for an update + an input of len bytes. + + @param len the length of the input. + @return the space required to accommodate a call to update + with len bytes of input. + + + process a single byte, producing an output block if necessary. + + @param in the input byte. + @param out the space for any output that might be produced. + @param outOff the offset from which the output will be copied. + @return the number of output bytes copied to out. + @exception DataLengthException if there isn't enough space in out. + @exception InvalidOperationException if the cipher isn't initialised. + + + process an array of bytes, producing output if necessary. + + @param in the input byte array. + @param inOff the offset at which the input data starts. + @param len the number of bytes to be copied out of the input array. + @param out the space for any output that might be produced. + @param outOff the offset from which the output will be copied. + @return the number of output bytes copied to out. + @exception DataLengthException if there isn't enough space in out. + @exception InvalidOperationException if the cipher isn't initialised. + + + Process the last block in the buffer. If the buffer is currently + full and padding needs to be added a call to doFinal will produce + 2 * GetBlockSize() bytes. + + @param out the array the block currently being held is copied into. + @param outOff the offset at which the copying starts. + @return the number of output bytes copied to out. + @exception DataLengthException if there is insufficient space in out for + the output or we are decrypting and the input is not block size aligned. + @exception InvalidOperationException if the underlying cipher is not + initialised. + @exception InvalidCipherTextException if padding is expected and not found. + + + A padder that adds Pkcs7/Pkcs5 padding to a block. + + + Initialise the padder. + + @param random - a SecureRandom if available. + + + add the pad bytes to the passed in block, returning the + number of bytes added. + + + return the number of pad bytes present in the block. + + + Return the name of the algorithm the cipher implements. + + @return the name of the algorithm the cipher implements. + + + A padder that adds Trailing-Bit-Compliment padding to a block. +

    + This padding pads the block out compliment of the last bit + of the plain text. +

    +
    +
    + + Initialise the padder. + - a SecureRandom if available. + + + + add the pad bytes to the passed in block, returning the + number of bytes added. +

    + Note: this assumes that the last block of plain text is always + passed to it inside in. i.e. if inOff is zero, indicating the + entire block is to be overwritten with padding the value of in + should be the same as the last block of plain text. +

    +
    +
    + + return the number of pad bytes present in the block. + + + Return the name of the algorithm the cipher implements. + the name of the algorithm the cipher implements. + + + + A padder that adds X9.23 padding to a block - if a SecureRandom is + passed in random padding is assumed, otherwise padding with zeros is used. + + + Initialise the padder. + + @param random a SecureRandom if one is available. + + + add the pad bytes to the passed in block, returning the + number of bytes added. + + + return the number of pad bytes present in the block. + + + Return the name of the algorithm the cipher implements. + + @return the name of the algorithm the cipher implements. + + + A padder that adds Null byte padding to a block. + + + Initialise the padder. + + + - a SecureRandom if available. + + + + add the pad bytes to the passed in block, returning the + number of bytes added. + + + + return the number of pad bytes present in the block. + + + Return the name of the algorithm the cipher implements. + + + the name of the algorithm the cipher implements. + + + + Base constructor. + + @param key key to be used by underlying cipher + @param macSize macSize in bits + @param nonce nonce to be used + + + Base constructor. + + @param key key to be used by underlying cipher + @param macSize macSize in bits + @param nonce nonce to be used + @param associatedText associated text, if any + + + Base constructor. + + @param key key to be used by underlying cipher + @param macSize macSize in bits + @param nonce nonce to be used + @param associatedText associated text, if any + + + DES has 16 weak keys. This method will check + if the given DES key material is weak or semi-weak. + Key material that is too short is regarded as weak. +

    + See "Applied + Cryptography" by Bruce Schneier for more information. +

    + @return true if the given DES key material is weak or semi-weak, + false otherwise. +
    + + DES Keys use the LSB as the odd parity bit. This can + be used to check for corrupt keys. + + @param bytes the byte array to set the parity on. + + + return true if the passed in key is a DES-EDE weak key. + + @param key bytes making up the key + @param offset offset into the byte array the key starts at + @param length number of bytes making up the key + + + return true if the passed in key is a DES-EDE weak key. + + @param key bytes making up the key + @param offset offset into the byte array the key starts at + + + return true if the passed in key is a real 2/3 part DES-EDE key. + + @param key bytes making up the key + @param offset offset into the byte array the key starts at + + + return true if the passed in key is a real 2 part DES-EDE key. + + @param key bytes making up the key + @param offset offset into the byte array the key starts at + + + return true if the passed in key is a real 3 part DES-EDE key. + + @param key bytes making up the key + @param offset offset into the byte array the key starts at + + + The minimum bitlength of the private value. + + + The bitlength of the private value. + + + Construct without a usage index, this will do a random construction of G. + + @param L desired length of prime P in bits (the effective key size). + @param N desired length of prime Q in bits. + @param certainty certainty level for prime number generation. + @param random the source of randomness to use. + + + Construct for a specific usage index - this has the effect of using verifiable canonical generation of G. + + @param L desired length of prime P in bits (the effective key size). + @param N desired length of prime Q in bits. + @param certainty certainty level for prime number generation. + @param random the source of randomness to use. + @param usageIndex a valid usage index. + + + return the generator - g + + + return private value limit - l + + + Parameter class for the HkdfBytesGenerator class. + + + Generates parameters for HKDF, specifying both the optional salt and + optional info. Step 1: Extract won't be skipped. + + @param ikm the input keying material or seed + @param salt the salt to use, may be null for a salt for hashLen zeros + @param info the info to use, may be null for an info field of zero bytes + + + Factory method that makes the HKDF skip the extract part of the key + derivation function. + + @param ikm the input keying material or seed, directly used for step 2: + Expand + @param info the info to use, may be null for an info field of zero bytes + @return HKDFParameters that makes the implementation skip step 1 + + + Returns the input keying material or seed. + + @return the keying material + + + Returns the salt, or null if the salt should be generated as a byte array + of HashLen zeros. + + @return the salt, or null + + + Returns the info field, which may be empty (null is converted to empty). + + @return the info field, never null + + + Returns if step 1: extract has to be skipped or not + + @return true for skipping, false for no skipping of step 1 + + + parameters for using an integrated cipher in stream mode. + + + @param derivation the derivation parameter for the KDF function. + @param encoding the encoding parameter for the KDF function. + @param macKeySize the size of the MAC key (in bits). + + + @param derivation the derivation parameter for the KDF function. + @param encoding the encoding parameter for the KDF function. + @param macKeySize the size of the MAC key (in bits). + @param cipherKeySize the size of the associated Cipher key (in bits). + + + parameters for Key derivation functions for ISO-18033 + + + parameters for Key derivation functions for IEEE P1363a + + + Parameters for mask derivation functions. + + + Parameters for NaccacheStern public private key generation. For details on + this cipher, please see + + http://www.gemplus.com/smart/rd/publications/pdf/NS98pkcs.pdf + + + Parameters for generating a NaccacheStern KeyPair. + + @param random + The source of randomness + @param strength + The desired strength of the Key in Bits + @param certainty + the probability that the generated primes are not really prime + as integer: 2^(-certainty) is then the probability + @param countSmallPrimes + How many small key factors are desired + + + * Parameters for a NaccacheStern KeyPair. + * + * @param random + * The source of randomness + * @param strength + * The desired strength of the Key in Bits + * @param certainty + * the probability that the generated primes are not really prime + * as integer: 2^(-certainty) is then the probability + * @param cntSmallPrimes + * How many small key factors are desired + * @param debug + * Ignored + + + @return Returns the certainty. + + + @return Returns the countSmallPrimes. + + + Public key parameters for NaccacheStern cipher. For details on this cipher, + please see + + http://www.gemplus.com/smart/rd/publications/pdf/NS98pkcs.pdf + + + @param privateKey + + + @return Returns the g. + + + @return Returns the lowerSigmaBound. + + + @return Returns the n. + + + Private key parameters for NaccacheStern cipher. For details on this cipher, + please see + + http://www.gemplus.com/smart/rd/publications/pdf/NS98pkcs.pdf + + + Constructs a NaccacheSternPrivateKey + + @param g + the public enryption parameter g + @param n + the public modulus n = p*q + @param lowerSigmaBound + the public lower sigma bound up to which data can be encrypted + @param smallPrimes + the small primes, of which sigma is constructed in the right + order + @param phi_n + the private modulus phi(n) = (p-1)(q-1) + + + Cipher parameters with a fixed salt value associated with them. + + + + Parameters for the Skein hash function - a series of byte[] strings identified by integer tags. + + + Parameterised Skein can be used for: +
      +
    • MAC generation, by providing a key.
    • +
    • Randomised hashing, by providing a nonce.
    • +
    • A hash function for digital signatures, associating a + public key with the message digest.
    • +
    • A key derivation function, by providing a + key identifier.
    • +
    • Personalised hashing, by providing a + recommended format or + arbitrary personalisation string.
    • +
    +
    + + + +
    + + + The parameter type for a secret key, supporting MAC or KDF functions: 0 + + + + + The parameter type for the Skein configuration block: 4 + + + + + The parameter type for a personalisation string: 8 + + + + + The parameter type for a public key: 12 + + + + + The parameter type for a key identifier string: 16 + + + + + The parameter type for a nonce: 20 + + + + + The parameter type for the message: 48 + + + + + The parameter type for the output transformation: 63 + + + + + Obtains a map of type (int) to value (byte[]) for the parameters tracked in this object. + + + + + Obtains the value of the key parameter, or null if not + set. + + The key. + + + + Obtains the value of the personalisation parameter, or + null if not set. + + + + + Obtains the value of the public key parameter, or + null if not set. + + + + + Obtains the value of the key identifier parameter, or + null if not set. + + + + + Obtains the value of the nonce parameter, or null if + not set. + + + + + A builder for . + + + + + Sets a parameters to apply to the Skein hash function. + + + Parameter types must be in the range 0,5..62, and cannot use the value 48 + (reserved for message body). +

    + Parameters with type < 48 are processed before + the message content, parameters with type > 48 + are processed after the message and prior to output. + + the type of the parameter, in the range 5..62. + the byte sequence of the parameter. + + +

    + Sets the parameter. + +
    + + + Sets the parameter. + + + + + Implements the recommended personalisation format for Skein defined in Section 4.11 of + the Skein 1.3 specification. + + + The format is YYYYMMDD email@address distinguisher, encoded to a byte + sequence using UTF-8 encoding. + + the date the personalised application of the Skein was defined. + the email address of the creation of the personalised application. + an arbitrary personalisation string distinguishing the application. + + + + Sets the parameter. + + + + + Sets the parameter. + + + + + Sets the parameter. + + + + + Constructs a new instance with the parameters provided to this + builder. + + + + + Parameters for tweakable block ciphers. + + + + + Gets the key. + + the key to use, or null to use the current key. + + + + Gets the tweak value. + + The tweak to use, or null to use the current tweak. + + + An EntropySourceProvider where entropy generation is based on a SecureRandom output using SecureRandom.generateSeed(). + + + Create a entropy source provider based on the passed in SecureRandom. + + @param secureRandom the SecureRandom to base EntropySource construction on. + @param isPredictionResistant boolean indicating if the SecureRandom is based on prediction resistant entropy or not (true if it is). + + + Return an entropy source that will create bitsRequired bits of entropy on + each invocation of getEntropy(). + + @param bitsRequired size (in bits) of entropy to be created by the provided source. + @return an EntropySource that generates bitsRequired bits of entropy on each call to its getEntropy() method. + + + + Uses Microsoft's RNGCryptoServiceProvider + + + + Generic interface for objects generating random bytes. + + + Add more seed material to the generator. + A byte array to be mixed into the generator's state. + + + Add more seed material to the generator. + A long value to be mixed into the generator's state. + + + Fill byte array with random values. + Array to be filled. + + + Fill byte array with random values. + Array to receive bytes. + Index to start filling at. + Length of segment to fill. + + + Random generation based on the digest with counter. Calling AddSeedMaterial will + always increase the entropy of the hash. +

    + Internal access to the digest is synchronized so a single one of these can be shared. +

    +
    + + A SP800-90A CTR DRBG. + + + Interface to SP800-90A deterministic random bit generators. + + + Populate a passed in array with random data. + + @param output output array for generated bits. + @param additionalInput additional input to be added to the DRBG in this step. + @param predictionResistant true if a reseed should be forced, false otherwise. + + @return number of bits generated, -1 if a reseed required. + + + Reseed the DRBG. + + @param additionalInput additional input to be added to the DRBG in this step. + + + Return the block size of the DRBG. + + @return the block size (in bits) produced by each round of the DRBG. + + + Construct a SP800-90A CTR DRBG. +

    + Minimum entropy requirement is the security strength requested. +

    + @param engine underlying block cipher to use to support DRBG + @param keySizeInBits size of the key to use with the block cipher. + @param securityStrength security strength required (in bits) + @param entropySource source of entropy to use for seeding/reseeding. + @param personalizationString personalization string to distinguish this DRBG (may be null). + @param nonce nonce to further distinguish this DRBG (may be null). +
    + + Populate a passed in array with random data. + + @param output output array for generated bits. + @param additionalInput additional input to be added to the DRBG in this step. + @param predictionResistant true if a reseed should be forced, false otherwise. + + @return number of bits generated, -1 if a reseed required. + + + Reseed the DRBG. + + @param additionalInput additional input to be added to the DRBG in this step. + + + Pad out a key for TDEA, setting odd parity for each byte. + + @param keyMaster + @param keyOff + @param tmp + @param tmpOff + + + Return the block size (in bits) of the DRBG. + + @return the number of bits produced on each internal round of the DRBG. + + + Used by both Dual EC and Hash. + + + A SP800-90A Hash DRBG. + + + Construct a SP800-90A Hash DRBG. +

    + Minimum entropy requirement is the security strength requested. +

    + @param digest source digest to use for DRB stream. + @param securityStrength security strength required (in bits) + @param entropySource source of entropy to use for seeding/reseeding. + @param personalizationString personalization string to distinguish this DRBG (may be null). + @param nonce nonce to further distinguish this DRBG (may be null). +
    + + Populate a passed in array with random data. + + @param output output array for generated bits. + @param additionalInput additional input to be added to the DRBG in this step. + @param predictionResistant true if a reseed should be forced, false otherwise. + + @return number of bits generated, -1 if a reseed required. + + + Reseed the DRBG. + + @param additionalInput additional input to be added to the DRBG in this step. + + + Return the block size (in bits) of the DRBG. + + @return the number of bits produced on each internal round of the DRBG. + + + A SP800-90A HMAC DRBG. + + + Construct a SP800-90A Hash DRBG. +

    + Minimum entropy requirement is the security strength requested. +

    + @param hMac Hash MAC to base the DRBG on. + @param securityStrength security strength required (in bits) + @param entropySource source of entropy to use for seeding/reseeding. + @param personalizationString personalization string to distinguish this DRBG (may be null). + @param nonce nonce to further distinguish this DRBG (may be null). +
    + + Populate a passed in array with random data. + + @param output output array for generated bits. + @param additionalInput additional input to be added to the DRBG in this step. + @param predictionResistant true if a reseed should be forced, false otherwise. + + @return number of bits generated, -1 if a reseed required. + + + Reseed the DRBG. + + @param additionalInput additional input to be added to the DRBG in this step. + + + Return the block size (in bits) of the DRBG. + + @return the number of bits produced on each round of the DRBG. + + + Generate numBytes worth of entropy from the passed in entropy source. + + @param entropySource the entropy source to request the data from. + @param numBytes the number of bytes of entropy requested. + @return a byte array populated with the random data. + + + + Takes bytes generated by an underling RandomGenerator and reverses the order in + each small window (of configurable size). +

    + Access to internals is synchronized so a single one of these can be shared. +

    +
    +
    + + Add more seed material to the generator. + A byte array to be mixed into the generator's state. + + + Add more seed material to the generator. + A long value to be mixed into the generator's state. + + + Fill byte array with random values. + Array to be filled. + + + Fill byte array with random values. + Array to receive bytes. + Index to start filling at. + Length of segment to fill. + + + + Create and auto-seed an instance based on the given algorithm. + + Equivalent to GetInstance(algorithm, true) + e.g. "SHA256PRNG" + + + + Create an instance based on the given algorithm, with optional auto-seeding + + e.g. "SHA256PRNG" + If true, the instance will be auto-seeded. + + + + To replicate existing predictable output, replace with GetInstance("SHA1PRNG", false), followed by SetSeed(seed) + + + + Use the specified instance of IRandomGenerator as random source. + + This constructor performs no seeding of either the IRandomGenerator or the + constructed SecureRandom. It is the responsibility of the client to provide + proper seed material as necessary/appropriate for the given IRandomGenerator + implementation. + + The source to generate all random bytes from. + + + Builder class for making SecureRandom objects based on SP 800-90A Deterministic Random Bit Generators (DRBG). + + + Basic constructor, creates a builder using an EntropySourceProvider based on the default SecureRandom with + predictionResistant set to false. +

    + Any SecureRandom created from a builder constructed like this will make use of input passed to SecureRandom.setSeed() if + the default SecureRandom does for its generateSeed() call. +

    +
    + + Construct a builder with an EntropySourceProvider based on the passed in SecureRandom and the passed in value + for prediction resistance. +

    + Any SecureRandom created from a builder constructed like this will make use of input passed to SecureRandom.setSeed() if + the passed in SecureRandom does for its generateSeed() call. +

    + @param entropySource + @param predictionResistant +
    + + Create a builder which makes creates the SecureRandom objects from a specified entropy source provider. +

    + Note: If this constructor is used any calls to setSeed() in the resulting SecureRandom will be ignored. +

    + @param entropySourceProvider a provider of EntropySource objects. +
    + + Set the personalization string for DRBG SecureRandoms created by this builder + @param personalizationString the personalisation string for the underlying DRBG. + @return the current builder. + + + Set the security strength required for DRBGs used in building SecureRandom objects. + + @param securityStrength the security strength (in bits) + @return the current builder. + + + Set the amount of entropy bits required for seeding and reseeding DRBGs used in building SecureRandom objects. + + @param entropyBitsRequired the number of bits of entropy to be requested from the entropy source on each seed/reseed. + @return the current builder. + + + Build a SecureRandom based on a SP 800-90A Hash DRBG. + + @param digest digest algorithm to use in the DRBG underneath the SecureRandom. + @param nonce nonce value to use in DRBG construction. + @param predictionResistant specify whether the underlying DRBG in the resulting SecureRandom should reseed on each request for bytes. + @return a SecureRandom supported by a Hash DRBG. + + + Build a SecureRandom based on a SP 800-90A CTR DRBG. + + @param cipher the block cipher to base the DRBG on. + @param keySizeInBits key size in bits to be used with the block cipher. + @param nonce nonce value to use in DRBG construction. + @param predictionResistant specify whether the underlying DRBG in the resulting SecureRandom should reseed on each request for bytes. + @return a SecureRandom supported by a CTR DRBG. + + + Build a SecureRandom based on a SP 800-90A HMAC DRBG. + + @param hMac HMAC algorithm to use in the DRBG underneath the SecureRandom. + @param nonce nonce value to use in DRBG construction. + @param predictionResistant specify whether the underlying DRBG in the resulting SecureRandom should reseed on each request for bytes. + @return a SecureRandom supported by a HMAC DRBG. + + + A thread based seed generator - one source of randomness. +

    + Based on an idea from Marcus Lippert. +

    +
    + + Generate seed bytes. Set fast to false for best quality. +

    + If fast is set to true, the code should be round about 8 times faster when + generating a long sequence of random bytes. 20 bytes of random values using + the fast mode take less than half a second on a Nokia e70. If fast is set to false, + it takes round about 2500 ms. +

    + @param numBytes the number of bytes to generate + @param fast true if fast mode should be used +
    + + + Permutation generated by code: + + // First 1850 fractional digit of Pi number. + byte[] key = new BigInteger("14159265358979323846...5068006422512520511").ToByteArray(); + s = 0; + P = new byte[256]; + for (int i = 0; i < 256; i++) + { + P[i] = (byte) i; + } + for (int m = 0; m < 768; m++) + { + s = P[(s + P[m & 0xff] + key[m % key.length]) & 0xff]; + byte temp = P[m & 0xff]; + P[m & 0xff] = P[s & 0xff]; + P[s & 0xff] = temp; + } + + + + Value generated in the same way as P. + + + + @param engine + @param entropySource + + + Populate a passed in array with random data. + + @param output output array for generated bits. + @param predictionResistant true if a reseed should be forced, false otherwise. + + @return number of bits generated, -1 if a reseed required. + + + Reseed the RNG. + + + Basic constructor, creates a builder using an EntropySourceProvider based on the default SecureRandom with + predictionResistant set to false. +

    + Any SecureRandom created from a builder constructed like this will make use of input passed to SecureRandom.setSeed() if + the default SecureRandom does for its generateSeed() call. +

    +
    + + Construct a builder with an EntropySourceProvider based on the passed in SecureRandom and the passed in value + for prediction resistance. +

    + Any SecureRandom created from a builder constructed like this will make use of input passed to SecureRandom.setSeed() if + the passed in SecureRandom does for its generateSeed() call. +

    + @param entropySource + @param predictionResistant +
    + + Create a builder which makes creates the SecureRandom objects from a specified entropy source provider. +

    + Note: If this constructor is used any calls to setSeed() in the resulting SecureRandom will be ignored. +

    + @param entropySourceProvider a provider of EntropySource objects. +
    + + Construct a X9.31 secure random generator using the passed in engine and key. If predictionResistant is true the + generator will be reseeded on each request. + + @param engine a block cipher to use as the operator. + @param key the block cipher key to initialise engine with. + @param predictionResistant true if engine to be reseeded on each use, false otherwise. + @return a SecureRandom. + + + update the internal digest with the byte b + + + update the internal digest with the byte array in + + + Generate a signature for the message we've been loaded with using + the key we were initialised with. + + + true if the internal state represents the signature described in the passed in array. + + + Reset the internal state + + + The Digital Signature Algorithm - as described in "Handbook of Applied + Cryptography", pages 452 - 453. + + + Default configuration, random K values. + + + Configuration with an alternate, possibly deterministic calculator of K. + + @param kCalculator a K value calculator. + + + Generate a signature for the given message using the key we were + initialised with. For conventional DSA the message should be a SHA-1 + hash of the message of interest. + + @param message the message that will be verified later. + + + return true if the value r and s represent a DSA signature for + the passed in message for standard DSA the message should be a + SHA-1 hash of the real message to be verified. + + + EC-DSA as described in X9.62 + + + Default configuration, random K values. + + + Configuration with an alternate, possibly deterministic calculator of K. + + @param kCalculator a K value calculator. + + + Generate a signature for the given message using the key we were + initialised with. For conventional DSA the message should be a SHA-1 + hash of the message of interest. + + @param message the message that will be verified later. + + + return true if the value r and s represent a DSA signature for + the passed in message (for standard DSA the message should be + a SHA-1 hash of the real message to be verified). + + + GOST R 34.10-2001 Signature Algorithm + + + generate a signature for the given message using the key we were + initialised with. For conventional GOST3410 the message should be a GOST3411 + hash of the message of interest. + + @param message the message that will be verified later. + + + return true if the value r and s represent a GOST3410 signature for + the passed in message (for standard GOST3410 the message should be + a GOST3411 hash of the real message to be verified). + + + EC-NR as described in IEEE 1363-2000 + + + generate a signature for the given message using the key we were + initialised with. Generally, the order of the curve should be at + least as long as the hash of the message of interest, and with + ECNR it *must* be at least as long. + + @param digest the digest to be signed. + @exception DataLengthException if the digest is longer than the key allows + + + return true if the value r and s represent a signature for the + message passed in. Generally, the order of the curve should be at + least as long as the hash of the message of interest, and with + ECNR, it *must* be at least as long. But just in case the signer + applied mod(n) to the longer digest, this implementation will + apply mod(n) during verification. + + @param digest the digest to be verified. + @param r the r value of the signature. + @param s the s value of the signature. + @exception DataLengthException if the digest is longer than the key allows + + + initialise the signer for signing or verification. + + @param forSigning + true if for signing, false otherwise + @param parameters + necessary parameters. + + + update the internal digest with the byte b + + + update the internal digest with the byte array in + + + Generate a signature for the message we've been loaded with using the key + we were initialised with. + + + return true if the internal state represents the signature described in + the passed in array. + + + update the internal digest with the byte b + + + update the internal digest with the byte array in + + + Generate a signature for the message we've been loaded with using + the key we were initialised with. + + + true if the internal state represents the signature described in the passed in array. + + + Reset the internal state + + + Gost R 34.10-94 Signature Algorithm + + + generate a signature for the given message using the key we were + initialised with. For conventional Gost3410 the message should be a Gost3411 + hash of the message of interest. + + @param message the message that will be verified later. + + + return true if the value r and s represent a Gost3410 signature for + the passed in message for standard Gost3410 the message should be a + Gost3411 hash of the real message to be verified. + + + A deterministic K calculator based on the algorithm in section 3.2 of RFC 6979. + + + Interface define calculators of K values for DSA/ECDSA. + + + Non-deterministic initialiser. + + @param n the order of the DSA group. + @param random a source of randomness. + + + Deterministic initialiser. + + @param n the order of the DSA group. + @param d the DSA private value. + @param message the message being signed. + + + Return the next valid value of K. + + @return a K value. + + + Return true if this calculator is deterministic, false otherwise. + + @return true if deterministic, otherwise false. + + + Base constructor. + + @param digest digest to build the HMAC on. + + + ISO9796-2 - mechanism using a hash function with recovery (scheme 2 and 3). +

    + Note: the usual length for the salt is the length of the hash + function used in bytes.

    +
    +
    + + + Return a reference to the recoveredMessage message. + + The full/partial recoveredMessage message. + + + + + Generate a signer with either implicit or explicit trailers for ISO9796-2, scheme 2 or 3. + + base cipher to use for signature creation/verification + digest to use. + length of salt in bytes. + whether or not the trailer is implicit or gives the hash. + + + Constructor for a signer with an explicit digest trailer. + + + cipher to use. + + digest to sign with. + + length of salt in bytes. + + + + Initialise the signer. + true if for signing, false if for verification. + parameters for signature generation/verification. If the + parameters are for generation they should be a ParametersWithRandom, + a ParametersWithSalt, or just an RsaKeyParameters object. If RsaKeyParameters + are passed in a SecureRandom will be created. + + if wrong parameter type or a fixed + salt is passed in which is the wrong length. + + + + compare two byte arrays - constant time. + + + clear possible sensitive data + + + update the internal digest with the byte b + + + update the internal digest with the byte array in + + + reset the internal state + + + Generate a signature for the loaded message using the key we were + initialised with. + + + + return true if the signature represents a ISO9796-2 signature + for the passed in message. + + + + + Return true if the full message was recoveredMessage. + + true on full message recovery, false otherwise, or if not sure. + + + + int to octet string. + int to octet string. + + + long to octet string. + + + mask generator function, as described in Pkcs1v2. + + + ISO9796-2 - mechanism using a hash function with recovery (scheme 1) + + + + Return a reference to the recoveredMessage message. + + The full/partial recoveredMessage message. + + + + + Generate a signer with either implicit or explicit trailers for ISO9796-2. + + base cipher to use for signature creation/verification + digest to use. + whether or not the trailer is implicit or gives the hash. + + + Constructor for a signer with an explicit digest trailer. + + + cipher to use. + + digest to sign with. + + + + compare two byte arrays - constant time. + + + clear possible sensitive data + + + update the internal digest with the byte b + + + update the internal digest with the byte array in + + + reset the internal state + + + Generate a signature for the loaded message using the key we were + initialised with. + + + + return true if the signature represents a ISO9796-2 signature + for the passed in message. + + + + + Return true if the full message was recoveredMessage. + + true on full message recovery, false otherwise. + + + + RSA-PSS as described in Pkcs# 1 v 2.1. +

    + Note: the usual value for the salt length is the number of + bytes in the hash function.

    +
    +
    + + Basic constructor + the asymmetric cipher to use. + the digest to use. + the length of the salt to use (in bytes). + + + Basic constructor + the asymmetric cipher to use. + the digest to use. + the fixed salt to be used. + + + clear possible sensitive data + + + update the internal digest with the byte b + + + update the internal digest with the byte array in + + + reset the internal state + + + Generate a signature for the message we've been loaded with using + the key we were initialised with. + + + + return true if the internal state represents the signature described + in the passed in array. + + + + int to octet string. + + + mask generator function, as described in Pkcs1v2. + + + + Load oid table. + + + + Initialise the signer for signing or verification. + + @param forSigning true if for signing, false otherwise + @param param necessary parameters. + + + update the internal digest with the byte b + + + update the internal digest with the byte array in + + + Generate a signature for the message we've been loaded with using + the key we were initialised with. + + + return true if the internal state represents the signature described + in the passed in array. + + + X9.31-1998 - signing using a hash. +

    + The message digest hash, H, is encapsulated to form a byte string as follows +

    +
    +            EB = 06 || PS || 0xBA || H || TRAILER
    +            
    + where PS is a string of bytes all of value 0xBB of length such that |EB|=|n|, and TRAILER is the ISO/IEC 10118 part number† for the digest. The byte string, EB, is converted to an integer value, the message representative, f. +
    + + Generate a signer with either implicit or explicit trailers for X9.31. + + @param cipher base cipher to use for signature creation/verification + @param digest digest to use. + @param implicit whether or not the trailer is implicit or gives the hash. + + + Constructor for a signer with an explicit digest trailer. + + @param cipher cipher to use. + @param digest digest to sign with. + + + clear possible sensitive data + + + update the internal digest with the byte b + + + update the internal digest with the byte array in + + + reset the internal state + + + generate a signature for the loaded message using the key we were + initialised with. + + + return true if the signature represents a ISO9796-2 signature + for the passed in message. + + + a wrapper for block ciphers with a single byte block size, so that they + can be treated like stream ciphers. + + + basic constructor. + + @param cipher the block cipher to be wrapped. + @exception ArgumentException if the cipher has a block size other than + one. + + + initialise the underlying cipher. + + @param forEncryption true if we are setting up for encryption, false otherwise. + @param param the necessary parameters for the underlying cipher to be initialised. + + + encrypt/decrypt a single byte returning the result. + + @param in the byte to be processed. + @return the result of processing the input byte. + + + process a block of bytes from in putting the result into out. + + @param in the input byte array. + @param inOff the offset into the in array where the data to be processed starts. + @param len the number of bytes to be processed. + @param out the output buffer the processed bytes go into. + @param outOff the offset into the output byte array the processed data stars at. + @exception DataLengthException if the output buffer is too small. + + + reset the underlying cipher. This leaves it in the same state + it was at after the last init (if there was one). + + + return the name of the algorithm we are wrapping. + + @return the name of the algorithm we are wrapping. + + + + + + + + + + + + + + + + draft-mathewson-no-gmtunixtime-00 2. "If existing users of a TLS implementation may rely on + gmt_unix_time containing the current time, we recommend that implementors MAY provide the + ability to set gmt_unix_time as an option only, off by default." + + + true if the current time should be used in the gmt_unix_time field of + Random, or false if gmt_unix_time should contain a cryptographically + random value. + + + + + Report whether the server supports secure renegotiation + + + The protocol handler automatically processes the relevant extensions + + + A , true if the server supports secure renegotiation + + + + + + Return an implementation of to handle record compression. + + A + + + + + Return an implementation of to use for encryption/decryption. + + A + + + + This method will be called when an alert is raised by the protocol. + + + A human-readable message explaining what caused this alert. May be null. + The Exception that caused this alert to be raised. May be null. + + + This method will be called when an alert is received from the remote peer. + + + + + Notifies the peer that the handshake has been successfully completed. + + + + + Called at the start of a new TLS session, before any other methods. + + + A + + + + Return the session this client wants to resume, if any. + Note that the peer's certificate chain for the session (if any) may need to be periodically revalidated. + + A representing the resumable session to be used for this connection, + or null to use a new session. + + + + + Get the list of cipher suites that this client supports. + + + An array of values, each specifying a supported cipher suite. + + + + + Get the list of compression methods that this client supports. + + + An array of values, each specifying a supported compression method. + + + + + Get the (optional) table of client extensions to be included in (extended) client hello. + + + A (Int32 -> byte[]). May be null. + + + + + + + + + Notifies the client of the session_id sent in the ServerHello. + + An array of + + + + Report the cipher suite that was selected by the server. + + + The protocol handler validates this value against the offered cipher suites + + + + A + + + + + Report the compression method that was selected by the server. + + + The protocol handler validates this value against the offered compression methods + + + + A + + + + + Report the extensions from an extended server hello. + + + Will only be called if we returned a non-null result from . + + + A (Int32 -> byte[]) + + + + A list of + + + + + Return an implementation of to negotiate the key exchange + part of the protocol. + + + A + + + + + + Return an implementation of to handle authentication + part of the protocol. + + + + + A list of + + + + RFC 5077 3.3. NewSessionTicket Handshake Message + + This method will be called (only) when a NewSessionTicket handshake message is received. The + ticket is opaque to the client and clients MUST NOT examine the ticket under the assumption + that it complies with e.g. RFC 5077 4. Recommended Ticket Construction. + + The ticket + + + + + Return the to use for the TLSPlaintext.version field prior to + receiving the server version. NOTE: This method is not called for DTLS. + + + See RFC 5246 E.1.: "TLS clients that wish to negotiate with older servers MAY send any value + {03,XX} as the record layer version number. Typical values would be {03,00}, the lowest + version number supported by the client, and the value of ClientHello.client_version. No + single value will guarantee interoperability with all old servers, but this is a complex + topic beyond the scope of this document." + + The to use. + + + Export keying material according to RFC 5705: "Keying Material Exporters for TLS". + + @param asciiLabel indicates which application will use the exported keys. + @param context_value allows the application using the exporter to mix its own data with the TLS PRF for + the exporter output. + @param length the number of bytes to generate + @return a pseudorandom bit string of 'length' bytes generated from the master_secret. + + + Used to get the resumable session, if any, used by this connection. Only available after the + handshake has successfully completed. + + @return A {@link TlsSession} representing the resumable session used by this connection, or + null if no resumable session available. + @see TlsPeer#NotifyHandshakeComplete() + + + + + + + + + + A generic interface for key exchange implementations in (D)TLS. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A (Int32 -> byte[]). Will never be null. + + + + + + + + + + + + + + Get the (optional) table of server extensions to be included in (extended) server hello. + + + A (Int32 -> byte[]). May be null. + + + + + + A (). May be null. + + + + + + + + + This method will be called (only) if the server included an extension of type + "status_request" with empty "extension_data" in the extended server hello. See RFC 3546 + 3.6. Certificate Status Request. If a non-null is returned, it + is sent to the client as a handshake message of type "certificate_status". + + A to be sent to the client (or null for none). + + + + + + + + + + () + + + + + Called by the protocol handler to report the client certificate, only if GetCertificateRequest + returned non-null. + + Note: this method is responsible for certificate verification and validation. + the effective client certificate (may be an empty chain). + + + + RFC 5077 3.3. NewSessionTicket Handshake Message. + + This method will be called (only) if a NewSessionTicket extension was sent by the server. See + RFC 5077 4. Recommended Ticket Construction for recommended format and protection. + + The ticket) + + + + + + + + + + + RFC 5246 7.2 + + + + This message notifies the recipient that the sender will not send any more messages on this + connection. Note that as of TLS 1.1, failure to properly close a connection no longer + requires that a session not be resumed. This is a change from TLS 1.0 ("The session becomes + unresumable if any connection is terminated without proper close_notify messages with level + equal to warning.") to conform with widespread implementation practice. + + + An inappropriate message was received. This alert is always fatal and should never be + observed in communication between proper implementations. + + + This alert is returned if a record is received with an incorrect MAC. This alert also MUST be + returned if an alert is sent because a TLSCiphertext decrypted in an invalid way: either it + wasn't an even multiple of the block length, or its padding values, when checked, weren't + correct. This message is always fatal and should never be observed in communication between + proper implementations (except when messages were corrupted in the network). + + + This alert was used in some earlier versions of TLS, and may have permitted certain attacks + against the CBC mode [CBCATT]. It MUST NOT be sent by compliant implementations. + + + A TLSCiphertext record was received that had a length more than 2^14+2048 bytes, or a record + decrypted to a TLSCompressed record with more than 2^14+1024 bytes. This message is always + fatal and should never be observed in communication between proper implementations (except + when messages were corrupted in the network). + + + The decompression function received improper input (e.g., data that would expand to excessive + length). This message is always fatal and should never be observed in communication between + proper implementations. + + + Reception of a handshake_failure alert message indicates that the sender was unable to + negotiate an acceptable set of security parameters given the options available. This is a + fatal error. + + + This alert was used in SSLv3 but not any version of TLS. It MUST NOT be sent by compliant + implementations. + + + A certificate was corrupt, contained signatures that did not verify correctly, etc. + + + A certificate was of an unsupported type. + + + A certificate was revoked by its signer. + + + A certificate has expired or is not currently valid. + + + Some other (unspecified) issue arose in processing the certificate, rendering it + unacceptable. + + + A field in the handshake was out of range or inconsistent with other fields. This message is + always fatal. + + + A valid certificate chain or partial chain was received, but the certificate was not accepted + because the CA certificate could not be located or couldn't be matched with a known, trusted + CA. This message is always fatal. + + + A valid certificate was received, but when access control was applied, the sender decided not + to proceed with negotiation. This message is always fatal. + + + A message could not be decoded because some field was out of the specified range or the + length of the message was incorrect. This message is always fatal and should never be + observed in communication between proper implementations (except when messages were corrupted + in the network). + + + A handshake cryptographic operation failed, including being unable to correctly verify a + signature or validate a Finished message. This message is always fatal. + + + This alert was used in some earlier versions of TLS. It MUST NOT be sent by compliant + implementations. + + + The protocol version the client has attempted to negotiate is recognized but not supported. + (For example, old protocol versions might be avoided for security reasons.) This message is + always fatal. + + + Returned instead of handshake_failure when a negotiation has failed specifically because the + server requires ciphers more secure than those supported by the client. This message is + always fatal. + + + An internal error unrelated to the peer or the correctness of the protocol (such as a memory + allocation failure) makes it impossible to continue. This message is always fatal. + + + This handshake is being canceled for some reason unrelated to a protocol failure. If the user + cancels an operation after the handshake is complete, just closing the connection by sending + a close_notify is more appropriate. This alert should be followed by a close_notify. This + message is generally a warning. + + + Sent by the client in response to a hello request or by the server in response to a client + hello after initial handshaking. Either of these would normally lead to renegotiation; when + that is not appropriate, the recipient should respond with this alert. At that point, the + original requester can decide whether to proceed with the connection. One case where this + would be appropriate is where a server has spawned a process to satisfy a request; the + process might receive security parameters (key length, authentication, etc.) at startup, and + it might be difficult to communicate changes to these parameters after that point. This + message is always a warning. + + + Sent by clients that receive an extended server hello containing an extension that they did + not put in the corresponding client hello. This message is always fatal. + + + This alert is sent by servers who are unable to retrieve a certificate chain from the URL + supplied by the client (see Section 3.3). This message MAY be fatal - for example if client + authentication is required by the server for the handshake to continue and the server is + unable to retrieve the certificate chain, it may send a fatal alert. + + + This alert is sent by servers that receive a server_name extension request, but do not + recognize the server name. This message MAY be fatal. + + + This alert is sent by clients that receive an invalid certificate status response (see + Section 3.6). This message is always fatal. + + + This alert is sent by servers when a certificate hash does not match a client provided + certificate_hash. This message is always fatal. + + + If the server does not recognize the PSK identity, it MAY respond with an + "unknown_psk_identity" alert message. + + + If TLS_FALLBACK_SCSV appears in ClientHello.cipher_suites and the highest protocol version + supported by the server is higher than the version indicated in ClientHello.client_version, + the server MUST respond with a fatal inappropriate_fallback alert [..]. + + + + RFC 5246 7.2 + + + + RFC 2246 + + Note that the values here are implementation-specific and arbitrary. It is recommended not to + depend on the particular values (e.g. serialization). + + + + + A queue for bytes. +

    + This file could be more optimized. +

    +
    +
    + + The initial size for our buffer. + + + The smallest number which can be written as 2^x which is bigger than i. + + + The buffer where we store our data. + + + How many bytes at the beginning of the buffer are skipped. + + + How many bytes in the buffer are valid data. + + + Read data from the buffer. + The buffer where the read data will be copied to. + How many bytes to skip at the beginning of buf. + How many bytes to read at all. + How many bytes from our data to skip. + + + Add some data to our buffer. + A byte-array to read data from. + How many bytes to skip at the beginning of the array. + How many bytes to read from the array. + + + Remove some bytes from our data from the beginning. + How many bytes to remove. + + + The number of bytes which are available in this buffer. + + + Parsing and encoding of a Certificate struct from RFC 4346. +

    +

    +             opaque ASN.1Cert<2^24-1>;
    +            
    +             struct {
    +                 ASN.1Cert certificate_list<0..2^24-1>;
    +             } Certificate;
    +             
    + + @see Org.BouncyCastle.Asn1.X509.X509CertificateStructure +
    + + The certificates. + + + @return an array of {@link org.bouncycastle.asn1.x509.Certificate} representing a certificate + chain. + + + Encode this {@link Certificate} to a {@link Stream}. + + @param output the {@link Stream} to encode to. + @throws IOException + + + Parse a {@link Certificate} from a {@link Stream}. + + @param input the {@link Stream} to parse from. + @return a {@link Certificate} object. + @throws IOException + + + @return true if this certificate chain contains no certificates, or + false otherwise. + + + Parsing and encoding of a CertificateRequest struct from RFC 4346. +

    +

    +             struct {
    +                 ClientCertificateType certificate_types<1..2^8-1>;
    +                 DistinguishedName certificate_authorities<3..2^16-1>
    +             } CertificateRequest;
    +             
    + + @see ClientCertificateType + @see X509Name +
    + + @param certificateTypes see {@link ClientCertificateType} for valid constants. + @param certificateAuthorities an {@link IList} of {@link X509Name}. + + + Encode this {@link CertificateRequest} to a {@link Stream}. + + @param output the {@link Stream} to encode to. + @throws IOException + + + Parse a {@link CertificateRequest} from a {@link Stream}. + + @param context + the {@link TlsContext} of the current connection. + @param input + the {@link Stream} to parse from. + @return a {@link CertificateRequest} object. + @throws IOException + + + @return an array of certificate types + @see {@link ClientCertificateType} + + + @return an {@link IList} of {@link SignatureAndHashAlgorithm} (or null before TLS 1.2). + + + @return an {@link IList} of {@link X509Name} + + + Encode this {@link CertificateStatus} to a {@link Stream}. + + @param output + the {@link Stream} to encode to. + @throws IOException + + + Parse a {@link CertificateStatus} from a {@link Stream}. + + @param input + the {@link Stream} to parse from. + @return a {@link CertificateStatus} object. + @throws IOException + + + Encode this {@link CertificateStatusRequest} to a {@link Stream}. + + @param output + the {@link Stream} to encode to. + @throws IOException + + + Parse a {@link CertificateStatusRequest} from a {@link Stream}. + + @param input + the {@link Stream} to parse from. + @return a {@link CertificateStatusRequest} object. + @throws IOException + + + RFC 6091 + + + @param type + see {@link CertChainType} for valid constants. + @param urlAndHashList + a {@link IList} of {@link UrlAndHash}. + + + Encode this {@link CertificateUrl} to a {@link Stream}. + + @param output the {@link Stream} to encode to. + @throws IOException + + + Parse a {@link CertificateUrl} from a {@link Stream}. + + @param context + the {@link TlsContext} of the current connection. + @param input + the {@link Stream} to parse from. + @return a {@link CertificateUrl} object. + @throws IOException + + + @return {@link CertChainType} + + + @return an {@link IList} of {@link UrlAndHash} + + + draft-ietf-tls-chacha20-poly1305-04 + + + + + + + + + + + + + + + + + + + + + + RFC 2246 A.5 + + + + RFC 2246 + + Note that the values here are implementation-specific and arbitrary. It is recommended not to + depend on the particular values (e.g. serialization). + + + + A combined hash, which implements md5(m) || sha1(m). + + + @see org.bouncycastle.crypto.Digest#update(byte[], int, int) + + + @see org.bouncycastle.crypto.Digest#doFinal(byte[], int) + + + @see org.bouncycastle.crypto.Digest#reset() + + + + RFC 2246 6.1 + + + + RFC 2246 + + Note that the values here are implementation-specific and arbitrary. It is recommended not to + depend on the particular values (e.g. serialization). + + + + RFC 2246 6.2.1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Check whether the given SRP group parameters are acceptable for use. + + @param group the {@link SRP6GroupParameters} to check + @return true if (and only if) the specified group parameters are acceptable + + + Accept only the group parameters specified in RFC 5054 Appendix A. + + + Specify a custom set of acceptable group parameters. + + @param groups a {@link Vector} of acceptable {@link SRP6GroupParameters} + + + Buffers input until the hash algorithm is determined. + + + Encode this {@link DigitallySigned} to a {@link Stream}. + + @param output + the {@link Stream} to encode to. + @throws IOException + + + Parse a {@link DigitallySigned} from a {@link Stream}. + + @param context + the {@link TlsContext} of the current connection. + @param input + the {@link Stream} to parse from. + @return a {@link DigitallySigned} object. + @throws IOException + + + @return a {@link SignatureAndHashAlgorithm} (or null before TLS 1.2). + + + + + + + + + + + + + + + + + + + + + + + + + + + Check that there are no "extra" messages left in the current inbound flight + + + RFC 4347 4.1.2.5 Anti-replay +

    + Support fast rejection of duplicate records by maintaining a sliding receive window + + + Check whether a received record with the given sequence number should be rejected as a duplicate. + + @param seq the 48-bit DTLSPlainText.sequence_number field of a received record. + @return true if the record should be discarded without further processing. + + + Report that a received record with the given sequence number passed authentication checks. + + @param seq the 48-bit DTLSPlainText.sequence_number field of an authenticated record. + + + When a new epoch begins, sequence numbers begin again at 0 + + +

    RFC 4492 5.4. (Errata ID: 2389) +
    + + + RFC 4492 5.4 + + + + Indicates the elliptic curve domain parameters are conveyed verbosely, and the + underlying finite field is a prime field. + + + Indicates the elliptic curve domain parameters are conveyed verbosely, and the + underlying finite field is a characteristic-2 field. + + + Indicates that a named curve is used. This option SHOULD be used when applicable. + + + + RFC 4492 5.1.2 + + + + RFC 2246 + + Note that the values here are implementation-specific and arbitrary. It is recommended not to + depend on the particular values (e.g. serialization). + + + + RFC 5705 + + + RFC 5246 7.4.1.4.1 + + + Encode this {@link HeartbeatExtension} to a {@link Stream}. + + @param output + the {@link Stream} to encode to. + @throws IOException + + + Parse a {@link HeartbeatExtension} from a {@link Stream}. + + @param input + the {@link Stream} to parse from. + @return a {@link HeartbeatExtension} object. + @throws IOException + + + Encode this {@link HeartbeatMessage} to a {@link Stream}. + + @param output + the {@link Stream} to encode to. + @throws IOException + + + Parse a {@link HeartbeatMessage} from a {@link Stream}. + + @param input + the {@link Stream} to parse from. + @return a {@link HeartbeatMessage} object. + @throws IOException + + + RFC 2246 + + Note that the values here are implementation-specific and arbitrary. It is recommended not to + depend on the particular values (e.g. serialization). + + + + RFC 2246 + + Note that the values here are implementation-specific and arbitrary. It is recommended not to + depend on the particular values (e.g. serialization). + + + + + RFC 4492 5.1.1 + The named curves defined here are those specified in SEC 2 [13]. Note that many of + these curves are also recommended in ANSI X9.62 [7] and FIPS 186-2 [11]. Values 0xFE00 + through 0xFEFF are reserved for private use. Values 0xFF01 and 0xFF02 indicate that the + client supports arbitrary prime and characteristic-2 curves, respectively (the curve + parameters must be encoded explicitly in ECParameters). + + + + Encode this {@link NewSessionTicket} to a {@link Stream}. + + @param output the {@link Stream} to encode to. + @throws IOException + + + Parse a {@link NewSessionTicket} from a {@link Stream}. + + @param input the {@link Stream} to parse from. + @return a {@link NewSessionTicket} object. + @throws IOException + + + RFC 3546 3.6 + + + @param responderIDList + an {@link IList} of {@link ResponderID}, specifying the list of trusted OCSP + responders. An empty list has the special meaning that the responders are + implicitly known to the server - e.g., by prior arrangement. + @param requestExtensions + OCSP request extensions. A null value means that there are no extensions. + + + Encode this {@link OcspStatusRequest} to a {@link Stream}. + + @param output + the {@link Stream} to encode to. + @throws IOException + + + Parse a {@link OcspStatusRequest} from a {@link Stream}. + + @param input + the {@link Stream} to parse from. + @return an {@link OcspStatusRequest} object. + @throws IOException + + + @return an {@link IList} of {@link ResponderID} + + + @return OCSP request extensions + + + RFC 5246 + + Note that the values here are implementation-specific and arbitrary. It is recommended not to + depend on the particular values (e.g. serialization). + + + + + + + An implementation of the TLS 1.0/1.1/1.2 record layer, allowing downgrade to SSLv3. + + + RFC 5246 E.1. "Earlier versions of the TLS specification were not fully clear on what the + record layer version number (TLSPlaintext.version) should contain when sending ClientHello + (i.e., before it is known which version of the protocol will be employed). Thus, TLS servers + compliant with this specification MUST accept any value {03,XX} as the record layer version + number for ClientHello." + + + @return {@link ConnectionEnd} + + + @return {@link CipherSuite} + + + @return {@link CompressionMethod} + + + @return {@link PRFAlgorithm} + + + Encode this {@link ServerDHParams} to a {@link Stream}. + + @param output + the {@link Stream} to encode to. + @throws IOException + + + Parse a {@link ServerDHParams} from a {@link Stream}. + + @param input + the {@link Stream} to parse from. + @return a {@link ServerDHParams} object. + @throws IOException + + + Encode this {@link ServerName} to a {@link Stream}. + + @param output + the {@link Stream} to encode to. + @throws IOException + + + Parse a {@link ServerName} from a {@link Stream}. + + @param input + the {@link Stream} to parse from. + @return a {@link ServerName} object. + @throws IOException + + + @param serverNameList an {@link IList} of {@link ServerName}. + + + Encode this {@link ServerNameList} to a {@link Stream}. + + @param output + the {@link Stream} to encode to. + @throws IOException + + + Parse a {@link ServerNameList} from a {@link Stream}. + + @param input + the {@link Stream} to parse from. + @return a {@link ServerNameList} object. + @throws IOException + + + @return an {@link IList} of {@link ServerName}. + + + + Called by the protocol handler to report the server certificate. + + + This method is responsible for certificate verification and validation + + The server received + + + + + Return client credentials in response to server's certificate request + + + A containing server certificate request details + + + A to be used for client authentication + (or null for no client authentication) + + + + + Encode this {@link ServerSRPParams} to an {@link OutputStream}. + + @param output + the {@link OutputStream} to encode to. + @throws IOException + + + Parse a {@link ServerSRPParams} from an {@link InputStream}. + + @param input + the {@link InputStream} to parse from. + @return a {@link ServerSRPParams} object. + @throws IOException + + + RFC 5246 7.4.1.4.1 (in RFC 2246, there were no specific values assigned) + + + RFC 5246 7.4.1.4.1 + + + @param hash {@link HashAlgorithm} + @param signature {@link SignatureAlgorithm} + + + Encode this {@link SignatureAndHashAlgorithm} to a {@link Stream}. + + @param output the {@link Stream} to encode to. + @throws IOException + + + Parse a {@link SignatureAndHashAlgorithm} from a {@link Stream}. + + @param input the {@link Stream} to parse from. + @return a {@link SignatureAndHashAlgorithm} object. + @throws IOException + + + @return {@link HashAlgorithm} + + + @return {@link SignatureAlgorithm} + + + An implementation of {@link TlsSRPIdentityManager} that simulates the existence of "unknown" identities + to obscure the fact that there is no verifier for them. + + + Lookup the {@link TlsSRPLoginParameters} corresponding to the specified identity. + + NOTE: To avoid "identity probing", unknown identities SHOULD be handled as recommended in RFC + 5054 2.5.1.3. {@link SimulatedTlsSRPIdentityManager} is provided for this purpose. + + @param identity + the SRP identity sent by the connecting client + @return the {@link TlsSRPLoginParameters} for the specified identity, or else 'simulated' + parameters if the identity is not recognized. A null value is also allowed, but not + recommended. + + + Create a {@link SimulatedTlsSRPIdentityManager} that implements the algorithm from RFC 5054 2.5.1.3 + + @param group the {@link SRP6GroupParameters} defining the group that SRP is operating in + @param seedKey the secret "seed key" referred to in RFC 5054 2.5.1.3 + @return an instance of {@link SimulatedTlsSRPIdentityManager} + + + HMAC implementation based on original internet draft for HMAC (RFC 2104) + + The difference is that padding is concatentated versus XORed with the key + + H(K + opad, H(K + ipad, text)) + + + Base constructor for one of the standard digest algorithms that the byteLength of + the algorithm is know for. Behaviour is undefined for digests other than MD5 or SHA1. + + @param digest the digest. + + + Reset the mac generator. + + + RFC 4680 + + + + + + + + + + + + + + + + + + + A generic TLS 1.0-1.2 / SSLv3 block cipher. This can be used for AES or 3DES for example. + + + + + + + + + + This method is called, when a change cipher spec message is received. + + @throws IOException If the message has an invalid content or the handshake is not in the correct + state. + + + Read data from the network. The method will return immediately, if there is still some data + left in the buffer, or block until some application data has been read from the network. + + @param buf The buffer where the data will be copied to. + @param offset The position where the data will be placed in the buffer. + @param len The maximum number of bytes to read. + @return The number of bytes read. + @throws IOException If something goes wrong during reading data. + + + Send some application data to the remote system. +

    + The method will handle fragmentation internally. + + @param buf The buffer with the data. + @param offset The position in the buffer where the data is placed. + @param len The length of the data. + @throws IOException If something goes wrong during sending. + + + Offer input from an arbitrary source. Only allowed in non-blocking mode.
    +
    + After this method returns, the input buffer is "owned" by this object. Other code + must not attempt to do anything with it.
    +
    + This method will decrypt and process all records that are fully available. + If only part of a record is available, the buffer will be retained until the + remainder of the record is offered.
    +
    + If any records containing application data were processed, the decrypted data + can be obtained using {@link #readInput(byte[], int, int)}. If any records + containing protocol data were processed, a response may have been generated. + You should always check to see if there is any available output after calling + this method by calling {@link #getAvailableOutputBytes()}. + @param input The input buffer to offer + @throws IOException If an error occurs while decrypting or processing a record +
    + + Gets the amount of received application data. A call to {@link #readInput(byte[], int, int)} + is guaranteed to be able to return at least this much data.
    +
    + Only allowed in non-blocking mode. + @return The number of bytes of available application data +
    + + Retrieves received application data. Use {@link #getAvailableInputBytes()} to check + how much application data is currently available. This method functions similarly to + {@link InputStream#read(byte[], int, int)}, except that it never blocks. If no data + is available, nothing will be copied and zero will be returned.
    +
    + Only allowed in non-blocking mode. + @param buffer The buffer to hold the application data + @param offset The start offset in the buffer at which the data is written + @param length The maximum number of bytes to read + @return The total number of bytes copied to the buffer. May be less than the + length specified if the length was greater than the amount of available data. +
    + + Offer output from an arbitrary source. Only allowed in non-blocking mode.
    +
    + After this method returns, the specified section of the buffer will have been + processed. Use {@link #readOutput(byte[], int, int)} to get the bytes to + transmit to the other peer.
    +
    + This method must not be called until after the handshake is complete! Attempting + to call it before the handshake is complete will result in an exception. + @param buffer The buffer containing application data to encrypt + @param offset The offset at which to begin reading data + @param length The number of bytes of data to read + @throws IOException If an error occurs encrypting the data, or the handshake is not complete +
    + + Gets the amount of encrypted data available to be sent. A call to + {@link #readOutput(byte[], int, int)} is guaranteed to be able to return at + least this much data.
    +
    + Only allowed in non-blocking mode. + @return The number of bytes of available encrypted data +
    + + Retrieves encrypted data to be sent. Use {@link #getAvailableOutputBytes()} to check + how much encrypted data is currently available. This method functions similarly to + {@link InputStream#read(byte[], int, int)}, except that it never blocks. If no data + is available, nothing will be copied and zero will be returned.
    +
    + Only allowed in non-blocking mode. + @param buffer The buffer to hold the encrypted data + @param offset The start offset in the buffer at which the data is written + @param length The maximum number of bytes to read + @return The total number of bytes copied to the buffer. May be less than the + length specified if the length was greater than the amount of available data. +
    + + Terminate this connection with an alert. Can be used for normal closure too. + + @param alertLevel + See {@link AlertLevel} for values. + @param alertDescription + See {@link AlertDescription} for values. + @throws IOException + If alert was fatal. + + + Closes this connection. + + @throws IOException If something goes wrong during closing. + + + Make sure the InputStream 'buf' now empty. Fail otherwise. + + @param buf The InputStream to check. + @throws IOException If 'buf' is not empty. + + + 'sender' only relevant to SSLv3 + + +

    The secure bidirectional stream for this connection + Only allowed in blocking mode. +
    + + Constructor for blocking mode. + @param stream The bi-directional stream of data to/from the server + @param secureRandom Random number generator for various cryptographic functions + + + Constructor for blocking mode. + @param input The stream of data from the server + @param output The stream of data to the server + @param secureRandom Random number generator for various cryptographic functions + + + Constructor for non-blocking mode.
    +
    + When data is received, use {@link #offerInput(java.nio.ByteBuffer)} to + provide the received ciphertext, then use + {@link #readInput(byte[], int, int)} to read the corresponding cleartext.
    +
    + Similarly, when data needs to be sent, use + {@link #offerOutput(byte[], int, int)} to provide the cleartext, then use + {@link #readOutput(byte[], int, int)} to get the corresponding + ciphertext. + + @param secureRandom + Random number generator for various cryptographic functions +
    + + Initiates a TLS handshake in the role of client.
    +
    + In blocking mode, this will not return until the handshake is complete. + In non-blocking mode, use {@link TlsPeer#NotifyHandshakeComplete()} to + receive a callback when the handshake is complete. + + @param tlsClient The {@link TlsClient} to use for the handshake. + @throws IOException If in blocking mode and handshake was not successful. +
    + + (D)TLS DH key exchange. + + + (D)TLS ECDHE key exchange (see RFC 4492). + + + (D)TLS ECDH key exchange (see RFC 4492). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A generic TLS MAC implementation, acting as an HMAC based on some underlying Digest. + + + + Generate a new instance of an TlsMac. + + @param context the TLS client context + @param digest The digest to use. + @param key A byte-array where the key for this MAC is located. + @param keyOff The number of bytes to skip, before the key starts in the buffer. + @param keyLen The length of the key. + + + Calculate the MAC for some given data. + + @param type The message type of the message. + @param message A byte-buffer containing the message. + @param offset The number of bytes to skip, before the message starts. + @param length The length of the message. + @return A new byte-buffer containing the MAC value. + + + @return the MAC write secret + + + @return The output length of this MAC. + + + + A NULL CipherSuite, with optional MAC. + + + + + + + + + + + + + Both streams can be the same object + + + (D)TLS PSK key exchange (RFC 4279). + + + (D)TLS and SSLv3 RSA key exchange. + + + + + + Constructor for blocking mode. + @param stream The bi-directional stream of data to/from the client + @param output The stream of data to the client + @param secureRandom Random number generator for various cryptographic functions + + + Constructor for blocking mode. + @param input The stream of data from the client + @param output The stream of data to the client + @param secureRandom Random number generator for various cryptographic functions + + + Constructor for non-blocking mode.
    +
    + When data is received, use {@link #offerInput(java.nio.ByteBuffer)} to + provide the received ciphertext, then use + {@link #readInput(byte[], int, int)} to read the corresponding cleartext.
    +
    + Similarly, when data needs to be sent, use + {@link #offerOutput(byte[], int, int)} to provide the cleartext, then use + {@link #readOutput(byte[], int, int)} to get the corresponding + ciphertext. + + @param secureRandom + Random number generator for various cryptographic functions +
    + + Receives a TLS handshake in the role of server.
    +
    + In blocking mode, this will not return until the handshake is complete. + In non-blocking mode, use {@link TlsPeer#notifyHandshakeComplete()} to + receive a callback when the handshake is complete. + + @param tlsServer + @throws IOException If in blocking mode and handshake was not successful. +
    + + (D)TLS SRP key exchange (RFC 5054). + + + RFC 5764 DTLS Extension to Establish Keys for SRTP. + + + + + + + + + + + + Some helper functions for MicroTLS. + + + Add a 'signature_algorithms' extension to existing extensions. + + @param extensions A {@link Hashtable} to add the extension to. + @param supportedSignatureAlgorithms {@link Vector} containing at least 1 {@link SignatureAndHashAlgorithm}. + @throws IOException + + + Get a 'signature_algorithms' extension from extensions. + + @param extensions A {@link Hashtable} to get the extension from, if it is present. + @return A {@link Vector} containing at least 1 {@link SignatureAndHashAlgorithm}, or null. + @throws IOException + + + Create a 'signature_algorithms' extension value. + + @param supportedSignatureAlgorithms A {@link Vector} containing at least 1 {@link SignatureAndHashAlgorithm}. + @return A byte array suitable for use as an extension value. + @throws IOException + + + Read 'signature_algorithms' extension data. + + @param extensionData The extension data. + @return A {@link Vector} containing at least 1 {@link SignatureAndHashAlgorithm}. + @throws IOException + + + RFC 6066 5. + + + Encode this {@link UrlAndHash} to a {@link Stream}. + + @param output the {@link Stream} to encode to. + @throws IOException + + + Parse a {@link UrlAndHash} from a {@link Stream}. + + @param context + the {@link TlsContext} of the current connection. + @param input + the {@link Stream} to parse from. + @return a {@link UrlAndHash} object. + @throws IOException + + + RFC 4681 + + + RFC 5764 4.1.1 + + + @param protectionProfiles see {@link SrtpProtectionProfile} for valid constants. + @param mki valid lengths from 0 to 255. + + + @return see {@link SrtpProtectionProfile} for valid constants. + + + @return valid lengths from 0 to 255. + + + return a = a + b - b preserved. + + + unsigned comparison on two arrays - note the arrays may + start with leading zeros. + + + return z = x / y - done in place (z value preserved, x contains the + remainder) + + + return whether or not a BigInteger is probably prime with a + probability of 1 - (1/2)**certainty. +

    From Knuth Vol 2, pg 395.

    +
    + + Calculate the numbers u1, u2, and u3 such that: + + u1 * a + u2 * b = u3 + + where u3 is the greatest common divider of a and b. + a and b using the extended Euclid algorithm (refer p. 323 + of The Art of Computer Programming vol 2, 2nd ed). + This also seems to have the side effect of calculating + some form of multiplicative inverse. + + @param a First number to calculate gcd for + @param b Second number to calculate gcd for + @param u1Out the return object for the u1 value + @return The greatest common divisor of a and b + + + return w with w = x * x - w is assumed to have enough space. + + + return x with x = y * z - x is assumed to have enough space. + + + Calculate mQuote = -m^(-1) mod b with b = 2^32 (32 = word size) + + + Montgomery multiplication: a = x * y * R^(-1) mod m +
    + Based algorithm 14.36 of Handbook of Applied Cryptography. +
    +
  • m, x, y should have length n
  • +
  • a should have length (n + 1)
  • +
  • b = 2^32, R = b^n
  • +
    + The result is put in x +
    + NOTE: the indices of x, y, m, a different in HAC and in Java +
    + + return x = x % y - done in place (y value preserved) + + + do a left shift - this returns a new array. + + + do a right shift - this does it in place. + + + do a right shift by one - this does it in place. + + + returns x = x - y - we assume x is >= y + + + Class representing a simple version of a big decimal. A + SimpleBigDecimal is basically a + {@link java.math.BigInteger BigInteger} with a few digits on the right of + the decimal point. The number of (binary) digits on the right of the decimal + point is called the scale of the SimpleBigDecimal. + Unlike in {@link java.math.BigDecimal BigDecimal}, the scale is not adjusted + automatically, but must be set manually. All SimpleBigDecimals + taking part in the same arithmetic operation must have equal scale. The + result of a multiplication of two SimpleBigDecimals returns a + SimpleBigDecimal with double scale. + + + Returns a SimpleBigDecimal representing the same numerical + value as value. + @param value The value of the SimpleBigDecimal to be + created. + @param scale The scale of the SimpleBigDecimal to be + created. + @return The such created SimpleBigDecimal. + + + Constructor for SimpleBigDecimal. The value of the + constructed SimpleBigDecimal Equals bigInt / + 2scale. + @param bigInt The bigInt value parameter. + @param scale The scale of the constructed SimpleBigDecimal. + + + Class holding methods for point multiplication based on the window + τ-adic nonadjacent form (WTNAF). The algorithms are based on the + paper "Improved Algorithms for Arithmetic on Anomalous Binary Curves" + by Jerome A. Solinas. The paper first appeared in the Proceedings of + Crypto 1997. + + + The window width of WTNAF. The standard value of 4 is slightly less + than optimal for running time, but keeps space requirements for + precomputation low. For typical curves, a value of 5 or 6 results in + a better running time. When changing this value, the + αu's must be computed differently, see + e.g. "Guide to Elliptic Curve Cryptography", Darrel Hankerson, + Alfred Menezes, Scott Vanstone, Springer-Verlag New York Inc., 2004, + p. 121-122 + + + 24 + + + The αu's for a=0 as an array + of ZTauElements. + + + The αu's for a=0 as an array + of TNAFs. + + + The αu's for a=1 as an array + of ZTauElements. + + + The αu's for a=1 as an array + of TNAFs. + + + Computes the norm of an element λ of + Z[τ]. + @param mu The parameter μ of the elliptic curve. + @param lambda The element λ of + Z[τ]. + @return The norm of λ. + + + Computes the norm of an element λ of + R[τ], where λ = u + vτ + and u and u are real numbers (elements of + R). + @param mu The parameter μ of the elliptic curve. + @param u The real part of the element λ of + R[τ]. + @param v The τ-adic part of the element + λ of R[τ]. + @return The norm of λ. + + + Rounds an element λ of R[τ] + to an element of Z[τ], such that their difference + has minimal norm. λ is given as + λ = λ0 + λ1τ. + @param lambda0 The component λ0. + @param lambda1 The component λ1. + @param mu The parameter μ of the elliptic curve. Must + equal 1 or -1. + @return The rounded element of Z[τ]. + @throws ArgumentException if lambda0 and + lambda1 do not have same scale. + + + Approximate division by n. For an integer + k, the value λ = s k / n is + computed to c bits of accuracy. + @param k The parameter k. + @param s The curve parameter s0 or + s1. + @param vm The Lucas Sequence element Vm. + @param a The parameter a of the elliptic curve. + @param m The bit length of the finite field + Fm. + @param c The number of bits of accuracy, i.e. the scale of the returned + SimpleBigDecimal. + @return The value λ = s k / n computed to + c bits of accuracy. + + + Computes the τ-adic NAF (non-adjacent form) of an + element λ of Z[τ]. + @param mu The parameter μ of the elliptic curve. + @param lambda The element λ of + Z[τ]. + @return The τ-adic NAF of λ. + + + Applies the operation τ() to an + AbstractF2mPoint. + @param p The AbstractF2mPoint to which τ() is applied. + @return τ(p) + + + Returns the parameter μ of the elliptic curve. + @param curve The elliptic curve from which to obtain μ. + The curve must be a Koblitz curve, i.e. a Equals + 0 or 1 and b Equals + 1. + @return μ of the elliptic curve. + @throws ArgumentException if the given ECCurve is not a Koblitz + curve. + + + Calculates the Lucas Sequence elements Uk-1 and + Uk or Vk-1 and + Vk. + @param mu The parameter μ of the elliptic curve. + @param k The index of the second element of the Lucas Sequence to be + returned. + @param doV If set to true, computes Vk-1 and + Vk, otherwise Uk-1 and + Uk. + @return An array with 2 elements, containing Uk-1 + and Uk or Vk-1 + and Vk. + + + Computes the auxiliary value tw. If the width is + 4, then for mu = 1, tw = 6 and for + mu = -1, tw = 10 + @param mu The parameter μ of the elliptic curve. + @param w The window width of the WTNAF. + @return the auxiliary value tw + + + Computes the auxiliary values s0 and + s1 used for partial modular reduction. + @param curve The elliptic curve for which to compute + s0 and s1. + @throws ArgumentException if curve is not a + Koblitz curve (Anomalous Binary Curve, ABC). + + + Partial modular reduction modulo + m - 1)/(τ - 1). + @param k The integer to be reduced. + @param m The bitlength of the underlying finite field. + @param a The parameter a of the elliptic curve. + @param s The auxiliary values s0 and + s1. + @param mu The parameter μ of the elliptic curve. + @param c The precision (number of bits of accuracy) of the partial + modular reduction. + @return ρ := k partmod (τm - 1)/(τ - 1) + + + Multiplies a {@link org.bouncycastle.math.ec.AbstractF2mPoint AbstractF2mPoint} + by a BigInteger using the reduced τ-adic + NAF (RTNAF) method. + @param p The AbstractF2mPoint to Multiply. + @param k The BigInteger by which to Multiply p. + @return k * p + + + Multiplies a {@link org.bouncycastle.math.ec.AbstractF2mPoint AbstractF2mPoint} + by an element λ of Z[τ] + using the τ-adic NAF (TNAF) method. + @param p The AbstractF2mPoint to Multiply. + @param lambda The element λ of + Z[τ]. + @return λ * p + + + Multiplies a {@link org.bouncycastle.math.ec.AbstractF2mPoint AbstractF2mPoint} + by an element λ of Z[τ] + using the τ-adic NAF (TNAF) method, given the TNAF + of λ. + @param p The AbstractF2mPoint to Multiply. + @param u The the TNAF of λ.. + @return λ * p + + + Computes the [τ]-adic window NAF of an element + λ of Z[τ]. + @param mu The parameter μ of the elliptic curve. + @param lambda The element λ of + Z[τ] of which to compute the + [τ]-adic NAF. + @param width The window width of the resulting WNAF. + @param pow2w 2width. + @param tw The auxiliary value tw. + @param alpha The αu's for the window width. + @return The [τ]-adic window NAF of + λ. + + + Does the precomputation for WTNAF multiplication. + @param p The ECPoint for which to do the precomputation. + @param a The parameter a of the elliptic curve. + @return The precomputation array for p. + + + Class representing an element of Z[τ]. Let + λ be an element of Z[τ]. Then + λ is given as λ = u + vτ. The + components u and v may be used directly, there + are no accessor methods. + Immutable class. + + + The "real" part of λ. + + + The "τ-adic" part of λ. + + + Constructor for an element λ of + Z[τ]. + @param u The "real" part of λ. + @param v The "τ-adic" part of + λ. + + + Base class for an elliptic curve. + + + Adds PreCompInfo for a point on this curve, under a given name. Used by + ECMultipliers to save the precomputation for this ECPoint for use + by subsequent multiplication. + + @param point + The ECPoint to store precomputations for. + @param name + A String used to index precomputations of different types. + @param preCompInfo + The values precomputed by the ECMultiplier. + + + Normalization ensures that any projective coordinate is 1, and therefore that the x, y + coordinates reflect those of the equivalent point in an affine coordinate system. Where more + than one point is to be normalized, this method will generally be more efficient than + normalizing each point separately. + + @param points + An array of points that will be updated in place with their normalized versions, + where necessary + + + Normalization ensures that any projective coordinate is 1, and therefore that the x, y + coordinates reflect those of the equivalent point in an affine coordinate system. Where more + than one point is to be normalized, this method will generally be more efficient than + normalizing each point separately. An (optional) z-scaling factor can be applied; effectively + each z coordinate is scaled by this value prior to normalization (but only one + actual multiplication is needed). + + @param points + An array of points that will be updated in place with their normalized versions, + where necessary + @param off + The start of the range of points to normalize + @param len + The length of the range of points to normalize + @param iso + The (optional) z-scaling factor - can be null + + + Sets the default ECMultiplier, unless already set. + + + Decode a point on this curve from its ASN.1 encoding. The different + encodings are taken account of, including point compression for + Fp (X9.62 s 4.2.1 pg 17). + @return The decoded point. + + + return a sqrt root - the routine verifies that the calculation returns the right value - if + none exists it returns null. + + + base class for points on elliptic curves. + + + Normalization ensures that any projective coordinate is 1, and therefore that the x, y + coordinates reflect those of the equivalent point in an affine coordinate system. + + @return a new ECPoint instance representing the same point, but with normalized coordinates + + + Normalizes this point, and then returns the affine x-coordinate. + + Note: normalization can be expensive, this method is deprecated in favour + of caller-controlled normalization. + + + Normalizes this point, and then returns the affine y-coordinate. + + Note: normalization can be expensive, this method is deprecated in favour + of caller-controlled normalization. + + + Returns the affine x-coordinate after checking that this point is normalized. + + @return The affine x-coordinate of this point + @throws IllegalStateException if the point is not normalized + + + Returns the affine y-coordinate after checking that this point is normalized + + @return The affine y-coordinate of this point + @throws IllegalStateException if the point is not normalized + + + Returns the x-coordinate. + + Caution: depending on the curve's coordinate system, this may not be the same value as in an + affine coordinate system; use Normalize() to get a point where the coordinates have their + affine values, or use AffineXCoord if you expect the point to already have been normalized. + + @return the x-coordinate of this point + + + Returns the y-coordinate. + + Caution: depending on the curve's coordinate system, this may not be the same value as in an + affine coordinate system; use Normalize() to get a point where the coordinates have their + affine values, or use AffineYCoord if you expect the point to already have been normalized. + + @return the y-coordinate of this point + + + return the field element encoded with point compression. (S 4.3.6) + + + Multiplies this ECPoint by the given number. + @param k The multiplicator. + @return k * this. + + + Create a point which encodes with point compression. + + @param curve the curve to use + @param x affine x co-ordinate + @param y affine y co-ordinate + + @deprecated Use ECCurve.CreatePoint to construct points + + + Create a point that encodes with or without point compresion. + + @param curve the curve to use + @param x affine x co-ordinate + @param y affine y co-ordinate + @param withCompression if true encode with point compression + + @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + + + return a sqrt root - the routine verifies that the calculation returns the right value - if + none exists it returns null. + + + Create a point which encodes with point compression. + + @param curve + the curve to use + @param x + affine x co-ordinate + @param y + affine y co-ordinate + + @deprecated Use ECCurve.createPoint to construct points + + + Create a point that encodes with or without point compresion. + + @param curve + the curve to use + @param x + affine x co-ordinate + @param y + affine y co-ordinate + @param withCompression + if true encode with point compression + + @deprecated per-point compression property will be removed, refer + {@link #getEncoded(boolean)} + + + Create a point which encodes with point compression. + + @param curve + the curve to use + @param x + affine x co-ordinate + @param y + affine y co-ordinate + + @deprecated Use ECCurve.CreatePoint to construct points + + + Create a point that encodes with or without point compresion. + + @param curve + the curve to use + @param x + affine x co-ordinate + @param y + affine y co-ordinate + @param withCompression + if true encode with point compression + + @deprecated per-point compression property will be removed, refer + {@link #getEncoded(bool)} + + + return a sqrt root - the routine verifies that the calculation returns the right value - if + none exists it returns null. + + + Create a point which encodes with point compression. + + @param curve + the curve to use + @param x + affine x co-ordinate + @param y + affine y co-ordinate + + @deprecated Use ECCurve.CreatePoint to construct points + + + Create a point that encodes with or without point compresion. + + @param curve + the curve to use + @param x + affine x co-ordinate + @param y + affine y co-ordinate + @param withCompression + if true encode with point compression + + @deprecated per-point compression property will be removed, refer + {@link #getEncoded(bool)} + + + return a sqrt root - the routine verifies that the calculation returns the right value - if + none exists it returns null. + + + Create a point which encodes with point compression. + + @param curve + the curve to use + @param x + affine x co-ordinate + @param y + affine y co-ordinate + + @deprecated Use ECCurve.CreatePoint to construct points + + + Create a point that encodes with or without point compresion. + + @param curve + the curve to use + @param x + affine x co-ordinate + @param y + affine y co-ordinate + @param withCompression + if true encode with point compression + + @deprecated per-point compression property will be removed, refer + {@link #getEncoded(bool)} + + + return a sqrt root - the routine verifies that the calculation returns the right value - if + none exists it returns null. + + + Create a point which encodes with point compression. + + @param curve + the curve to use + @param x + affine x co-ordinate + @param y + affine y co-ordinate + + @deprecated Use ECCurve.createPoint to construct points + + + Create a point that encodes with or without point compresion. + + @param curve + the curve to use + @param x + affine x co-ordinate + @param y + affine y co-ordinate + @param withCompression + if true encode with point compression + + @deprecated per-point compression property will be removed, refer + {@link #getEncoded(bool)} + + + return a sqrt root - the routine verifies that the calculation returns the right value - if + none exists it returns null. + + + Create a point which encodes with point compression. + + @param curve + the curve to use + @param x + affine x co-ordinate + @param y + affine y co-ordinate + + @deprecated Use ECCurve.createPoint to construct points + + + Create a point that encodes with or without point compresion. + + @param curve + the curve to use + @param x + affine x co-ordinate + @param y + affine y co-ordinate + @param withCompression + if true encode with point compression + + @deprecated per-point compression property will be removed, refer + {@link #getEncoded(bool)} + + + return a sqrt root - the routine verifies that the calculation returns the right value - if + none exists it returns null. + + + Create a point which encodes with point compression. + + @param curve + the curve to use + @param x + affine x co-ordinate + @param y + affine y co-ordinate + + @deprecated Use ECCurve.createPoint to construct points + + + Create a point that encodes with or without point compresion. + + @param curve + the curve to use + @param x + affine x co-ordinate + @param y + affine y co-ordinate + @param withCompression + if true encode with point compression + + @deprecated per-point compression property will be removed, refer + {@link #getEncoded(bool)} + + + return a sqrt root - the routine verifies that the calculation returns the right value - if + none exists it returns null. + + + Create a point which encodes with point compression. + + @param curve + the curve to use + @param x + affine x co-ordinate + @param y + affine y co-ordinate + + @deprecated Use ECCurve.createPoint to construct points + + + Create a point that encodes with or without point compresion. + + @param curve + the curve to use + @param x + affine x co-ordinate + @param y + affine y co-ordinate + @param withCompression + if true encode with point compression + + @deprecated per-point compression property will be removed, refer + {@link #getEncoded(bool)} + + + return a sqrt root - the routine verifies that the calculation returns the right value - if + none exists it returns null. + + + Create a point which encodes with point compression. + + @param curve + the curve to use + @param x + affine x co-ordinate + @param y + affine y co-ordinate + + @deprecated Use ECCurve.createPoint to construct points + + + Create a point that encodes with or without point compresion. + + @param curve + the curve to use + @param x + affine x co-ordinate + @param y + affine y co-ordinate + @param withCompression + if true encode with point compression + + @deprecated per-point compression property will be removed, refer + {@link #getEncoded(bool)} + + + return a sqrt root - the routine verifies that the calculation returns the right value - if + none exists it returns null. + + + Create a point which encodes with point compression. + + @param curve + the curve to use + @param x + affine x co-ordinate + @param y + affine y co-ordinate + + @deprecated Use ECCurve.createPoint to construct points + + + Create a point that encodes with or without point compresion. + + @param curve + the curve to use + @param x + affine x co-ordinate + @param y + affine y co-ordinate + @param withCompression + if true encode with point compression + + @deprecated per-point compression property will be removed, refer + {@link #getEncoded(bool)} + + + return a sqrt root - the routine verifies that the calculation returns the right value - if + none exists it returns null. + + + Create a point which encodes with point compression. + + @param curve + the curve to use + @param x + affine x co-ordinate + @param y + affine y co-ordinate + + @deprecated Use ECCurve.createPoint to construct points + + + Create a point that encodes with or without point compresion. + + @param curve + the curve to use + @param x + affine x co-ordinate + @param y + affine y co-ordinate + @param withCompression + if true encode with point compression + + @deprecated per-point compression property will be removed, refer + {@link #getEncoded(bool)} + + + return a sqrt root - the routine verifies that the calculation returns the right value - if + none exists it returns null. + + + Create a point which encodes with point compression. + + @param curve + the curve to use + @param x + affine x co-ordinate + @param y + affine y co-ordinate + + @deprecated Use ECCurve.createPoint to construct points + + + Create a point that encodes with or without point compresion. + + @param curve + the curve to use + @param x + affine x co-ordinate + @param y + affine y co-ordinate + @param withCompression + if true encode with point compression + + @deprecated per-point compression property will be removed, refer + {@link #getEncoded(bool)} + + + The auxiliary values s0 and + s1 used for partial modular reduction for + Koblitz curves. + + + Solves a quadratic equation z2 + z = beta(X9.62 + D.1.6) The other solution is z + 1. + + @param beta + The value to solve the qradratic equation for. + @return the solution for z2 + z = beta or + null if no solution exists. + + + @return the auxiliary values s0 and + s1 used for partial modular reduction for + Koblitz curves. + + + Returns true if this is a Koblitz curve (ABC curve). + @return true if this is a Koblitz curve (ABC curve), false otherwise + + + @deprecated Use ECCurve.createPoint to construct points + + + @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + + + @deprecated Use ECCurve.createPoint to construct points + + + @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + + + @deprecated Use ECCurve.createPoint to construct points + + + @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + + + @deprecated Use ECCurve.createPoint to construct points + + + @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + + + @deprecated Use ECCurve.createPoint to construct points + + + @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + + + @deprecated Use ECCurve.createPoint to construct points + + + @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + + + @deprecated Use ECCurve.createPoint to construct points + + + @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + + + @deprecated Use ECCurve.createPoint to construct points + + + @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + + + @deprecated Use ECCurve.createPoint to construct points + + + @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + + + @deprecated Use ECCurve.createPoint to construct points + + + @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + + + @deprecated Use ECCurve.createPoint to construct points + + + @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + + + @deprecated Use ECCurve.createPoint to construct points + + + @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + + + @deprecated Use ECCurve.createPoint to construct points + + + @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + + + @deprecated Use ECCurve.createPoint to construct points + + + @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + + + @deprecated Use ECCurve.createPoint to construct points + + + @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + + + @deprecated Use ECCurve.createPoint to construct points + + + @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + + + @deprecated Use ECCurve.createPoint to construct points + + + @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + + + @deprecated Use ECCurve.createPoint to construct points + + + @deprecated per-point compression property will be removed, refer {@link #getEncoded(bool)} + + + Simple shift-and-add multiplication. Serves as reference implementation + to verify (possibly faster) implementations, and for very small scalars. + + @param p + The point to multiply. + @param k + The multiplier. + @return The result of the point multiplication kP. + + + Elliptic curve over Fp + + + Elliptic curves over F2m. The Weierstrass equation is given by + y2 + xy = x3 + ax2 + b. + + + The exponent m of F2m. + + + TPB: The integer k where xm + + xk + 1 represents the reduction polynomial + f(z).
    + PPB: The integer k1 where xm + + xk3 + xk2 + xk1 + 1 + represents the reduction polynomial f(z).
    +
    + + TPB: Always set to 0
    + PPB: The integer k2 where xm + + xk3 + xk2 + xk1 + 1 + represents the reduction polynomial f(z).
    +
    + + TPB: Always set to 0
    + PPB: The integer k3 where xm + + xk3 + xk2 + xk1 + 1 + represents the reduction polynomial f(z).
    +
    + + The point at infinity on this curve. + + + Constructor for Trinomial Polynomial Basis (TPB). + @param m The exponent m of + F2m. + @param k The integer k where xm + + xk + 1 represents the reduction + polynomial f(z). + @param a The coefficient a in the Weierstrass equation + for non-supersingular elliptic curves over + F2m. + @param b The coefficient b in the Weierstrass equation + for non-supersingular elliptic curves over + F2m. + + + Constructor for Trinomial Polynomial Basis (TPB). + @param m The exponent m of + F2m. + @param k The integer k where xm + + xk + 1 represents the reduction + polynomial f(z). + @param a The coefficient a in the Weierstrass equation + for non-supersingular elliptic curves over + F2m. + @param b The coefficient b in the Weierstrass equation + for non-supersingular elliptic curves over + F2m. + @param order The order of the main subgroup of the elliptic curve. + @param cofactor The cofactor of the elliptic curve, i.e. + #Ea(F2m) = h * n. + + + Constructor for Pentanomial Polynomial Basis (PPB). + @param m The exponent m of + F2m. + @param k1 The integer k1 where xm + + xk3 + xk2 + xk1 + 1 + represents the reduction polynomial f(z). + @param k2 The integer k2 where xm + + xk3 + xk2 + xk1 + 1 + represents the reduction polynomial f(z). + @param k3 The integer k3 where xm + + xk3 + xk2 + xk1 + 1 + represents the reduction polynomial f(z). + @param a The coefficient a in the Weierstrass equation + for non-supersingular elliptic curves over + F2m. + @param b The coefficient b in the Weierstrass equation + for non-supersingular elliptic curves over + F2m. + + + Constructor for Pentanomial Polynomial Basis (PPB). + @param m The exponent m of + F2m. + @param k1 The integer k1 where xm + + xk3 + xk2 + xk1 + 1 + represents the reduction polynomial f(z). + @param k2 The integer k2 where xm + + xk3 + xk2 + xk1 + 1 + represents the reduction polynomial f(z). + @param k3 The integer k3 where xm + + xk3 + xk2 + xk1 + 1 + represents the reduction polynomial f(z). + @param a The coefficient a in the Weierstrass equation + for non-supersingular elliptic curves over + F2m. + @param b The coefficient b in the Weierstrass equation + for non-supersingular elliptic curves over + F2m. + @param order The order of the main subgroup of the elliptic curve. + @param cofactor The cofactor of the elliptic curve, i.e. + #Ea(F2m) = h * n. + + + Return true if curve uses a Trinomial basis. + + @return true if curve Trinomial, false otherwise. + + + return a sqrt root - the routine verifies that the calculation + returns the right value - if none exists it returns null. + + + return the field name for this field. + + @return the string "Fp". + + + Class representing the Elements of the finite field + F2m in polynomial basis (PB) + representation. Both trinomial (Tpb) and pentanomial (Ppb) polynomial + basis representations are supported. Gaussian normal basis (GNB) + representation is not supported. + + + Indicates gaussian normal basis representation (GNB). Number chosen + according to X9.62. GNB is not implemented at present. + + + Indicates trinomial basis representation (Tpb). Number chosen + according to X9.62. + + + Indicates pentanomial basis representation (Ppb). Number chosen + according to X9.62. + + + Tpb or Ppb. + + + The exponent m of F2m. + + + The LongArray holding the bits. + + + Constructor for Ppb. + @param m The exponent m of + F2m. + @param k1 The integer k1 where xm + + xk3 + xk2 + xk1 + 1 + represents the reduction polynomial f(z). + @param k2 The integer k2 where xm + + xk3 + xk2 + xk1 + 1 + represents the reduction polynomial f(z). + @param k3 The integer k3 where xm + + xk3 + xk2 + xk1 + 1 + represents the reduction polynomial f(z). + @param x The BigInteger representing the value of the field element. + + + Constructor for Tpb. + @param m The exponent m of + F2m. + @param k The integer k where xm + + xk + 1 represents the reduction + polynomial f(z). + @param x The BigInteger representing the value of the field element. + + + Checks, if the ECFieldElements a and b + are elements of the same field F2m + (having the same representation). + @param a field element. + @param b field element to be compared. + @throws ArgumentException if a and b + are not elements of the same field + F2m (having the same + representation). + + + @return the representation of the field + F2m, either of + {@link F2mFieldElement.Tpb} (trinomial + basis representation) or + {@link F2mFieldElement.Ppb} (pentanomial + basis representation). + + + @return the degree m of the reduction polynomial + f(z). + + + @return Tpb: The integer k where xm + + xk + 1 represents the reduction polynomial + f(z).
    + Ppb: The integer k1 where xm + + xk3 + xk2 + xk1 + 1 + represents the reduction polynomial f(z).
    +
    + + @return Tpb: Always returns 0
    + Ppb: The integer k2 where xm + + xk3 + xk2 + xk1 + 1 + represents the reduction polynomial f(z).
    +
    + + @return Tpb: Always set to 0
    + Ppb: The integer k3 where xm + + xk3 + xk2 + xk1 + 1 + represents the reduction polynomial f(z).
    +
    + + Elliptic curve points over Fp + + + Create a point which encodes without point compression. + + @param curve the curve to use + @param x affine x co-ordinate + @param y affine y co-ordinate + + + Create a point that encodes with or without point compression. + + @param curve the curve to use + @param x affine x co-ordinate + @param y affine y co-ordinate + @param withCompression if true encode with point compression + + + Elliptic curve points over F2m + + + @param curve base curve + @param x x point + @param y y point + + + @param curve base curve + @param x x point + @param y y point + @param withCompression true if encode with point compression. + + + Constructor for point at infinity + + + Interface for classes encapsulating a point multiplication algorithm + for ECPoints. + + + Multiplies the ECPoint p by k, i.e. + p is added k times to itself. + @param p The ECPoint to be multiplied. + @param k The factor by which p is multiplied. + @return p multiplied by k. + + + Joye's double-add algorithm. + + + Class holding precomputation data for fixed-point multiplications. + + + Interface for classes storing precomputation data for multiplication + algorithms. Used as a Memento (see GOF patterns) for + WNafMultiplier. + + + Array holding the precomputed ECPoints used for a fixed + point multiplication. + + + The width used for the precomputation. If a larger width precomputation + is already available this may be larger than was requested, so calling + code should refer to the actual width. + + + Class implementing the NAF (Non-Adjacent Form) multiplication algorithm (right-to-left) using + mixed coordinates. + + + By default, addition will be done in Jacobian coordinates, and doubling will be done in + Modified Jacobian coordinates (independent of the original coordinate system of each point). + + + Montgomery ladder. + + + Class implementing the NAF (Non-Adjacent Form) multiplication algorithm (left-to-right). + + + Class implementing the NAF (Non-Adjacent Form) multiplication algorithm (right-to-left). + + + Class implementing the WNAF (Window Non-Adjacent Form) multiplication + algorithm. + + + Multiplies this by an integer k using the + Window NAF method. + @param k The integer by which this is multiplied. + @return A new ECPoint which equals this + multiplied by k. + + + Determine window width to use for a scalar multiplication of the given size. + + @param bits the bit-length of the scalar to multiply by + @return the window size to use + + + Class holding precomputation data for the WNAF (Window Non-Adjacent Form) + algorithm. + + + Array holding the precomputed ECPoints used for a Window + NAF multiplication. + + + Array holding the negations of the precomputed ECPoints used + for a Window NAF multiplication. + + + Holds an ECPoint representing Twice(this). Used for the + Window NAF multiplication to create or extend the precomputed values. + + + Computes the Window NAF (non-adjacent Form) of an integer. + @param width The width w of the Window NAF. The width is + defined as the minimal number w, such that for any + w consecutive digits in the resulting representation, at + most one is non-zero. + @param k The integer of which the Window NAF is computed. + @return The Window NAF of the given width, such that the following holds: + k = &sum;i=0l-1 ki2i + , where the ki denote the elements of the + returned byte[]. + + + Determine window width to use for a scalar multiplication of the given size. + + @param bits the bit-length of the scalar to multiply by + @return the window size to use + + + Determine window width to use for a scalar multiplication of the given size. + + @param bits the bit-length of the scalar to multiply by + @param windowSizeCutoffs a monotonically increasing list of bit sizes at which to increment the window width + @return the window size to use + + + Class implementing the WTNAF (Window + τ-adic Non-Adjacent Form) algorithm. + + + Multiplies a {@link org.bouncycastle.math.ec.AbstractF2mPoint AbstractF2mPoint} + by k using the reduced τ-adic NAF (RTNAF) + method. + @param p The AbstractF2mPoint to multiply. + @param k The integer by which to multiply k. + @return p multiplied by k. + + + Multiplies a {@link org.bouncycastle.math.ec.AbstractF2mPoint AbstractF2mPoint} + by an element λ of Z[τ] using + the τ-adic NAF (TNAF) method. + @param p The AbstractF2mPoint to multiply. + @param lambda The element λ of + Z[τ] of which to compute the + [τ]-adic NAF. + @return p multiplied by λ. + + + Multiplies a {@link org.bouncycastle.math.ec.AbstractF2mPoint AbstractF2mPoint} + by an element λ of Z[τ] + using the window τ-adic NAF (TNAF) method, given the + WTNAF of λ. + @param p The AbstractF2mPoint to multiply. + @param u The the WTNAF of λ.. + @return λ * p + + + Class holding precomputation data for the WTNAF (Window + τ-adic Non-Adjacent Form) algorithm. + + + Array holding the precomputed AbstractF2mPoints used for the + WTNAF multiplication in + {@link org.bouncycastle.math.ec.multiplier.WTauNafMultiplier.multiply() + WTauNafMultiplier.multiply()}. + + + 'Zeroless' Signed Digit Left-to-Right. + + + 'Zeroless' Signed Digit Right-to-Left. + + + Utility methods for generating primes and testing for primality. + + + FIPS 186-4 C.6 Shawe-Taylor Random_Prime Routine + + Construct a provable prime number using a hash function. + + @param hash + the {@link Digest} instance to use (as "Hash()"). Cannot be null. + @param length + the length (in bits) of the prime to be generated. Must be at least 2. + @param inputSeed + the seed to be used for the generation of the requested prime. Cannot be null or + empty. + @return an {@link STOutput} instance containing the requested prime. + + + FIPS 186-4 C.3.2 Enhanced Miller-Rabin Probabilistic Primality Test + + Run several iterations of the Miller-Rabin algorithm with randomly-chosen bases. This is an + alternative to {@link #isMRProbablePrime(BigInteger, SecureRandom, int)} that provides more + information about a composite candidate, which may be useful when generating or validating + RSA moduli. + + @param candidate + the {@link BigInteger} instance to test for primality. + @param random + the source of randomness to use to choose bases. + @param iterations + the number of randomly-chosen bases to perform the test for. + @return an {@link MROutput} instance that can be further queried for details. + + + A fast check for small divisors, up to some implementation-specific limit. + + @param candidate + the {@link BigInteger} instance to test for division by small factors. + + @return true if the candidate is found to have any small factors, + false otherwise. + + + FIPS 186-4 C.3.1 Miller-Rabin Probabilistic Primality Test + + Run several iterations of the Miller-Rabin algorithm with randomly-chosen bases. + + @param candidate + the {@link BigInteger} instance to test for primality. + @param random + the source of randomness to use to choose bases. + @param iterations + the number of randomly-chosen bases to perform the test for. + @return false if any witness to compositeness is found amongst the chosen bases + (so candidate is definitely NOT prime), or else true + (indicating primality with some probability dependent on the number of iterations + that were performed). + + + FIPS 186-4 C.3.1 Miller-Rabin Probabilistic Primality Test (to a fixed base). + + Run a single iteration of the Miller-Rabin algorithm against the specified base. + + @param candidate + the {@link BigInteger} instance to test for primality. + @param baseValue + the base value to use for this iteration. + @return false if the specified base is a witness to compositeness (so + candidate is definitely NOT prime), or else true. + + + Used to return the output from the + {@linkplain Primes#enhancedMRProbablePrimeTest(BigInteger, SecureRandom, int) Enhanced + Miller-Rabin Probabilistic Primality Test} + + + Used to return the output from the {@linkplain Primes#generateSTRandomPrime(Digest, int, byte[]) Shawe-Taylor Random_Prime Routine} + + + + + BasicOcspResponse ::= SEQUENCE { + tbsResponseData ResponseData, + signatureAlgorithm AlgorithmIdentifier, + signature BIT STRING, + certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL + } + + + + + + Get all critical extension values, by oid + + IDictionary with string (OID) keys and Asn1OctetString values + + + + Get all non-critical extension values, by oid + + IDictionary with string (OID) keys and Asn1OctetString values + + + + Get non critical extensions. + + A set of non critical extension oids. + + + + Get any critical extensions. + + A sorted list of critical entension. + + + + Get the value of a given extension. + + The object ID of the extension. + An Asn1OctetString object if that extension is found or null if not. + + + The DER encoding of the tbsResponseData field. + In the event of an encoding error. + + + The certificates, if any, associated with the response. + In the event of an encoding error. + + + + Verify the signature against the tbsResponseData object we contain. + + + + The ASN.1 encoded representation of this object. + + + Generator for basic OCSP response objects. + + + basic constructor + + + construct with the responderID to be the SHA-1 keyHash of the passed in public key. + + + Add a response for a particular Certificate ID. + + @param certID certificate ID details + @param certStatus status of the certificate - null if okay + + + Add a response for a particular Certificate ID. + + @param certID certificate ID details + @param certStatus status of the certificate - null if okay + @param singleExtensions optional extensions + + + Add a response for a particular Certificate ID. + + @param certID certificate ID details + @param nextUpdate date when next update should be requested + @param certStatus status of the certificate - null if okay + @param singleExtensions optional extensions + + + Add a response for a particular Certificate ID. + + @param certID certificate ID details + @param thisUpdate date this response was valid on + @param nextUpdate date when next update should be requested + @param certStatus status of the certificate - null if okay + @param singleExtensions optional extensions + + + Set the extensions for the response. + + @param responseExtensions the extension object to carry. + + + + Generate the signed response using the passed in signature calculator. + + Implementation of signing calculator factory. + The certificate chain associated with the response signer. + "produced at" date. + + + + Return an IEnumerable of the signature names supported by the generator. + + @return an IEnumerable containing recognised names. + + + create from an issuer certificate and the serial number of the + certificate it signed. + @exception OcspException if any problems occur creating the id fields. + + + Create a new CertificateID for a new serial number derived from a previous one + calculated for the same CA certificate. + + @param original the previously calculated CertificateID for the CA. + @param newSerialNumber the serial number for the new certificate of interest. + + @return a new CertificateID for newSerialNumber + + + return the serial number for the certificate associated + with this request. + + +
    +             OcspRequest     ::=     SEQUENCE {
    +                   tbsRequest                  TBSRequest,
    +                   optionalSignature   [0]     EXPLICIT Signature OPTIONAL }
    +            
    +               TBSRequest      ::=     SEQUENCE {
    +                   version             [0]     EXPLICIT Version DEFAULT v1,
    +                   requestorName       [1]     EXPLICIT GeneralName OPTIONAL,
    +                   requestList                 SEQUENCE OF Request,
    +                   requestExtensions   [2]     EXPLICIT Extensions OPTIONAL }
    +            
    +               Signature       ::=     SEQUENCE {
    +                   signatureAlgorithm      AlgorithmIdentifier,
    +                   signature               BIT STRING,
    +                   certs               [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL}
    +            
    +               Version         ::=             INTEGER  {  v1(0) }
    +            
    +               Request         ::=     SEQUENCE {
    +                   reqCert                     CertID,
    +                   singleRequestExtensions     [0] EXPLICIT Extensions OPTIONAL }
    +            
    +               CertID          ::=     SEQUENCE {
    +                   hashAlgorithm       AlgorithmIdentifier,
    +                   issuerNameHash      OCTET STRING, -- Hash of Issuer's DN
    +                   issuerKeyHash       OCTET STRING, -- Hash of Issuers public key
    +                   serialNumber        CertificateSerialNumber }
    +             
    +
    + + Return the DER encoding of the tbsRequest field. + @return DER encoding of tbsRequest + @throws OcspException in the event of an encoding error. + + + If the request is signed return a possibly empty CertStore containing the certificates in the + request. If the request is not signed the method returns null. + + @return null if not signed, a CertStore otherwise + @throws OcspException + + + Verify the signature against the TBSRequest object we contain. + + + return the ASN.1 encoded representation of this object. + + + return the object identifier representing the signature algorithm + + + Return whether or not this request is signed. + + @return true if signed false otherwise. + + + Add a request for the given CertificateID. + + @param certId certificate ID of interest + + + Add a request with extensions + + @param certId certificate ID of interest + @param singleRequestExtensions the extensions to attach to the request + + + Set the requestor name to the passed in X509Principal + + @param requestorName a X509Principal representing the requestor name. + + + Generate an unsigned request + + @return the OcspReq + @throws OcspException + + + Return an IEnumerable of the signature names supported by the generator. + + @return an IEnumerable containing recognised names. + + + return the ASN.1 encoded representation of this object. + + + base generator for an OCSP response - at the moment this only supports the + generation of responses containing BasicOCSP responses. + + + note 4 is not used. + + + Carrier for a ResponderID. + + + wrapper for the RevokedInfo object + + + return the revocation reason. Note: this field is optional, test for it + with hasRevocationReason() first. + @exception InvalidOperationException if a reason is asked for and none is avaliable + + + Return the status object for the response - null indicates good. + + @return the status object for the response, null if it is good. + + + return the NextUpdate value - note: this is an optional field so may + be returned as null. + + @return nextUpdate, or null if not present. + + + wrapper for the UnknownInfo object + + + Compressed data objects + + + Get the raw input stream contained in the object. + + + Return an uncompressed input stream which allows reading of the compressed data. + + + The algorithm used for compression + + + Class for producing compressed data packets. + + + +

    + Return an output stream which will save the data being written to + the compressed object. +

    +

    + The stream created can be closed off by either calling Close() + on the stream or Close() on the generator. Closing the returned + stream does not close off the Stream parameter outStr. +

    +
    + Stream to be used for output. + A Stream for output of the compressed data. + + + +
    + + +

    + Return an output stream which will compress the data as it is written to it. + The stream will be written out in chunks according to the size of the passed in buffer. +

    +

    + The stream created can be closed off by either calling Close() + on the stream or Close() on the generator. Closing the returned + stream does not close off the Stream parameter outStr. +

    +

    + Note: if the buffer is not a power of 2 in length only the largest power of 2 + bytes worth of the buffer will be used. +

    +

    + Note: using this may break compatibility with RFC 1991 compliant tools. + Only recent OpenPGP implementations are capable of accepting these streams. +

    +
    + Stream to be used for output. + The buffer to use. + A Stream for output of the compressed data. + + + + +
    + + Close the compressed object.summary> + + + + Thrown if the IV at the start of a data stream indicates the wrong key is being used. + + + + Generic exception class for PGP encoding/decoding problems. + + + Return the raw input stream for the data stream. + + + Return true if the message is integrity protected. + True, if there is a modification detection code namespace associated + with this stream. + + + Note: This can only be called after the message has been read. + True, if the message verifies, false otherwise + + + Generator for encrypted objects. + + + Existing SecureRandom constructor. + The symmetric algorithm to use. + Source of randomness. + + + Creates a cipher stream which will have an integrity packet associated with it. + + + Base constructor. + The symmetric algorithm to use. + Source of randomness. + PGP 2.6.x compatibility required. + + + + Add a PBE encryption method to the encrypted object using the default algorithm (S2K_SHA1). + + + Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + the historical behaviour of the library (1.7 and earlier). + + + + Add a PBE encryption method to the encrypted object. + + Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + the historical behaviour of the library (1.7 and earlier). + + + + Add a PBE encryption method to the encrypted object. + + The passphrase is encoded to bytes using UTF8 (Encoding.UTF8.GetBytes). + + + + Add a PBE encryption method to the encrypted object. + + Allows the caller to handle the encoding of the passphrase to bytes. + + + + Add a public key encrypted session key to the encrypted object. + + + +

    + If buffer is non null stream assumed to be partial, otherwise the length will be used + to output a fixed length packet. +

    +

    + The stream created can be closed off by either calling Close() + on the stream or Close() on the generator. Closing the returned + stream does not close off the Stream parameter outStr. +

    +
    +
    + + +

    + Return an output stream which will encrypt the data as it is written to it. +

    +

    + The stream created can be closed off by either calling Close() + on the stream or Close() on the generator. Closing the returned + stream does not close off the Stream parameter outStr. +

    +
    +
    + + +

    + Return an output stream which will encrypt the data as it is written to it. + The stream will be written out in chunks according to the size of the passed in buffer. +

    +

    + The stream created can be closed off by either calling Close() + on the stream or Close() on the generator. Closing the returned + stream does not close off the Stream parameter outStr. +

    +

    + Note: if the buffer is not a power of 2 in length only the largest power of 2 + bytes worth of the buffer will be used. +

    +
    +
    + + +

    + Close off the encrypted object - this is equivalent to calling Close() on the stream + returned by the Open() method. +

    +

    + Note: This does not close the underlying output stream, only the stream on top of + it created by the Open() method. +

    +
    +
    + + A holder for a list of PGP encryption method packets. + + + Key flag values for the KeyFlags subpacket. + + + + General class to handle JCA key pairs and convert them into OpenPGP ones. +

    + A word for the unwary, the KeyId for an OpenPGP public key is calculated from + a hash that includes the time of creation, if you pass a different date to the + constructor below with the same public private key pair the KeyIs will not be the + same as for previous generations of the key, so ideally you only want to do + this once. +

    +
    +
    + + Create a key pair from a PgpPrivateKey and a PgpPublicKey. + The public key. + The private key. + + + The keyId associated with this key pair. + + + + Generator for a PGP master and subkey ring. + This class will generate both the secret and public key rings + + + + + Create a new key ring generator using old style checksumming. It is recommended to use + SHA1 checksumming where possible. + + + Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + the historical behaviour of the library (1.7 and earlier). + + The certification level for keys on this ring. + The master key pair. + The id to be associated with the ring. + The algorithm to be used to protect secret keys. + The passPhrase to be used to protect secret keys. + Packets to be included in the certification hash. + Packets to be attached unhashed to the certification. + input secured random. + + + + Create a new key ring generator. + + + Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + the historical behaviour of the library (1.7 and earlier). + + The certification level for keys on this ring. + The master key pair. + The id to be associated with the ring. + The algorithm to be used to protect secret keys. + The passPhrase to be used to protect secret keys. + Checksum the secret keys with SHA1 rather than the older 16 bit checksum. + Packets to be included in the certification hash. + Packets to be attached unhashed to the certification. + input secured random. + + + + Create a new key ring generator. + + The certification level for keys on this ring. + The master key pair. + The id to be associated with the ring. + The algorithm to be used to protect secret keys. + + If true, conversion of the passphrase to bytes uses Encoding.UTF8.GetBytes(), otherwise the conversion + is performed using Convert.ToByte(), which is the historical behaviour of the library (1.7 and earlier). + + The passPhrase to be used to protect secret keys. + Checksum the secret keys with SHA1 rather than the older 16 bit checksum. + Packets to be included in the certification hash. + Packets to be attached unhashed to the certification. + input secured random. + + + + Create a new key ring generator. + + The certification level for keys on this ring. + The master key pair. + The id to be associated with the ring. + The algorithm to be used to protect secret keys. + The passPhrase to be used to protect secret keys. + Checksum the secret keys with SHA1 rather than the older 16 bit checksum. + Packets to be included in the certification hash. + Packets to be attached unhashed to the certification. + input secured random. + + + + Create a new key ring generator. + + + Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + the historical behaviour of the library (1.7 and earlier). + + The certification level for keys on this ring. + The master key pair. + The id to be associated with the ring. + The algorithm to be used to protect secret keys. + The hash algorithm. + The passPhrase to be used to protect secret keys. + Checksum the secret keys with SHA1 rather than the older 16 bit checksum. + Packets to be included in the certification hash. + Packets to be attached unhashed to the certification. + input secured random. + + + + Create a new key ring generator. + + The certification level for keys on this ring. + The master key pair. + The id to be associated with the ring. + The algorithm to be used to protect secret keys. + The hash algorithm. + + If true, conversion of the passphrase to bytes uses Encoding.UTF8.GetBytes(), otherwise the conversion + is performed using Convert.ToByte(), which is the historical behaviour of the library (1.7 and earlier). + + The passPhrase to be used to protect secret keys. + Checksum the secret keys with SHA1 rather than the older 16 bit checksum. + Packets to be included in the certification hash. + Packets to be attached unhashed to the certification. + input secured random. + + + + Create a new key ring generator. + + + Allows the caller to handle the encoding of the passphrase to bytes. + + The certification level for keys on this ring. + The master key pair. + The id to be associated with the ring. + The algorithm to be used to protect secret keys. + The hash algorithm. + The passPhrase to be used to protect secret keys. + Checksum the secret keys with SHA1 rather than the older 16 bit checksum. + Packets to be included in the certification hash. + Packets to be attached unhashed to the certification. + input secured random. + + + Add a subkey to the key ring to be generated with default certification. + + + + Add a subkey to the key ring to be generated with default certification. + + The key pair. + The hash algorithm. + + + + Add a subkey with specific hashed and unhashed packets associated with it and + default certification. + + Public/private key pair. + Hashed packet values to be included in certification. + Unhashed packets values to be included in certification. + + + + + Add a subkey with specific hashed and unhashed packets associated with it and + default certification. + + Public/private key pair. + Hashed packet values to be included in certification. + Unhashed packets values to be included in certification. + The hash algorithm. + exception adding subkey: + + + + Return the secret key ring. + + + Return the public key ring that corresponds to the secret key ring. + + + + Thrown if the key checksum is invalid. + + + + Class for processing literal data objects. + + + The special name indicating a "for your eyes only" packet. + + + Return the file name as an unintrepreted byte array. + + + The raw input stream for the data stream. + + + The input stream representing the data stream. + + + The format of the data stream - Binary or Text + + + The file name that's associated with the data stream. + + + The modification time for the file. + + + Class for producing literal data packets. + + + The special name indicating a "for your eyes only" packet. + + + + Generates literal data objects in the old format. + This is important if you need compatibility with PGP 2.6.x. + + If true, uses old format. + + + +

    + Open a literal data packet, returning a stream to store the data inside the packet. +

    +

    + The stream created can be closed off by either calling Close() + on the stream or Close() on the generator. Closing the returned + stream does not close off the Stream parameter outStr. +

    +
    + The stream we want the packet in. + The format we are using. + The name of the 'file'. + The length of the data we will write. + The time of last modification we want stored. +
    + + +

    + Open a literal data packet, returning a stream to store the data inside the packet, + as an indefinite length stream. The stream is written out as a series of partial + packets with a chunk size determined by the size of the passed in buffer. +

    +

    + The stream created can be closed off by either calling Close() + on the stream or Close() on the generator. Closing the returned + stream does not close off the Stream parameter outStr. +

    +

    + Note: if the buffer is not a power of 2 in length only the largest power of 2 + bytes worth of the buffer will be used.

    +
    + The stream we want the packet in. + The format we are using. + The name of the 'file'. + The time of last modification we want stored. + The buffer to use for collecting data to put into chunks. +
    + + +

    + Open a literal data packet for the passed in FileInfo object, returning + an output stream for saving the file contents. +

    +

    + The stream created can be closed off by either calling Close() + on the stream or Close() on the generator. Closing the returned + stream does not close off the Stream parameter outStr. +

    +
    + The stream we want the packet in. + The format we are using. + The FileInfo object containg the packet details. +
    + + + Close the literal data packet - this is equivalent to calling Close() + on the stream returned by the Open() method. + + + + + A PGP marker packet - in general these should be ignored other than where + the idea is to preserve the original input stream. + + + + + General class for reading a PGP object stream. +

    + Note: if this class finds a PgpPublicKey or a PgpSecretKey it + will create a PgpPublicKeyRing, or a PgpSecretKeyRing for each + key found. If all you are trying to do is read a key ring file use + either PgpPublicKeyRingBundle or PgpSecretKeyRingBundle.

    +
    +
    + + Return the next object in the stream, or null if the end is reached. + On a parse error + + + + Return all available objects in a list. + + An IList containing all objects from this factory, in order. + + + A one pass signature object. + + + Initialise the signature object for verification. + + + Verify the calculated signature against the passed in PgpSignature. + + + Holder for a list of PgpOnePassSignature objects. + + + Padding functions. + + + A password based encryption object. + + + Return the raw input stream for the data stream. + + + Return the decrypted input stream, using the passed in passphrase. + + Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + the historical behaviour of the library (1.7 and earlier). + + + + Return the decrypted input stream, using the passed in passphrase. + + The passphrase is encoded to bytes using UTF8 (Encoding.UTF8.GetBytes). + + + + Return the decrypted input stream, using the passed in passphrase. + + Allows the caller to handle the encoding of the passphrase to bytes. + + + + General class to contain a private key for use with other OpenPGP objects. + + + + Create a PgpPrivateKey from a keyID, the associated public data packet, and a regular private key. + + ID of the corresponding public key. + the public key data packet to be associated with this private key. + the private key data packet to be associated with this private key. + + + The keyId associated with the contained private key. + + + The public key packet associated with this private key, if available. + + + The contained private key. + + + General class to handle a PGP public key object. + + + + Create a PgpPublicKey from the passed in lightweight one. + + + Note: the time passed in affects the value of the key's keyId, so you probably only want + to do this once for a lightweight key, or make sure you keep track of the time you used. + + Asymmetric algorithm type representing the public key. + Actual public key to associate. + Date of creation. + If pubKey is not public. + On key creation problem. + + + Constructor for a sub-key. + + + Copy constructor. + The public key to copy. + + + Return the trust data associated with the public key, if present. + A byte array with trust data, null otherwise. + + + The number of valid seconds from creation time - zero means no expiry. + + + The fingerprint of the key + + + The public key contained in the object. + A lightweight public key. + If the key algorithm is not recognised. + + + Allows enumeration of any user IDs associated with the key. + An IEnumerable of string objects. + + + Allows enumeration of any user attribute vectors associated with the key. + An IEnumerable of PgpUserAttributeSubpacketVector objects. + + + Allows enumeration of any signatures associated with the passed in id. + The ID to be matched. + An IEnumerable of PgpSignature objects. + + + Allows enumeration of signatures associated with the passed in user attributes. + The vector of user attributes to be matched. + An IEnumerable of PgpSignature objects. + + + Allows enumeration of signatures of the passed in type that are on this key. + The type of the signature to be returned. + An IEnumerable of PgpSignature objects. + + + Allows enumeration of all signatures/certifications associated with this key. + An IEnumerable with all signatures/certifications. + + + Return all signatures/certifications directly associated with this key (ie, not to a user id). + + @return an iterator (possibly empty) with all signatures/certifications. + + + Check whether this (sub)key has a revocation signature on it. + True, if this (sub)key has been revoked. + + + Add a certification for an id to the given public key. + The key the certification is to be added to. + The ID the certification is associated with. + The new certification. + The re-certified key. + + + Add a certification for the given UserAttributeSubpackets to the given public key. + The key the certification is to be added to. + The attributes the certification is associated with. + The new certification. + The re-certified key. + + + + Remove any certifications associated with a user attribute subpacket on a key. + + The key the certifications are to be removed from. + The attributes to be removed. + + The re-certified key, or null if the user attribute subpacket was not found on the key. + + + + Remove any certifications associated with a given ID on a key. + The key the certifications are to be removed from. + The ID that is to be removed. + The re-certified key, or null if the ID was not found on the key. + + + Remove a certification associated with a given ID on a key. + The key the certifications are to be removed from. + The ID that the certfication is to be removed from. + The certfication to be removed. + The re-certified key, or null if the certification was not found. + + + Remove a certification associated with a given user attributes on a key. + The key the certifications are to be removed from. + The user attributes that the certfication is to be removed from. + The certification to be removed. + The re-certified key, or null if the certification was not found. + + + Add a revocation or some other key certification to a key. + The key the revocation is to be added to. + The key signature to be added. + The new changed public key object. + + + Remove a certification from the key. + The key the certifications are to be removed from. + The certfication to be removed. + The modified key, null if the certification was not found. + + + The version of this key. + + + The creation time of this key. + + + The number of valid days from creation time - zero means no expiry. + WARNING: This method will return 1 for keys with version > 3 that expire in less than 1 day + + + The keyId associated with the public key. + + + + Check if this key has an algorithm type that makes it suitable to use for encryption. + + + Note: with version 4 keys KeyFlags subpackets should also be considered when present for + determining the preferred use of the key. + + + true if this key algorithm is suitable for encryption. + + + + True, if this is a master key. + + + The algorithm code associated with the public key. + + + The strength of the key in bits. + + + A public key encrypted data object. + + + + Return the algorithm code for the symmetric algorithm used to encrypt the data. + + + + Return the decrypted data stream for the packet. + + + The key ID for the key used to encrypt the data. + + + + Class to hold a single master public key and its subkeys. +

    + Often PGP keyring files consist of multiple master keys, if you are trying to process + or construct one of these you should use the PgpPublicKeyRingBundle class. +

    +
    +
    + + Return the first public key in the ring. + + + Return the public key referred to by the passed in key ID if it is present. + + + Allows enumeration of all the public keys. + An IEnumerable of PgpPublicKey objects. + + + + Returns a new key ring with the public key passed in either added or + replacing an existing one. + + The public key ring to be modified. + The public key to be inserted. + A new PgpPublicKeyRing + + + Returns a new key ring with the public key passed in removed from the key ring. + The public key ring to be modified. + The public key to be removed. + A new PgpPublicKeyRing, or null if pubKey is not found. + + + + Often a PGP key ring file is made up of a succession of master/sub-key key rings. + If you want to read an entire public key file in one hit this is the class for you. + + + + Build a PgpPublicKeyRingBundle from the passed in input stream. + Input stream containing data. + If a problem parsing the stream occurs. + If an object is encountered which isn't a PgpPublicKeyRing. + + + Allow enumeration of the public key rings making up this collection. + + + Allow enumeration of the key rings associated with the passed in userId. + The user ID to be matched. + An IEnumerable of key rings which matched (possibly none). + + + Allow enumeration of the key rings associated with the passed in userId. + The user ID to be matched. + If true, userId need only be a substring of an actual ID string to match. + An IEnumerable of key rings which matched (possibly none). + + + Allow enumeration of the key rings associated with the passed in userId. + The user ID to be matched. + If true, userId need only be a substring of an actual ID string to match. + If true, case is ignored in user ID comparisons. + An IEnumerable of key rings which matched (possibly none). + + + Return the PGP public key associated with the given key id. + The ID of the public key to return. + + + Return the public key ring which contains the key referred to by keyId + key ID to match against + + + + Return true if a key matching the passed in key ID is present, false otherwise. + + key ID to look for. + + + + Return a new bundle containing the contents of the passed in bundle and + the passed in public key ring. + + The PgpPublicKeyRingBundle the key ring is to be added to. + The key ring to be added. + A new PgpPublicKeyRingBundle merging the current one with the passed in key ring. + If the keyId for the passed in key ring is already present. + + + + Return a new bundle containing the contents of the passed in bundle with + the passed in public key ring removed. + + The PgpPublicKeyRingBundle the key ring is to be removed from. + The key ring to be removed. + A new PgpPublicKeyRingBundle not containing the passed in key ring. + If the keyId for the passed in key ring is not present. + + + Return the number of key rings in this collection. + + + General class to handle a PGP secret key object. + + + + Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + the historical behaviour of the library (1.7 and earlier). + + + + + Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + the historical behaviour of the library (1.7 and earlier). + + + + + If utf8PassPhrase is true, conversion of the passphrase to bytes uses Encoding.UTF8.GetBytes(), otherwise the conversion + is performed using Convert.ToByte(), which is the historical behaviour of the library (1.7 and earlier). + + + + + Allows the caller to handle the encoding of the passphrase to bytes. + + + + + Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + the historical behaviour of the library (1.7 and earlier). + + + + + If utf8PassPhrase is true, conversion of the passphrase to bytes uses Encoding.UTF8.GetBytes(), otherwise the conversion + is performed using Convert.ToByte(), which is the historical behaviour of the library (1.7 and earlier). + + + + + Allows the caller to handle the encoding of the passphrase to bytes. + + + + Extract a PgpPrivateKey from this secret key's encrypted contents. + + Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + the historical behaviour of the library (1.7 and earlier). + + + + Extract a PgpPrivateKey from this secret key's encrypted contents. + + The passphrase is encoded to bytes using UTF8 (Encoding.UTF8.GetBytes). + + + + Extract a PgpPrivateKey from this secret key's encrypted contents. + + Allows the caller to handle the encoding of the passphrase to bytes. + + + + + Return a copy of the passed in secret key, encrypted using a new password + and the passed in algorithm. + + + Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + the historical behaviour of the library (1.7 and earlier). + + The PgpSecretKey to be copied. + The current password for the key. + The new password for the key. + The algorithm to be used for the encryption. + Source of randomness. + + + + Return a copy of the passed in secret key, encrypted using a new password + and the passed in algorithm. + + + The passphrase is encoded to bytes using UTF8 (Encoding.UTF8.GetBytes). + + The PgpSecretKey to be copied. + The current password for the key. + The new password for the key. + The algorithm to be used for the encryption. + Source of randomness. + + + + Return a copy of the passed in secret key, encrypted using a new password + and the passed in algorithm. + + + Allows the caller to handle the encoding of the passphrase to bytes. + + The PgpSecretKey to be copied. + The current password for the key. + The new password for the key. + The algorithm to be used for the encryption. + Source of randomness. + + + Replace the passed the public key on the passed in secret key. + Secret key to change. + New public key. + A new secret key. + If KeyId's do not match. + + + + Parse a secret key from one of the GPG S expression keys associating it with the passed in public key. + + + Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + the historical behaviour of the library (1.7 and earlier). + + + + + Parse a secret key from one of the GPG S expression keys associating it with the passed in public key. + + + The passphrase is encoded to bytes using UTF8 (Encoding.UTF8.GetBytes). + + + + + Parse a secret key from one of the GPG S expression keys associating it with the passed in public key. + + + Allows the caller to handle the encoding of the passphrase to bytes. + + + + + Parse a secret key from one of the GPG S expression keys. + + + Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + the historical behaviour of the library (1.7 and earlier). + + + + + Parse a secret key from one of the GPG S expression keys. + + + The passphrase is encoded to bytes using UTF8 (Encoding.UTF8.GetBytes). + + + + + Parse a secret key from one of the GPG S expression keys. + + + Allows the caller to handle the encoding of the passphrase to bytes. + + + + + Parse a secret key from one of the GPG S expression keys. + + + + + Check if this key has an algorithm type that makes it suitable to use for signing. + + + Note: with version 4 keys KeyFlags subpackets should also be considered when present for + determining the preferred use of the key. + + + true if this key algorithm is suitable for use with signing. + + + + True, if this is a master key. + + + Detect if the Secret Key's Private Key is empty or not + + + The algorithm the key is encrypted with. + + + The key ID of the public key associated with this key. + + + Return the S2K usage associated with this key. + + + Return the S2K used to process this key. + + + The public key associated with this key. + + + Allows enumeration of any user IDs associated with the key. + An IEnumerable of string objects. + + + Allows enumeration of any user attribute vectors associated with the key. + An IEnumerable of string objects. + + + + Class to hold a single master secret key and its subkeys. +

    + Often PGP keyring files consist of multiple master keys, if you are trying to process + or construct one of these you should use the PgpSecretKeyRingBundle class. +

    +
    +
    + + Return the public key for the master key. + + + Return the master private key. + + + Allows enumeration of the secret keys. + An IEnumerable of PgpSecretKey objects. + + + + Return an iterator of the public keys in the secret key ring that + have no matching private key. At the moment only personal certificate data + appears in this fashion. + + An IEnumerable of unattached, or extra, public keys. + + + + Replace the public key set on the secret ring with the corresponding key off the public ring. + + Secret ring to be changed. + Public ring containing the new public key set. + + + + Return a copy of the passed in secret key ring, with the master key and sub keys encrypted + using a new password and the passed in algorithm. + + The PgpSecretKeyRing to be copied. + The current password for key. + The new password for the key. + The algorithm to be used for the encryption. + Source of randomness. + + + + Returns a new key ring with the secret key passed in either added or + replacing an existing one with the same key ID. + + The secret key ring to be modified. + The secret key to be inserted. + A new PgpSecretKeyRing + + + Returns a new key ring with the secret key passed in removed from the key ring. + The secret key ring to be modified. + The secret key to be removed. + A new PgpSecretKeyRing, or null if secKey is not found. + + + + Often a PGP key ring file is made up of a succession of master/sub-key key rings. + If you want to read an entire secret key file in one hit this is the class for you. + + + + Build a PgpSecretKeyRingBundle from the passed in input stream. + Input stream containing data. + If a problem parsing the stream occurs. + If an object is encountered which isn't a PgpSecretKeyRing. + + + Allow enumeration of the secret key rings making up this collection. + + + Allow enumeration of the key rings associated with the passed in userId. + The user ID to be matched. + An IEnumerable of key rings which matched (possibly none). + + + Allow enumeration of the key rings associated with the passed in userId. + The user ID to be matched. + If true, userId need only be a substring of an actual ID string to match. + An IEnumerable of key rings which matched (possibly none). + + + Allow enumeration of the key rings associated with the passed in userId. + The user ID to be matched. + If true, userId need only be a substring of an actual ID string to match. + If true, case is ignored in user ID comparisons. + An IEnumerable of key rings which matched (possibly none). + + + Return the PGP secret key associated with the given key id. + The ID of the secret key to return. + + + Return the secret key ring which contains the key referred to by keyId + The ID of the secret key + + + + Return true if a key matching the passed in key ID is present, false otherwise. + + key ID to look for. + + + + Return a new bundle containing the contents of the passed in bundle and + the passed in secret key ring. + + The PgpSecretKeyRingBundle the key ring is to be added to. + The key ring to be added. + A new PgpSecretKeyRingBundle merging the current one with the passed in key ring. + If the keyId for the passed in key ring is already present. + + + + Return a new bundle containing the contents of the passed in bundle with + the passed in secret key ring removed. + + The PgpSecretKeyRingBundle the key ring is to be removed from. + The key ring to be removed. + A new PgpSecretKeyRingBundle not containing the passed in key ring. + If the keyId for the passed in key ring is not present. + + + Return the number of rings in this collection. + + + A PGP signature object. + + + Return true if this signature represents a certification. + + + + Verify the signature as certifying the passed in public key as associated + with the passed in user attributes. + + User attributes the key was stored under. + The key to be verified. + True, if the signature matches, false otherwise. + + + + Verify the signature as certifying the passed in public key as associated + with the passed in ID. + + ID the key was stored under. + The key to be verified. + True, if the signature matches, false otherwise. + + + Verify a certification for the passed in key against the passed in master key. + The key we are verifying against. + The key we are verifying. + True, if the certification is valid, false otherwise. + + + Verify a key certification, such as revocation, for the passed in key. + The key we are checking. + True, if the certification is valid, false otherwise. + + + + Return true if the passed in signature type represents a certification, false if the signature type is not. + + + true if signatureType is a certification, false otherwise. + + + The OpenPGP version number for this signature. + + + The key algorithm associated with this signature. + + + The hash algorithm associated with this signature. + + + The ID of the key that created the signature. + + + The creation time of this signature. + + + + Return true if the signature has either hashed or unhashed subpackets. + + + + Generator for PGP signatures. + + + Create a generator for the passed in keyAlgorithm and hashAlgorithm codes. + + + Initialise the generator for signing. + + + Initialise the generator for signing. + + + Return the one pass header associated with the current signature. + + + Return a signature object containing the current signature state. + + + Generate a certification for the passed in ID and key. + The ID we are certifying against the public key. + The key we are certifying against the ID. + The certification. + + + Generate a certification for the passed in userAttributes. + The ID we are certifying against the public key. + The key we are certifying against the ID. + The certification. + + + Generate a certification for the passed in key against the passed in master key. + The key we are certifying against. + The key we are certifying. + The certification. + + + Generate a certification, such as a revocation, for the passed in key. + The key we are certifying. + The certification. + + + A list of PGP signatures - normally in the signature block after literal data. + + + Generator for signature subpackets. + + + + Add a TrustSignature packet to the signature. The values for depth and trust are largely + installation dependent but there are some guidelines in RFC 4880 - 5.2.3.13. + + true if the packet is critical. + depth level. + trust amount. + + + + Set the number of seconds a key is valid for after the time of its creation. + A value of zero means the key never expires. + + True, if should be treated as critical, false otherwise. + The number of seconds the key is valid, or zero if no expiry. + + + + Set the number of seconds a signature is valid for after the time of its creation. + A value of zero means the signature never expires. + + True, if should be treated as critical, false otherwise. + The number of seconds the signature is valid, or zero if no expiry. + + + + Set the creation time for the signature. +

    + Note: this overrides the generation of a creation time when the signature + is generated.

    +
    +
    + + + Sets revocation reason sub packet + + + + + Sets revocation key sub packet + + + + + Sets issuer key sub packet + + + + Container for a list of signature subpackets. + + + Return true if a particular subpacket type exists. + + @param type type to look for. + @return true if present, false otherwise. + + + Return all signature subpackets of the passed in type. + @param type subpacket type code + @return an array of zero or more matching subpackets. + + + + Return the number of seconds a signature is valid for after its creation date. + A value of zero means the signature never expires. + + Seconds a signature is valid for. + + + + Return the number of seconds a key is valid for after its creation date. + A value of zero means the key never expires. + + Seconds a signature is valid for. + + + Return the number of packets this vector contains. + + + Container for a list of user attribute subpackets. + + + Basic utility class. + + + + Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + the historical behaviour of the library (1.7 and earlier). + + + + + The passphrase is encoded to bytes using UTF8 (Encoding.UTF8.GetBytes). + + + + + Allows the caller to handle the encoding of the passphrase to bytes. + + + + Write out the passed in file as a literal data packet. + + + Write out the passed in file as a literal data packet in partial packet format. + + + + Return either an ArmoredInputStream or a BcpgInputStream based on whether + the initial characters of the stream are binary PGP encodings or not. + + + + Generator for old style PGP V3 Signatures. + + + Create a generator for the passed in keyAlgorithm and hashAlgorithm codes. + + + Initialise the generator for signing. + + + Initialise the generator for signing. + + + Return the one pass header associated with the current signature. + + + Return a V3 signature object containing the current signature state. + + + Utility functions for looking a S-expression keys. This class will move when it finds a better home! +

    + Format documented here: + http://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=agent/keyformat.txt;h=42c4b1f06faf1bbe71ffadc2fee0fad6bec91a97;hb=refs/heads/master +

    +
    + + PEM generator for the original set of PEM objects used in Open SSL. + + + + A + + + + + Class for reading OpenSSL PEM encoded streams containing + X509 certificates, PKCS8 encoded keys and PKCS7 objects. +

    + In the case of PKCS7 objects the reader will return a CMS ContentInfo object. Keys and + Certificates will be returned using the appropriate java.security type.

    +
    + + + A + + + + + Create a new PemReader + + @param reader the Reader + + + Create a new PemReader with a password finder + + @param reader the Reader + @param pFinder the password finder + + + Reads in a X509Certificate. + + @return the X509Certificate + @throws IOException if an I/O error occured + + + Reads in a X509CRL. + + @return the X509Certificate + @throws IOException if an I/O error occured + + + Reads in a PKCS10 certification request. + + @return the certificate request. + @throws IOException if an I/O error occured + + + Reads in a X509 Attribute Certificate. + + @return the X509 Attribute Certificate + @throws IOException if an I/O error occured + + + Reads in a PKCS7 object. This returns a ContentInfo object suitable for use with the CMS + API. + + @return the X509Certificate + @throws IOException if an I/O error occured + + + Read a Key Pair + + + General purpose writer for OpenSSL PEM objects. + + + A generic PEM writer, based on RFC 1421 + + + Base constructor. + + @param out output stream to use. + + + Return the number of bytes or characters required to contain the + passed in object if it is PEM encoded. + + @param obj pem object to be output + @return an estimate of the number of bytes + + + The TextWriter object to write the output to. + + + Constructor for an unencrypted private key PEM object. + + @param key private key to be encoded. + + + Constructor for an encrypted private key PEM object. + + @param key private key to be encoded + @param algorithm encryption algorithm to use + @param provider provider to use + @throws NoSuchAlgorithmException if algorithm/mode cannot be found + + + + A class for verifying and creating Pkcs10 Certification requests. + + + CertificationRequest ::= Sequence { + certificationRequestInfo CertificationRequestInfo, + signatureAlgorithm AlgorithmIdentifier{{ SignatureAlgorithms }}, + signature BIT STRING + } + + CertificationRequestInfo ::= Sequence { + version Integer { v1(0) } (v1,...), + subject Name, + subjectPKInfo SubjectPublicKeyInfo{{ PKInfoAlgorithms }}, + attributes [0] Attributes{{ CRIAttributes }} + } + + Attributes { ATTRIBUTE:IOSet } ::= Set OF Attr{{ IOSet }} + + Attr { ATTRIBUTE:IOSet } ::= Sequence { + type ATTRIBUTE.&id({IOSet}), + values Set SIZE(1..MAX) OF ATTRIBUTE.&Type({IOSet}{\@type}) + } + + see + + + + Instantiate a Pkcs10CertificationRequest object with the necessary credentials. + + Name of Sig Alg. + X509Name of subject eg OU="My unit." O="My Organisatioin" C="au" + Public Key to be included in cert reqest. + ASN1Set of Attributes. + Matching Private key for nominated (above) public key to be used to sign the request. + + + + Instantiate a Pkcs10CertificationRequest object with the necessary credentials. + + The factory for signature calculators to sign the PKCS#10 request with. + X509Name of subject eg OU="My unit." O="My Organisatioin" C="au" + Public Key to be included in cert reqest. + ASN1Set of Attributes. + Matching Private key for nominated (above) public key to be used to sign the request. + + + + Get the public key. + + The public key. + + + + Verify Pkcs10 Cert Request is valid. + + true = valid. + + + + A class for creating and verifying Pkcs10 Certification requests (this is an extension on ). + The requests are made using delay signing. This is useful for situations where + the private key is in another environment and not directly accessible (e.g. HSM) + So the first step creates the request, then the signing is done outside this + object and the signature is then used to complete the request. + + + CertificationRequest ::= Sequence { + certificationRequestInfo CertificationRequestInfo, + signatureAlgorithm AlgorithmIdentifier{{ SignatureAlgorithms }}, + signature BIT STRING + } + + CertificationRequestInfo ::= Sequence { + version Integer { v1(0) } (v1,...), + subject Name, + subjectPKInfo SubjectPublicKeyInfo{{ PKInfoAlgorithms }}, + attributes [0] Attributes{{ CRIAttributes }} + } + + Attributes { ATTRIBUTE:IOSet } ::= Set OF Attr{{ IOSet }} + + Attr { ATTRIBUTE:IOSet } ::= Sequence { + type ATTRIBUTE.&id({IOSet}), + values Set SIZE(1..MAX) OF ATTRIBUTE.&Type({IOSet}{\@type}) + } + + see + + + + Instantiate a Pkcs10CertificationRequest object with the necessary credentials. + + Name of Sig Alg. + X509Name of subject eg OU="My unit." O="My Organisatioin" C="au" + Public Key to be included in cert reqest. + ASN1Set of Attributes. + + After the object is constructed use the and finally the + SignRequest methods to finalize the request. + + + + simply return the cert entry for the private key + + + Utility class for reencoding PKCS#12 files to definite length. + + + Just re-encode the outer layer of the PKCS#12 file to definite length encoding. + + @param berPKCS12File - original PKCS#12 file + @return a byte array representing the DER encoding of the PFX structure + @throws IOException + + + Re-encode the PKCS#12 structure to definite length encoding at the inner layer + as well, recomputing the MAC accordingly. + + @param berPKCS12File - original PKCS12 file. + @param provider - provider to use for MAC calculation. + @return a byte array representing the DER encoding of the PFX structure. + @throws IOException on parsing, encoding errors. + + + + Returns the revocationDate. + + + + + Returns the certStatus. + + + + Returns an immutable Set of X.509 attribute certificate + extensions that this PkixAttrCertChecker supports or + null if no extensions are supported. +

    + Each element of the set is a String representing the + Object Identifier (OID) of the X.509 extension that is supported. +

    +

    + All X.509 attribute certificate extensions that a + PkixAttrCertChecker might possibly be able to process + should be included in the set. +

    + + @return an immutable Set of X.509 extension OIDs (in + String format) supported by this + PkixAttrCertChecker, or null if no + extensions are supported +
    + + Performs checks on the specified attribute certificate. Every handled + extension is rmeoved from the unresolvedCritExts + collection. + + @param attrCert The attribute certificate to be checked. + @param certPath The certificate path which belongs to the attribute + certificate issuer public key certificate. + @param holderCertPath The certificate path which belongs to the holder + certificate. + @param unresolvedCritExts a Collection of OID strings + representing the current set of unresolved critical extensions + @throws CertPathValidatorException if the specified attribute certificate + does not pass the check. + + + Returns a clone of this object. + + @return a copy of this PkixAttrCertChecker + + + Build and validate a CertPath using the given parameter. + + @param params PKIXBuilderParameters object containing all information to + build the CertPath + + + CertPathValidatorSpi implementation for X.509 Attribute Certificates la RFC 3281. + + @see org.bouncycastle.x509.ExtendedPkixParameters + + + Validates an attribute certificate with the given certificate path. + +

    + params must be an instance of + ExtendedPkixParameters. +

    + The target constraints in the params must be an + X509AttrCertStoreSelector with at least the attribute + certificate criterion set. Obey that also target informations may be + necessary to correctly validate this attribute certificate. +

    + The attribute certificate issuer must be added to the trusted attribute + issuers with {@link ExtendedPkixParameters#setTrustedACIssuers(Set)}. +

    + @param certPath The certificate path which belongs to the attribute + certificate issuer public key certificate. + @param params The PKIX parameters. + @return A PKIXCertPathValidatorResult of the result of + validating the certPath. + @throws InvalidAlgorithmParameterException if params is + inappropriate for this validator. + @throws CertPathValidatorException if the verification fails. +
    + + + Summary description for PkixBuilderParameters. + + + + + Summary description for PkixParameters. + + + + This is the default PKIX validity model. Actually there are two variants + of this: The PKIX model and the modified PKIX model. The PKIX model + verifies that all involved certificates must have been valid at the + current time. The modified PKIX model verifies that all involved + certificates were valid at the signing time. Both are indirectly choosen + with the {@link PKIXParameters#setDate(java.util.Date)} method, so this + methods sets the Date when all certificates must have been + valid. + + + This model uses the following validity model. Each certificate must have + been valid at the moment where is was used. That means the end + certificate must have been valid at the time the signature was done. The + CA certificate which signed the end certificate must have been valid, + when the end certificate was signed. The CA (or Root CA) certificate must + have been valid, when the CA certificate was signed and so on. So the + {@link PKIXParameters#setDate(java.util.Date)} method sets the time, when + the end certificate must have been valid.

    It is used e.g. + in the German signature law. + + + Creates an instance of PKIXParameters with the specified Set of + most-trusted CAs. Each element of the set is a TrustAnchor.
    +
    + Note that the Set is copied to protect against subsequent modifications. + + @param trustAnchors + a Set of TrustAnchors + + @exception InvalidAlgorithmParameterException + if the specified Set is empty + (trustAnchors.isEmpty() == true) + @exception NullPointerException + if the specified Set is null + @exception ClassCastException + if any of the elements in the Set are not of type + java.security.cert.TrustAnchor +
    + + Returns the required constraints on the target certificate. The + constraints are returned as an instance of CertSelector. If + null, no constraints are defined.
    +
    + Note that the CertSelector returned is cloned to protect against + subsequent modifications. + + @return a CertSelector specifying the constraints on the target + certificate (or null) + + @see #setTargetCertConstraints(CertSelector) +
    + + Sets the required constraints on the target certificate. The constraints + are specified as an instance of CertSelector. If null, no constraints are + defined.
    +
    + Note that the CertSelector specified is cloned to protect against + subsequent modifications. + + @param selector + a CertSelector specifying the constraints on the target + certificate (or null) + + @see #getTargetCertConstraints() +
    + + Returns an immutable Set of initial policy identifiers (OID strings), + indicating that any one of these policies would be acceptable to the + certificate user for the purposes of certification path processing. The + default return value is an empty Set, which is + interpreted as meaning that any policy would be acceptable. + + @return an immutable Set of initial policy OIDs in String + format, or an empty Set (implying any policy is + acceptable). Never returns null. + + @see #setInitialPolicies(java.util.Set) + + + Sets the Set of initial policy identifiers (OID strings), + indicating that any one of these policies would be acceptable to the + certificate user for the purposes of certification path processing. By + default, any policy is acceptable (i.e. all policies), so a user that + wants to allow any policy as acceptable does not need to call this + method, or can call it with an empty Set (or + null).
    +
    + Note that the Set is copied to protect against subsequent modifications.
    +
    + + @param initialPolicies + a Set of initial policy OIDs in String format (or + null) + + @exception ClassCastException + if any of the elements in the set are not of type String + + @see #getInitialPolicies() +
    + + Sets a List of additional certification path checkers. If + the specified List contains an object that is not a PKIXCertPathChecker, + it is ignored.
    +
    + Each PKIXCertPathChecker specified implements additional + checks on a certificate. Typically, these are checks to process and + verify private extensions contained in certificates. Each + PKIXCertPathChecker should be instantiated with any + initialization parameters needed to execute the check.
    +
    + This method allows sophisticated applications to extend a PKIX + CertPathValidator or CertPathBuilder. Each + of the specified PKIXCertPathCheckers will be called, in turn, by a PKIX + CertPathValidator or CertPathBuilder for + each certificate processed or validated.
    +
    + Regardless of whether these additional PKIXCertPathCheckers are set, a + PKIX CertPathValidator or CertPathBuilder + must perform all of the required PKIX checks on each certificate. The one + exception to this rule is if the RevocationEnabled flag is set to false + (see the {@link #setRevocationEnabled(boolean) setRevocationEnabled} + method).
    +
    + Note that the List supplied here is copied and each PKIXCertPathChecker + in the list is cloned to protect against subsequent modifications. + + @param checkers + a List of PKIXCertPathCheckers. May be null, in which case no + additional checkers will be used. + @exception ClassCastException + if any of the elements in the list are not of type + java.security.cert.PKIXCertPathChecker + @see #getCertPathCheckers() +
    + + Returns the List of certification path checkers. Each PKIXCertPathChecker + in the returned IList is cloned to protect against subsequent modifications. + + @return an immutable List of PKIXCertPathCheckers (may be empty, but not + null) + + @see #setCertPathCheckers(java.util.List) + + + Adds a PKIXCertPathChecker to the list of certification + path checkers. See the {@link #setCertPathCheckers setCertPathCheckers} + method for more details. +

    + Note that the PKIXCertPathChecker is cloned to protect + against subsequent modifications.

    + + @param checker a PKIXCertPathChecker to add to the list of + checks. If null, the checker is ignored (not added to list). +
    + + Method to support Clone() under J2ME. + super.Clone() does not exist and fields are not copied. + + @param params Parameters to set. If this are + ExtendedPkixParameters they are copied to. + + + Sets the Bouncy Castle Stores for finding CRLs, certificates, attribute + certificates or cross certificates. +

    + The IList is cloned. +

    + + @param stores A list of stores to use. + @see #getStores + @throws ClassCastException if an element of stores is not + a {@link Store}. +
    + + Adds a Bouncy Castle {@link Store} to find CRLs, certificates, attribute + certificates or cross certificates. +

    + This method should be used to add local stores, like collection based + X.509 stores, if available. Local stores should be considered first, + before trying to use additional (remote) locations, because they do not + need possible additional network traffic. +

    + If store is null it is ignored. +

    + + @param store The store to add. + @see #getStores +
    + + Adds an additional Bouncy Castle {@link Store} to find CRLs, certificates, + attribute certificates or cross certificates. +

    + You should not use this method. This method is used for adding additional + X.509 stores, which are used to add (remote) locations, e.g. LDAP, found + during X.509 object processing, e.g. in certificates or CRLs. This method + is used in PKIX certification path processing. +

    + If store is null it is ignored. +

    + + @param store The store to add. + @see #getStores() +
    + + Returns an IList of additional Bouncy Castle + Stores used for finding CRLs, certificates, attribute + certificates or cross certificates. + + @return an immutable IList of additional Bouncy Castle + Stores. Never null. + + @see #addAddionalStore(Store) + + + Returns an IList of Bouncy Castle + Stores used for finding CRLs, certificates, attribute + certificates or cross certificates. + + @return an immutable IList of Bouncy Castle + Stores. Never null. + + @see #setStores(IList) + + + Sets if additional {@link X509Store}s for locations like LDAP found in + certificates or CRLs should be used. + + @param enabled true if additional stores are used. + + + Returns the required constraints on the target certificate or attribute + certificate. The constraints are returned as an instance of + IX509Selector. If null, no constraints are + defined. + +

    + The target certificate in a PKIX path may be a certificate or an + attribute certificate. +

    + Note that the IX509Selector returned is cloned to protect + against subsequent modifications. +

    + @return a IX509Selector specifying the constraints on the + target certificate or attribute certificate (or null) + @see #setTargetConstraints + @see X509CertStoreSelector + @see X509AttributeCertStoreSelector +
    + + Sets the required constraints on the target certificate or attribute + certificate. The constraints are specified as an instance of + IX509Selector. If null, no constraints are + defined. +

    + The target certificate in a PKIX path may be a certificate or an + attribute certificate. +

    + Note that the IX509Selector specified is cloned to protect + against subsequent modifications. +

    + + @param selector a IX509Selector specifying the constraints on + the target certificate or attribute certificate (or + null) + @see #getTargetConstraints + @see X509CertStoreSelector + @see X509AttributeCertStoreSelector +
    + + Returns the trusted attribute certificate issuers. If attribute + certificates is verified the trusted AC issuers must be set. +

    + The returned ISet consists of TrustAnchors. +

    + The returned ISet is immutable. Never null +

    + + @return Returns an immutable set of the trusted AC issuers. +
    + + Sets the trusted attribute certificate issuers. If attribute certificates + is verified the trusted AC issuers must be set. +

    + The trustedACIssuers must be a ISet of + TrustAnchor +

    + The given set is cloned. +

    + + @param trustedACIssuers The trusted AC issuers to set. Is never + null. + @throws ClassCastException if an element of stores is not + a TrustAnchor. +
    + + Returns the necessary attributes which must be contained in an attribute + certificate. +

    + The returned ISet is immutable and contains + Strings with the OIDs. +

    + + @return Returns the necessary AC attributes. +
    + + Sets the necessary which must be contained in an attribute certificate. +

    + The ISet must contain Strings with the + OIDs. +

    + The set is cloned. +

    + + @param necessaryACAttributes The necessary AC attributes to set. + @throws ClassCastException if an element of + necessaryACAttributes is not a + String. +
    + + Returns the attribute certificates which are not allowed. +

    + The returned ISet is immutable and contains + Strings with the OIDs. +

    + + @return Returns the prohibited AC attributes. Is never null. +
    + + Sets the attribute certificates which are not allowed. +

    + The ISet must contain Strings with the + OIDs. +

    + The set is cloned. +

    + + @param prohibitedACAttributes The prohibited AC attributes to set. + @throws ClassCastException if an element of + prohibitedACAttributes is not a + String. +
    + + Returns the attribute certificate checker. The returned set contains + {@link PKIXAttrCertChecker}s and is immutable. + + @return Returns the attribute certificate checker. Is never + null. + + + Sets the attribute certificate checkers. +

    + All elements in the ISet must a {@link PKIXAttrCertChecker}. +

    +

    + The given set is cloned. +

    + + @param attrCertCheckers The attribute certificate checkers to set. Is + never null. + @throws ClassCastException if an element of attrCertCheckers + is not a PKIXAttrCertChecker. +
    + + Whether delta CRLs should be used for checking the revocation status. + Defaults to false. + + + The validity model. + @see #CHAIN_VALIDITY_MODEL + @see #PKIX_VALIDITY_MODEL + + + Returns if additional {@link X509Store}s for locations like LDAP found + in certificates or CRLs should be used. + + @return Returns true if additional stores are used. + + + Returns an instance of PkixBuilderParameters. +

    + This method can be used to get a copy from other + PKIXBuilderParameters, PKIXParameters, + and ExtendedPKIXParameters instances. +

    + + @param pkixParams The PKIX parameters to create a copy of. + @return An PkixBuilderParameters instance. +
    + + + Excluded certificates are not used for building a certification path. + + the excluded certificates. + + + + Sets the excluded certificates which are not used for building a + certification path. If the ISet is null an + empty set is assumed. + + + The given set is cloned to protect it against subsequent modifications. + + The excluded certificates to set. + + + Can alse handle ExtendedPKIXBuilderParameters and + PKIXBuilderParameters. + + @param params Parameters to set. + @see org.bouncycastle.x509.ExtendedPKIXParameters#setParams(java.security.cert.PKIXParameters) + + + Makes a copy of this PKIXParameters object. Changes to the + copy will not affect the original and vice versa. + + @return a copy of this PKIXParameters object + + + An immutable sequence of certificates (a certification path).
    +
    + This is an abstract class that defines the methods common to all CertPaths. + Subclasses can handle different kinds of certificates (X.509, PGP, etc.).
    +
    + All CertPath objects have a type, a list of Certificates, and one or more + supported encodings. Because the CertPath class is immutable, a CertPath + cannot change in any externally visible way after being constructed. This + stipulation applies to all public fields and methods of this class and any + added or overridden by subclasses.
    +
    + The type is a string that identifies the type of Certificates in the + certification path. For each certificate cert in a certification path + certPath, cert.getType().equals(certPath.getType()) must be true.
    +
    + The list of Certificates is an ordered List of zero or more Certificates. + This List and all of the Certificates contained in it must be immutable.
    +
    + Each CertPath object must support one or more encodings so that the object + can be translated into a byte array for storage or transmission to other + parties. Preferably, these encodings should be well-documented standards + (such as PKCS#7). One of the encodings supported by a CertPath is considered + the default encoding. This encoding is used if no encoding is explicitly + requested (for the {@link #getEncoded()} method, for instance).
    +
    + All CertPath objects are also Serializable. CertPath objects are resolved + into an alternate {@link CertPathRep} object during serialization. This + allows a CertPath object to be serialized into an equivalent representation + regardless of its underlying implementation.
    +
    + CertPath objects can be created with a CertificateFactory or they can be + returned by other classes, such as a CertPathBuilder.
    +
    + By convention, X.509 CertPaths (consisting of X509Certificates), are ordered + starting with the target certificate and ending with a certificate issued by + the trust anchor. That is, the issuer of one certificate is the subject of + the following one. The certificate representing the + {@link TrustAnchor TrustAnchor} should not be included in the certification + path. Unvalidated X.509 CertPaths may not follow these conventions. PKIX + CertPathValidators will detect any departure from these conventions that + cause the certification path to be invalid and throw a + CertPathValidatorException.
    +
    + Concurrent Access
    +
    + All CertPath objects must be thread-safe. That is, multiple threads may + concurrently invoke the methods defined in this class on a single CertPath + object (or more than one) with no ill effects. This is also true for the List + returned by CertPath.getCertificates.
    +
    + Requiring CertPath objects to be immutable and thread-safe allows them to be + passed around to various pieces of code without worrying about coordinating + access. Providing this thread-safety is generally not difficult, since the + CertPath and List objects in question are immutable. + + @see CertificateFactory + @see CertPathBuilder + + CertPath implementation for X.509 certificates. + +
    + + @param certs + + + Creates a CertPath of the specified type. + This constructor is protected because most users should use + a CertificateFactory to create CertPaths. + @param type the standard name of the type of Certificatesin this path + + + + Creates a CertPath of the specified type. + This constructor is protected because most users should use + a CertificateFactory to create CertPaths. + + @param type the standard name of the type of Certificatesin this path + + + + Compares this certification path for equality with the specified object. + Two CertPaths are equal if and only if their types are equal and their + certificate Lists (and by implication the Certificates in those Lists) + are equal. A CertPath is never equal to an object that is not a CertPath.
    +
    + This algorithm is implemented by this method. If it is overridden, the + behavior specified here must be maintained. + + @param other + the object to test for equality with this certification path + + @return true if the specified object is equal to this certification path, + false otherwise + + @see Object#hashCode() Object.hashCode() +
    + + Returns the encoded form of this certification path, using + the default encoding. + + @return the encoded bytes + @exception CertificateEncodingException if an encoding error occurs + + + + Returns the encoded form of this certification path, using + the specified encoding. + + @param encoding the name of the encoding to use + @return the encoded bytes + @exception CertificateEncodingException if an encoding error + occurs or the encoding requested is not supported + + + + Return a DERObject containing the encoded certificate. + + @param cert the X509Certificate object to be encoded + + @return the DERObject + + + + Returns an iteration of the encodings supported by this + certification path, with the default encoding + first. Attempts to modify the returned Iterator via its + remove method result in an UnsupportedOperationException. + + @return an Iterator over the names of the supported encodings (as Strings) + + + + + Returns the list of certificates in this certification + path. + + + + Implements the PKIX CertPathBuilding algorithm for BouncyCastle. + + @see CertPathBuilderSpi + + + Build and validate a CertPath using the given parameter. + + @param params PKIXBuilderParameters object containing all information to + build the CertPath + + + + Summary description for PkixCertPathBuilderException. + + + + + Summary description for PkixCertPathBuilderResult. + + + + + Summary description for PkixCertPathValidatorResult. + + + + * Initializes the internal state of this PKIXCertPathChecker. + *

    + * The forward flag specifies the order that certificates + * will be passed to the {@link #check check} method (forward or reverse). A + * PKIXCertPathChecker must support reverse checking + * and may support forward checking. + *

    + * + * @param forward + * the order that certificates are presented to the + * check method. If true, + * certificates are presented from target to most-trusted CA + * (forward); if false, from most-trusted CA to + * target (reverse). + * @exception CertPathValidatorException + * if this PKIXCertPathChecker is unable to + * check certificates in the specified order; it should never + * be thrown if the forward flag is false since reverse + * checking must be supported +
    + + Indicates if forward checking is supported. Forward checking refers to + the ability of the PKIXCertPathChecker to perform its + checks when certificates are presented to the check method + in the forward direction (from target to most-trusted CA). + + @return true if forward checking is supported, + false otherwise + + + * Returns an immutable Set of X.509 certificate extensions + * that this PKIXCertPathChecker supports (i.e. recognizes, + * is able to process), or null if no extensions are + * supported. + *

    + * Each element of the set is a String representing the + * Object Identifier (OID) of the X.509 extension that is supported. The OID + * is represented by a set of nonnegative integers separated by periods. + *

    + * All X.509 certificate extensions that a PKIXCertPathChecker + * might possibly be able to process should be included in the set. + *

    + * + * @return an immutable Set of X.509 extension OIDs (in + * String format) supported by this + * PKIXCertPathChecker, or null if no + * extensions are supported +
    + + Performs the check(s) on the specified certificate using its internal + state and removes any critical extensions that it processes from the + specified collection of OID strings that represent the unresolved + critical extensions. The certificates are presented in the order + specified by the init method. + + @param cert + the Certificate to be checked + @param unresolvedCritExts + a Collection of OID strings representing the + current set of unresolved critical extensions + @exception CertPathValidatorException + if the specified certificate does not pass the check + + + Returns a clone of this object. Calls the Object.clone() + method. All subclasses which maintain state must support and override + this method, if necessary. + + @return a copy of this PKIXCertPathChecker + + + The Service Provider Interface (SPI) + for the {@link CertPathValidator CertPathValidator} class. All + CertPathValidator implementations must include a class (the + SPI class) that extends this class (CertPathValidatorSpi) + and implements all of its methods. In general, instances of this class + should only be accessed through the CertPathValidator class. + For details, see the Java Cryptography Architecture.
    +
    + Concurrent Access
    +
    + Instances of this class need not be protected against concurrent + access from multiple threads. Threads that need to access a single + CertPathValidatorSpi instance concurrently should synchronize + amongst themselves and provide the necessary locking before calling the + wrapping CertPathValidator object.
    +
    + However, implementations of CertPathValidatorSpi may still + encounter concurrency issues, since multiple threads each + manipulating a different CertPathValidatorSpi instance need not + synchronize. + + CertPathValidatorSpi implementation for X.509 Certificate validation a la RFC + 3280. + +
    + + An exception indicating one of a variety of problems encountered when + validating a certification path.
    +
    + A CertPathValidatorException provides support for wrapping + exceptions. The {@link #getCause getCause} method returns the throwable, + if any, that caused this exception to be thrown.
    +
    + A CertPathValidatorException may also include the + certification path that was being validated when the exception was thrown + and the index of the certificate in the certification path that caused the + exception to be thrown. Use the {@link #getCertPath getCertPath} and + {@link #getIndex getIndex} methods to retrieve this information.
    +
    + Concurrent Access
    +
    + Unless otherwise specified, the methods defined in this class are not + thread-safe. Multiple threads that need to access a single + object concurrently should synchronize amongst themselves and + provide the necessary locking. Multiple threads each manipulating + separate objects need not synchronize. + + @see CertPathValidator + +
    + + + Creates a PkixCertPathValidatorException with the given detail + message. A detail message is a String that describes this + particular exception. + + the detail message + + + + Creates a PkixCertPathValidatorException with the specified + detail message and cause. + + the detail message + the cause (which is saved for later retrieval by the + {@link #getCause getCause()} method). (A null + value is permitted, and indicates that the cause is + nonexistent or unknown.) + + + + Creates a PkixCertPathValidatorException with the specified + detail message, cause, certification path, and index. + + the detail message (or null if none) + the cause (or null if none) + the certification path that was in the process of being + validated when the error was encountered + the index of the certificate in the certification path that * + + + + Returns the detail message for this CertPathValidatorException. + + the detail message, or null if neither the message nor cause were specified + + + Returns the certification path that was being validated when the + exception was thrown. + + @return the CertPath that was being validated when the + exception was thrown (or null if not specified) + + + Returns the index of the certificate in the certification path that + caused the exception to be thrown. Note that the list of certificates in + a CertPath is zero based. If no index has been set, -1 is + returned. + + @return the index that has been set, or -1 if none has been set + + + + Summary description for PkixCertPathValidatorUtilities. + + + + + key usage bits + + + + + Search the given Set of TrustAnchor's for one that is the + issuer of the given X509 certificate. + + the X509 certificate + a Set of TrustAnchor's + the TrustAnchor object if found or + null if not. + + @exception + + + + Returns the issuer of an attribute certificate or certificate. + + The attribute certificate or certificate. + The issuer as X500Principal. + + + Return the next working key inheriting DSA parameters if necessary. +

    + This methods inherits DSA parameters from the indexed certificate or + previous certificates in the certificate chain to the returned + PublicKey. The list is searched upwards, meaning the end + certificate is at position 0 and previous certificates are following. +

    +

    + If the indexed certificate does not contain a DSA key this method simply + returns the public key. If the DSA key already contains DSA parameters + the key is also only returned. +

    + + @param certs The certification path. + @param index The index of the certificate which contains the public key + which should be extended with DSA parameters. + @return The public key of the certificate in list position + index extended with DSA parameters if applicable. + @throws Exception if DSA parameters cannot be inherited. +
    + + + Return a Collection of all certificates or attribute certificates found + in the X509Store's that are matching the certSelect criteriums. + + a {@link Selector} object that will be used to select + the certificates + a List containing only X509Store objects. These + are used to search for certificates. + a Collection of all found or + objects. + May be empty but never null. + + + + Add the CRL issuers from the cRLIssuer field of the distribution point or + from the certificate if not given to the issuer criterion of the + selector. +

    + The issuerPrincipals are a collection with a single + X500Principal for X509Certificates. For + {@link X509AttributeCertificate}s the issuer may contain more than one + X500Principal. +

    + + @param dp The distribution point. + @param issuerPrincipals The issuers of the certificate or attribute + certificate which contains the distribution point. + @param selector The CRL selector. + @param pkixParams The PKIX parameters containing the cert stores. + @throws Exception if an exception occurs while processing. + @throws ClassCastException if issuerPrincipals does not + contain only X500Principals. +
    + + Fetches complete CRLs according to RFC 3280. + + @param dp The distribution point for which the complete CRL + @param cert The X509Certificate or + {@link org.bouncycastle.x509.X509AttributeCertificate} for + which the CRL should be searched. + @param currentDate The date for which the delta CRLs must be valid. + @param paramsPKIX The extended PKIX parameters. + @return A Set of X509CRLs with complete + CRLs. + @throws Exception if an exception occurs while picking the CRLs + or no CRLs are found. + + + Fetches delta CRLs according to RFC 3280 section 5.2.4. + + @param currentDate The date for which the delta CRLs must be valid. + @param paramsPKIX The extended PKIX parameters. + @param completeCRL The complete CRL the delta CRL is for. + @return A Set of X509CRLs with delta CRLs. + @throws Exception if an exception occurs while picking the delta + CRLs. + + + Find the issuer certificates of a given certificate. + + @param cert + The certificate for which an issuer should be found. + @param pkixParams + @return A Collection object containing the issuer + X509Certificates. Never null. + + @exception Exception + if an error occurs. + + + + Extract the value of the given extension, if it exists. + + The extension object. + The object identifier to obtain. + Asn1Object + if the extension cannot be read. + + + + crl checking + Return a Collection of all CRLs found in the X509Store's that are + matching the crlSelect criteriums. + + a {@link X509CRLStoreSelector} object that will be used + to select the CRLs + a List containing only {@link org.bouncycastle.x509.X509Store + X509Store} objects. These are used to search for CRLs + a Collection of all found {@link X509CRL X509CRL} objects. May be + empty but never null. + + + + Returns the intersection of the permitted IP ranges in + permitted with ip. + + @param permitted A Set of permitted IP addresses with + their subnet mask as byte arrays. + @param ips The IP address with its subnet mask. + @return The Set of permitted IP ranges intersected with + ip. + + + Returns the union of the excluded IP ranges in excluded + with ip. + + @param excluded A Set of excluded IP addresses with their + subnet mask as byte arrays. + @param ip The IP address with its subnet mask. + @return The Set of excluded IP ranges unified with + ip as byte arrays. + + + Calculates the union if two IP ranges. + + @param ipWithSubmask1 The first IP address with its subnet mask. + @param ipWithSubmask2 The second IP address with its subnet mask. + @return A Set with the union of both addresses. + + + Calculates the interesction if two IP ranges. + + @param ipWithSubmask1 The first IP address with its subnet mask. + @param ipWithSubmask2 The second IP address with its subnet mask. + @return A Set with the single IP address with its subnet + mask as a byte array or an empty Set. + + + Concatenates the IP address with its subnet mask. + + @param ip The IP address. + @param subnetMask Its subnet mask. + @return The concatenated IP address with its subnet mask. + + + Splits the IP addresses and their subnet mask. + + @param ipWithSubmask1 The first IP address with the subnet mask. + @param ipWithSubmask2 The second IP address with the subnet mask. + @return An array with two elements. Each element contains the IP address + and the subnet mask in this order. + + + Based on the two IP addresses and their subnet masks the IP range is + computed for each IP address - subnet mask pair and returned as the + minimum IP address and the maximum address of the range. + + @param ip1 The first IP address. + @param subnetmask1 The subnet mask of the first IP address. + @param ip2 The second IP address. + @param subnetmask2 The subnet mask of the second IP address. + @return A array with two elements. The first/second element contains the + min and max IP address of the first/second IP address and its + subnet mask. + + + Checks if the IP ip is included in the permitted ISet + permitted. + + @param permitted A Set of permitted IP addresses with + their subnet mask as byte arrays. + @param ip The IP address. + @throws PkixNameConstraintValidatorException + if the IP is not permitted. + + + Checks if the IP ip is included in the excluded ISet + excluded. + + @param excluded A Set of excluded IP addresses with their + subnet mask as byte arrays. + @param ip The IP address. + @throws PkixNameConstraintValidatorException + if the IP is excluded. + + + Checks if the IP address ip is constrained by + constraint. + + @param ip The IP address. + @param constraint The constraint. This is an IP address concatenated with + its subnetmask. + @return true if constrained, false + otherwise. + + + The common part of email1 and email2 is + added to the union union. If email1 and + email2 have nothing in common they are added both. + + @param email1 Email address constraint 1. + @param email2 Email address constraint 2. + @param union The union. + + + The most restricting part from email1 and + email2 is added to the intersection intersect. + + @param email1 Email address constraint 1. + @param email2 Email address constraint 2. + @param intersect The intersection. + + + Checks if the given GeneralName is in the permitted ISet. + + @param name The GeneralName + @throws PkixNameConstraintValidatorException + If the name + + + Check if the given GeneralName is contained in the excluded ISet. + + @param name The GeneralName. + @throws PkixNameConstraintValidatorException + If the name is + excluded. + + + Updates the permitted ISet of these name constraints with the intersection + with the given subtree. + + @param permitted The permitted subtrees + + + Adds a subtree to the excluded ISet of these name constraints. + + @param subtree A subtree with an excluded GeneralName. + + + Returns the maximum IP address. + + @param ip1 The first IP address. + @param ip2 The second IP address. + @return The maximum IP address. + + + Returns the minimum IP address. + + @param ip1 The first IP address. + @param ip2 The second IP address. + @return The minimum IP address. + + + Compares IP address ip1 with ip2. If ip1 + is equal to ip2 0 is returned. If ip1 is bigger 1 is returned, -1 + otherwise. + + @param ip1 The first IP address. + @param ip2 The second IP address. + @return 0 if ip1 is equal to ip2, 1 if ip1 is bigger, -1 otherwise. + + + Returns the logical OR of the IP addresses ip1 and + ip2. + + @param ip1 The first IP address. + @param ip2 The second IP address. + @return The OR of ip1 and ip2. + + + Stringifies an IPv4 or v6 address with subnet mask. + + @param ip The IP with subnet mask. + @return The stringified IP address. + + + + Summary description for PkixPolicyNode. + + + + Constructors + + + + This class helps to handle CRL revocation reasons mask. Each CRL handles a + certain set of revocation reasons. + + + + + Constructs are reason mask with the reasons. + + The reasons. + + + + A reason mask with no reason. + + + + + A mask with all revocation reasons. + + + + Adds all reasons from the reasons mask to this mask. + + @param mask The reasons mask to add. + + + + Intersects this mask with the given reasons mask. + + mask The mask to intersect with. + The intersection of this and teh given mask. + + + + Returns true if the passed reasons mask has new reasons. + + The reasons mask which should be tested for new reasons. + true if the passed reasons mask has new reasons. + + + + Returns true if this reasons mask contains all possible + reasons. + + true if this reasons mask contains all possible reasons. + + + + + Returns the reasons in this mask. + + + + If the complete CRL includes an issuing distribution point (IDP) CRL + extension check the following: +

    + (i) If the distribution point name is present in the IDP CRL extension + and the distribution field is present in the DP, then verify that one of + the names in the IDP matches one of the names in the DP. If the + distribution point name is present in the IDP CRL extension and the + distribution field is omitted from the DP, then verify that one of the + names in the IDP matches one of the names in the cRLIssuer field of the + DP. +

    +

    + (ii) If the onlyContainsUserCerts boolean is asserted in the IDP CRL + extension, verify that the certificate does not include the basic + constraints extension with the cA boolean asserted. +

    +

    + (iii) If the onlyContainsCACerts boolean is asserted in the IDP CRL + extension, verify that the certificate includes the basic constraints + extension with the cA boolean asserted. +

    +

    + (iv) Verify that the onlyContainsAttributeCerts boolean is not asserted. +

    + + @param dp The distribution point. + @param cert The certificate. + @param crl The CRL. + @throws AnnotatedException if one of the conditions is not met or an error occurs. +
    + + If the DP includes cRLIssuer, then verify that the issuer field in the + complete CRL matches cRLIssuer in the DP and that the complete CRL + contains an + g distribution point extension with the indirectCRL + boolean asserted. Otherwise, verify that the CRL issuer matches the + certificate issuer. + + @param dp The distribution point. + @param cert The certificate ot attribute certificate. + @param crl The CRL for cert. + @throws AnnotatedException if one of the above conditions does not apply or an error + occurs. + + + Obtain and validate the certification path for the complete CRL issuer. + If a key usage extension is present in the CRL issuer's certificate, + verify that the cRLSign bit is set. + + @param crl CRL which contains revocation information for the certificate + cert. + @param cert The attribute certificate or certificate to check if it is + revoked. + @param defaultCRLSignCert The issuer certificate of the certificate cert. + @param defaultCRLSignKey The public key of the issuer certificate + defaultCRLSignCert. + @param paramsPKIX paramsPKIX PKIX parameters. + @param certPathCerts The certificates on the certification path. + @return A Set with all keys of possible CRL issuer + certificates. + @throws AnnotatedException if the CRL is not valid or the status cannot be checked or + some error occurs. + + + Checks a distribution point for revocation information for the + certificate cert. + + @param dp The distribution point to consider. + @param paramsPKIX PKIX parameters. + @param cert Certificate to check if it is revoked. + @param validDate The date when the certificate revocation status should be + checked. + @param defaultCRLSignCert The issuer certificate of the certificate cert. + @param defaultCRLSignKey The public key of the issuer certificate + defaultCRLSignCert. + @param certStatus The current certificate revocation status. + @param reasonMask The reasons mask which is already checked. + @param certPathCerts The certificates of the certification path. + @throws AnnotatedException if the certificate is revoked or the status cannot be checked + or some error occurs. + + + Checks a certificate if it is revoked. + + @param paramsPKIX PKIX parameters. + @param cert Certificate to check if it is revoked. + @param validDate The date when the certificate revocation status should be + checked. + @param sign The issuer certificate of the certificate cert. + @param workingPublicKey The public key of the issuer certificate sign. + @param certPathCerts The certificates of the certification path. + @throws AnnotatedException if the certificate is revoked or the status cannot be checked + or some error occurs. + + + If use-deltas is set, verify the issuer and scope of the delta CRL. + + @param deltaCRL The delta CRL. + @param completeCRL The complete CRL. + @param pkixParams The PKIX paramaters. + @throws AnnotatedException if an exception occurs. + + + Checks if an attribute certificate is revoked. + + @param attrCert Attribute certificate to check if it is revoked. + @param paramsPKIX PKIX parameters. + @param issuerCert The issuer certificate of the attribute certificate + attrCert. + @param validDate The date when the certificate revocation status should + be checked. + @param certPathCerts The certificates of the certification path to be + checked. + + @throws CertPathValidatorException if the certificate is revoked or the + status cannot be checked or some error occurs. + + + Searches for a holder public key certificate and verifies its + certification path. + + @param attrCert the attribute certificate. + @param pkixParams The PKIX parameters. + @return The certificate path of the holder certificate. + @throws Exception if +
      +
    • no public key certificate can be found although holder + information is given by an entity name or a base certificate + ID
    • +
    • support classes cannot be created
    • +
    • no certification path for the public key certificate can + be built
    • +
    +
    + + + Checks a distribution point for revocation information for the + certificate attrCert. + + @param dp The distribution point to consider. + @param attrCert The attribute certificate which should be checked. + @param paramsPKIX PKIX parameters. + @param validDate The date when the certificate revocation status should + be checked. + @param issuerCert Certificate to check if it is revoked. + @param reasonMask The reasons mask which is already checked. + @param certPathCerts The certificates of the certification path to be + checked. + @throws Exception if the certificate is revoked or the status + cannot be checked or some error occurs. + + + + A trust anchor or most-trusted Certification Authority (CA). + + This class represents a "most-trusted CA", which is used as a trust anchor + for validating X.509 certification paths. A most-trusted CA includes the + public key of the CA, the CA's name, and any constraints upon the set of + paths which may be validated using this key. These parameters can be + specified in the form of a trusted X509Certificate or as individual + parameters. + + + + + Creates an instance of TrustAnchor with the specified X509Certificate and + optional name constraints, which are intended to be used as additional + constraints when validating an X.509 certification path. + The name constraints are specified as a byte array. This byte array + should contain the DER encoded form of the name constraints, as they + would appear in the NameConstraints structure defined in RFC 2459 and + X.509. The ASN.1 definition of this structure appears below. + +
    +            	NameConstraints ::= SEQUENCE {
    +            		permittedSubtrees       [0]     GeneralSubtrees OPTIONAL,
    +            		excludedSubtrees        [1]     GeneralSubtrees OPTIONAL }
    +            	   
    +             GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree
    +             
    +            		GeneralSubtree ::= SEQUENCE {
    +            		base                    GeneralName,
    +            		minimum         [0]     BaseDistance DEFAULT 0,
    +            		maximum         [1]     BaseDistance OPTIONAL }
    +            		
    +            		BaseDistance ::= INTEGER (0..MAX)
    +            
    +            		GeneralName ::= CHOICE {
    +            		otherName                       [0]     OtherName,
    +            		rfc822Name                      [1]     IA5String,
    +            		dNSName                         [2]     IA5String,
    +            		x400Address                     [3]     ORAddress,
    +            		directoryName                   [4]     Name,
    +            		ediPartyName                    [5]     EDIPartyName,
    +            		uniformResourceIdentifier       [6]     IA5String,
    +            		iPAddress                       [7]     OCTET STRING,
    +            		registeredID                    [8]     OBJECT IDENTIFIER}
    +            	
    + + Note that the name constraints byte array supplied is cloned to protect + against subsequent modifications. +
    + a trusted X509Certificate + a byte array containing the ASN.1 DER encoding of a + NameConstraints extension to be used for checking name + constraints. Only the value of the extension is included, not + the OID or criticality flag. Specify null to omit the + parameter. + if the specified X509Certificate is null +
    + + + Creates an instance of TrustAnchor where the + most-trusted CA is specified as an X500Principal and public key. + + +

    + Name constraints are an optional parameter, and are intended to be used + as additional constraints when validating an X.509 certification path. +

    + The name constraints are specified as a byte array. This byte array + contains the DER encoded form of the name constraints, as they + would appear in the NameConstraints structure defined in RFC 2459 + and X.509. The ASN.1 notation for this structure is supplied in the + documentation for the other constructors. +

    + Note that the name constraints byte array supplied here is cloned to + protect against subsequent modifications. +

    +
    + the name of the most-trusted CA as X509Name + the public key of the most-trusted CA + + a byte array containing the ASN.1 DER encoding of a NameConstraints extension to + be used for checking name constraints. Only the value of the extension is included, + not the OID or criticality flag. Specify null to omit the parameter. + + + if caPrincipal or pubKey is null + +
    + + + Creates an instance of TrustAnchor where the most-trusted + CA is specified as a distinguished name and public key. Name constraints + are an optional parameter, and are intended to be used as additional + constraints when validating an X.509 certification path. +
    + The name constraints are specified as a byte array. This byte array + contains the DER encoded form of the name constraints, as they would + appear in the NameConstraints structure defined in RFC 2459 and X.509. +
    + the X.500 distinguished name of the most-trusted CA in RFC + 2253 string format + the public key of the most-trusted CA + a byte array containing the ASN.1 DER encoding of a + NameConstraints extension to be used for checking name + constraints. Only the value of the extension is included, not + the OID or criticality flag. Specify null to omit the + parameter. + throws NullPointerException, IllegalArgumentException +
    + + + Decode the name constraints and clone them if not null. + + + + + Returns a formatted string describing the TrustAnchor. + + a formatted string describing the TrustAnchor + + + + Returns the most-trusted CA certificate. + + + + + Returns the name of the most-trusted CA as an X509Name. + + + + + Returns the name of the most-trusted CA in RFC 2253 string format. + + + + + Returns the public key of the most-trusted CA. + + + + + Utility class for creating IBasicAgreement objects from their names/Oids + + + + + Cipher Utility class contains methods that can not be specifically grouped into other classes. + + + + + Returns a ObjectIdentifier for a give encoding. + + A string representation of the encoding. + A DerObjectIdentifier, null if the Oid is not available. + + + + Utility class for creating IDigest objects from their names/Oids + + + + + Returns a ObjectIdentifier for a given digest mechanism. + + A string representation of the digest meanism. + A DerObjectIdentifier, null if the Oid is not available. + + + + A class containing methods to interface the BouncyCastle world to the .NET Crypto world. + + + + + Create an System.Security.Cryptography.X509Certificate from an X509Certificate Structure. + + + A System.Security.Cryptography.X509Certificate. + + + + Utility class for creating HMac object from their names/Oids + + + + + + + + + + Returns a ObjectIdentifier for a give encoding. + + A string representation of the encoding. + A DerObjectIdentifier, null if the Oid is not available. + + + base constructor. + + + create a SecurityUtilityException with the given message. + + @param message the message to be carried with the exception. + + + + Signer Utility class contains methods that can not be specifically grouped into other classes. + + + + + Returns an ObjectIdentifier for a given encoding. + + A string representation of the encoding. + A DerObjectIdentifier, null if the OID is not available. + + + + Utility class for creating IWrapper objects from their names/Oids + + + + Base class for an RFC 3161 Time Stamp Request. + + + Create a TimeStampRequest from the past in byte array. + + @param req byte array containing the request. + @throws IOException if the request is malformed. + + + Create a TimeStampRequest from the past in input stream. + + @param in input stream containing the request. + @throws IOException if the request is malformed. + + + Validate the timestamp request, checking the digest to see if it is of an + accepted type and whether it is of the correct length for the algorithm specified. + + @param algorithms a set of string OIDS giving accepted algorithms. + @param policies if non-null a set of policies we are willing to sign under. + @param extensions if non-null a set of extensions we are willing to accept. + @throws TspException if the request is invalid, or processing fails. + + + return the ASN.1 encoded representation of this object. + + + Generator for RFC 3161 Time Stamp Request objects. + + + add a given extension field for the standard extensions tag (tag 3) + @throws IOException + + + add a given extension field for the standard extensions tag + The value parameter becomes the contents of the octet string associated + with the extension. + + + add a given extension field for the standard extensions tag (tag 3) + @throws IOException + + + add a given extension field for the standard extensions tag + The value parameter becomes the contents of the octet string associated + with the extension. + + + Base class for an RFC 3161 Time Stamp Response object. + + + Create a TimeStampResponse from a byte array containing an ASN.1 encoding. + + @param resp the byte array containing the encoded response. + @throws TspException if the response is malformed. + @throws IOException if the byte array doesn't represent an ASN.1 encoding. + + + Create a TimeStampResponse from an input stream containing an ASN.1 encoding. + + @param input the input stream containing the encoded response. + @throws TspException if the response is malformed. + @throws IOException if the stream doesn't represent an ASN.1 encoding. + + + Check this response against to see if it a well formed response for + the passed in request. Validation will include checking the time stamp + token if the response status is GRANTED or GRANTED_WITH_MODS. + + @param request the request to be checked against + @throws TspException if the request can not match this response. + + + return the ASN.1 encoded representation of this object. + + + Generator for RFC 3161 Time Stamp Responses. + + + Return an appropriate TimeStampResponse. +

    + If genTime is null a timeNotAvailable error response will be returned. + + @param request the request this response is for. + @param serialNumber serial number for the response token. + @param genTime generation time for the response token. + @param provider provider to use for signature calculation. + @return + @throws NoSuchAlgorithmException + @throws NoSuchProviderException + @throws TSPException +

    +
    + + Generate a TimeStampResponse with chosen status and FailInfoField. + + @param status the PKIStatus to set. + @param failInfoField the FailInfoField to set. + @param statusString an optional string describing the failure. + @return a TimeStampResponse with a failInfoField and optional statusString + @throws TSPException in case the response could not be created + + + Validate the time stamp token. +

    + To be valid the token must be signed by the passed in certificate and + the certificate must be the one referred to by the SigningCertificate + attribute included in the hashed attributes of the token. The + certificate must also have the ExtendedKeyUsageExtension with only + KeyPurposeID.IdKPTimeStamping and have been valid at the time the + timestamp was created. +

    +

    + A successful call to validate means all the above are true. +

    +
    + + Return the underlying CmsSignedData object. + + @return the underlying CMS structure. + + + Return a ASN.1 encoded byte stream representing the encoded object. + + @throws IOException if encoding fails. + + + basic creation - only the default attributes will be included here. + + + create with a signer with extra signed/unsigned attributes. + + + @return the nonce value, null if there isn't one. + + + Recognised hash algorithms for the time stamp protocol. + + + Fetches the signature time-stamp attributes from a SignerInformation object. + Checks that the MessageImprint for each time-stamp matches the signature field. + (see RFC 3161 Appendix A). + + @param signerInfo a SignerInformation to search for time-stamps + @return a collection of TimeStampToken objects + @throws TSPValidationException + + + Validate the passed in certificate as being of the correct type to be used + for time stamping. To be valid it must have an ExtendedKeyUsage extension + which has a key purpose identifier of id-kp-timeStamping. + + @param cert the certificate of interest. + @throws TspValidationException if the certicate fails on one of the check points. + + + + Return the digest algorithm using one of the standard JCA string + representations rather than the algorithm identifier (if possible). + + + + Exception thrown if a TSP request or response fails to validate. +

    + If a failure code is associated with the exception it can be retrieved using + the getFailureCode() method.

    +
    + + Return the failure code associated with this exception - if one is set. + + @return the failure code if set, -1 otherwise. + + + General array utilities. + + + + Are two arrays equal. + + Left side. + Right side. + True if equal. + + + + A constant time equals comparison - does not terminate early if + test will fail. + + first array + second array + true if arrays equal, false otherwise. + + + Make a copy of a range of bytes from the passed in data array. The range can + extend beyond the end of the input array, in which case the return array will + be padded with zeroes. + + @param data the array from which the data is to be copied. + @param from the start index at which the copying should take place. + @param to the final index of the range (exclusive). + + @return a new byte array containing the range given. + + + BigInteger utilities. + + + Return the passed in value as an unsigned byte array. + + @param value value to be converted. + @return a byte array without a leading zero byte if present in the signed encoding. + + + Return the passed in value as an unsigned byte array of specified length, zero-extended as necessary. + + @param length desired length of result array. + @param n value to be converted. + @return a byte array of specified length, with leading zeroes as necessary given the size of n. + + + Return a random BigInteger not less than 'min' and not greater than 'max' + + @param min the least value that may be generated + @param max the greatest value that may be generated + @param random the source of randomness + @return a random BigInteger value in the range [min,max] + + + + Return the number of milliseconds since the Unix epoch (1 Jan., 1970 UTC) for a given DateTime value. + + A UTC DateTime value not before epoch. + Number of whole milliseconds after epoch. + 'dateTime' is before epoch. + + + + Create a DateTime value from the number of milliseconds since the Unix epoch (1 Jan., 1970 UTC). + + Number of milliseconds since the epoch. + A UTC DateTime value + + + + Return the current number of milliseconds since the Unix epoch (1 Jan., 1970 UTC). + + + + encode the input data producing a base 64 encoded byte array. + + @return a byte array containing the base 64 encoded data. + + + encode the input data producing a base 64 encoded byte array. + + @return a byte array containing the base 64 encoded data. + + + Encode the byte data to base 64 writing it to the given output stream. + + @return the number of bytes produced. + + + Encode the byte data to base 64 writing it to the given output stream. + + @return the number of bytes produced. + + + decode the base 64 encoded input data. It is assumed the input data is valid. + + @return a byte array representing the decoded data. + + + decode the base 64 encoded string data - whitespace will be ignored. + + @return a byte array representing the decoded data. + + + decode the base 64 encoded string data writing it to the given output stream, + whitespace characters will be ignored. + + @return the number of bytes produced. + + + Encode and decode byte arrays (typically from binary to 7-bit ASCII + encodings). + + + encode the input data producing a base 64 output stream. + + @return the number of bytes produced. + + + decode the base 64 encoded byte data writing it to the given output stream, + whitespace characters will be ignored. + + @return the number of bytes produced. + + + decode the base 64 encoded string data writing it to the given output stream, + whitespace characters will be ignored. + + @return the number of bytes produced. + + + + A buffering class to allow translation from one format to another to + be done in discrete chunks. + + + + + Create a buffered Decoder. + + The translater to use. + The size of the buffer. + + + + Process one byte of data. + + Data in. + Byte array for the output. + The offset in the output byte array to start writing from. + The amount of output bytes. + + + + Process data from a byte array. + + The input data. + Start position within input data array. + Amount of data to process from input data array. + Array to store output. + Position in output array to start writing from. + The amount of output bytes. + + + + A class that allows encoding of data using a specific encoder to be processed in chunks. + + + + + Create. + + The translator to use. + Size of the chunks. + + + + Process one byte of data. + + The byte. + An array to store output in. + Offset within output array to start writing from. + + + + + Process data from a byte array. + + Input data Byte array containing data to be processed. + Start position within input data array. + Amount of input data to be processed. + Output data array. + Offset within output data array to start writing to. + The amount of data written. + + + + Class to decode and encode Hex. + + + + encode the input data producing a Hex encoded byte array. + + @return a byte array containing the Hex encoded data. + + + encode the input data producing a Hex encoded byte array. + + @return a byte array containing the Hex encoded data. + + + Hex encode the byte data writing it to the given output stream. + + @return the number of bytes produced. + + + Hex encode the byte data writing it to the given output stream. + + @return the number of bytes produced. + + + decode the Hex encoded input data. It is assumed the input data is valid. + + @return a byte array representing the decoded data. + + + decode the Hex encoded string data - whitespace will be ignored. + + @return a byte array representing the decoded data. + + + decode the Hex encoded string data writing it to the given output stream, + whitespace characters will be ignored. + + @return the number of bytes produced. + + + encode the input data producing a Hex output stream. + + @return the number of bytes produced. + + + decode the Hex encoded byte data writing it to the given output stream, + whitespace characters will be ignored. + + @return the number of bytes produced. + + + decode the Hex encoded string data writing it to the given output stream, + whitespace characters will be ignored. + + @return the number of bytes produced. + + + + A hex translator. + + + + + Translator interface. + + + + + Return encoded block size. + + 2 + + + + Encode some data. + + Input data array. + Start position within input data array. + The amount of data to process. + The output data array. + The offset within the output data array to start writing from. + Amount of data encoded. + + + + Returns the decoded block size. + + 1 + + + + Decode data from a byte array. + + The input data array. + Start position within input data array. + The amounty of data to process. + The output data array. + The position within the output data array to start writing from. + The amount of data written. + + + Convert binary data to and from UrlBase64 encoding. This is identical to + Base64 encoding, except that the padding character is "." and the other + non-alphanumeric characters are "-" and "_" instead of "+" and "/". +

    + The purpose of UrlBase64 encoding is to provide a compact encoding of binary + data that is safe for use as an URL parameter. Base64 encoding does not + produce encoded values that are safe for use in URLs, since "/" can be + interpreted as a path delimiter; "+" is the encoded form of a space; and + "=" is used to separate a name from the corresponding value in an URL + parameter. +

    +
    + + Encode the input data producing a URL safe base 64 encoded byte array. + + @return a byte array containing the URL safe base 64 encoded data. + + + Encode the byte data writing it to the given output stream. + + @return the number of bytes produced. + + + Decode the URL safe base 64 encoded input data - white space will be ignored. + + @return a byte array representing the decoded data. + + + decode the URL safe base 64 encoded byte data writing it to the given output stream, + whitespace characters will be ignored. + + @return the number of bytes produced. + + + decode the URL safe base 64 encoded string data - whitespace will be ignored. + + @return a byte array representing the decoded data. + + + Decode the URL safe base 64 encoded string data writing it to the given output stream, + whitespace characters will be ignored. + + @return the number of bytes produced. + + + Convert binary data to and from UrlBase64 encoding. This is identical to + Base64 encoding, except that the padding character is "." and the other + non-alphanumeric characters are "-" and "_" instead of "+" and "/". +

    + The purpose of UrlBase64 encoding is to provide a compact encoding of binary + data that is safe for use as an URL parameter. Base64 encoding does not + produce encoded values that are safe for use in URLs, since "/" can be + interpreted as a path delimiter; "+" is the encoded form of a space; and + "=" is used to separate a name from the corresponding value in an URL + parameter. +

    +
    + + + A + + + A + + + + + + Pipe all bytes from inStr to outStr, throwing StreamFlowException if greater + than limit bytes in inStr. + + + A + + + A + + + A + + The number of bytes actually transferred, if not greater than limit + + + + Exception to be thrown on a failure to reset an object implementing Memoable. +

    + The exception extends InvalidCastException to enable users to have a single handling case, + only introducing specific handling of this one if required. +

    +
    + + Basic Constructor. + + @param msg message to be associated with this exception. + + + Validate the given IPv4 or IPv6 address. + + @param address the IP address as a string. + + @return true if a valid address, false otherwise + + + Validate the given IPv4 or IPv6 address and netmask. + + @param address the IP address as a string. + + @return true if a valid address with netmask, false otherwise + + + Validate the given IPv4 address. + + @param address the IP address as a string. + + @return true if a valid IPv4 address, false otherwise + + + Validate the given IPv6 address. + + @param address the IP address as a string. + + @return true if a valid IPv4 address, false otherwise + + + General string utilities. + + + + Summary description for DeflaterOutputStream. + + + + + Summary description for DeflaterOutputStream. + + + + + The Holder object. +
    +            Holder ::= SEQUENCE {
    +            	baseCertificateID   [0] IssuerSerial OPTIONAL,
    +            		-- the issuer and serial number of
    +            		-- the holder's Public Key Certificate
    +            	entityName          [1] GeneralNames OPTIONAL,
    +            		-- the name of the claimant or role
    +            	objectDigestInfo    [2] ObjectDigestInfo OPTIONAL
    +            		-- used to directly authenticate the holder,
    +            		-- for example, an executable
    +            }
    +            
    +
    +
    + + Constructs a holder for v2 attribute certificates with a hash value for + some type of object. +

    + digestedObjectType can be one of the following: +

      +
    • 0 - publicKey - A hash of the public key of the holder must be + passed.
    • +
    • 1 - publicKeyCert - A hash of the public key certificate of the + holder must be passed.
    • +
    • 2 - otherObjectDigest - A hash of some other object type must be + passed. otherObjectTypeID must not be empty.
    • +
    +

    +

    This cannot be used if a v1 attribute certificate is used.

    + + @param digestedObjectType The digest object type. + @param digestAlgorithm The algorithm identifier for the hash. + @param otherObjectTypeID The object type ID if + digestedObjectType is + otherObjectDigest. + @param objectDigest The hash value. +
    + + Returns the hash if an object digest info is used. + + @return The hash or null if no object digest info is set. + + + Return any principal objects inside the attribute certificate holder entity names field. + + @return an array of IPrincipal objects (usually X509Name), null if no entity names field is set. + + + Return the principals associated with the issuer attached to this holder + + @return an array of principals, null if no BaseCertificateID is set. + + + Returns the digest object type if an object digest info is used. +

    +

      +
    • 0 - publicKey - A hash of the public key of the holder must be + passed.
    • +
    • 1 - publicKeyCert - A hash of the public key certificate of the + holder must be passed.
    • +
    • 2 - otherObjectDigest - A hash of some other object type must be + passed. otherObjectTypeID must not be empty.
    • +
    +

    + + @return The digest object type or -1 if no object digest info is set. +
    + + Returns the other object type ID if an object digest info is used. + + @return The other object type ID or null if no object + digest info is set. + + + Returns the digest algorithm ID if an object digest info is used. + + @return The digest algorithm ID or null if no object + digest info is set. + + + Return the serial number associated with the issuer attached to this holder. + + @return the certificate serial number, null if no BaseCertificateID is set. + + + Carrying class for an attribute certificate issuer. + + + Set the issuer directly with the ASN.1 structure. + + @param issuer The issuer + + + Return any principal objects inside the attribute certificate issuer object. + An array of IPrincipal objects (usually X509Principal). + + + A high level authority key identifier. + + + Constructor which will take the byte[] returned from getExtensionValue() + + @param encodedValue a DER octet encoded string with the extension structure in it. + @throws IOException on parsing errors. + + + Create an AuthorityKeyIdentifier using the passed in certificate's public + key, issuer and serial number. + + @param certificate the certificate providing the information. + @throws CertificateParsingException if there is a problem processing the certificate + + + Create an AuthorityKeyIdentifier using just the hash of the + public key. + + @param pubKey the key to generate the hash from. + @throws InvalidKeyException if there is a problem using the key. + + + A high level subject key identifier. + + + Constructor which will take the byte[] returned from getExtensionValue() + + @param encodedValue a DER octet encoded string with the extension structure in it. + @throws IOException on parsing errors. + + + Interface for an X.509 Attribute Certificate. + + + Return the attributes contained in the attribute block in the certificate. + An array of attributes. + + + Return the attributes with the same type as the passed in oid. + The object identifier we wish to match. + An array of matched attributes, null if there is no match. + + + Return an ASN.1 encoded byte array representing the attribute certificate. + An ASN.1 encoded byte array. + If the certificate cannot be encoded. + + + The version number for the certificate. + + + The serial number for the certificate. + + + The UTC DateTime before which the certificate is not valid. + + + The UTC DateTime after which the certificate is not valid. + + + The holder of the certificate. + + + The issuer details for the certificate. + + + + A utility class that will extract X509Principal objects from X.509 certificates. +

    + Use this in preference to trying to recreate a principal from a string, not all + DNs are what they should be, so it's best to leave them encoded where they + can be.

    +
    +
    + + Return the issuer of the given cert as an X509Principal. + + + Return the subject of the given cert as an X509Principal. + + + Return the issuer of the given CRL as an X509Principal. + + + This class is an Selector like implementation to select + attribute certificates from a given set of criteria. + + @see org.bouncycastle.x509.X509AttributeCertificate + @see org.bouncycastle.x509.X509Store + + + + Decides if the given attribute certificate should be selected. + + The attribute certificate to be checked. + true if the object matches this selector. + + + Adds a target name criterion for the attribute certificate to the target + information extension criteria. The X509AttributeCertificate + must contain at least one of the specified target names. +

    + Each attribute certificate may contain a target information extension + limiting the servers where this attribute certificate can be used. If + this extension is not present, the attribute certificate is not targeted + and may be accepted by any server. +

    + + @param name The name as a GeneralName (not null) +
    + + Adds a target name criterion for the attribute certificate to the target + information extension criteria. The X509AttributeCertificate + must contain at least one of the specified target names. +

    + Each attribute certificate may contain a target information extension + limiting the servers where this attribute certificate can be used. If + this extension is not present, the attribute certificate is not targeted + and may be accepted by any server. +

    + + @param name a byte array containing the name in ASN.1 DER encoded form of a GeneralName + @throws IOException if a parsing error occurs. +
    + + Adds a collection with target names criteria. If null is + given any will do. +

    + The collection consists of either GeneralName objects or byte[] arrays representing + DER encoded GeneralName structures. +

    + + @param names A collection of target names. + @throws IOException if a parsing error occurs. + @see #AddTargetName(byte[]) + @see #AddTargetName(GeneralName) +
    + + Gets the target names. The collection consists of Lists + made up of an Integer in the first entry and a DER encoded + byte array or a String in the second entry. +

    The returned collection is immutable.

    + + @return The collection of target names + @see #setTargetNames(Collection) +
    + + Adds a target group criterion for the attribute certificate to the target + information extension criteria. The X509AttributeCertificate + must contain at least one of the specified target groups. +

    + Each attribute certificate may contain a target information extension + limiting the servers where this attribute certificate can be used. If + this extension is not present, the attribute certificate is not targeted + and may be accepted by any server. +

    + + @param group The group as GeneralName form (not null) +
    + + Adds a target group criterion for the attribute certificate to the target + information extension criteria. The X509AttributeCertificate + must contain at least one of the specified target groups. +

    + Each attribute certificate may contain a target information extension + limiting the servers where this attribute certificate can be used. If + this extension is not present, the attribute certificate is not targeted + and may be accepted by any server. +

    + + @param name a byte array containing the group in ASN.1 DER encoded form of a GeneralName + @throws IOException if a parsing error occurs. +
    + + Adds a collection with target groups criteria. If null is + given any will do. +

    + The collection consists of GeneralName objects or byte[] + representing DER encoded GeneralNames. +

    + + @param names A collection of target groups. + @throws IOException if a parsing error occurs. + @see #AddTargetGroup(byte[]) + @see #AddTargetGroup(GeneralName) +
    + + Gets the target groups. The collection consists of Lists + made up of an Integer in the first entry and a DER encoded + byte array or a String in the second entry. +

    The returned collection is immutable.

    + + @return The collection of target groups. + @see #setTargetGroups(Collection) +
    + + The attribute certificate which must be matched. + If null is given, any will do. + + + The criteria for validity + If null is given any will do. + + + The holder. + If null is given any will do. + + + The issuer. + If null is given any will do. + + + The serial number. + If null is given any will do. + + + + This class is an IX509Selector implementation to select + certificate pairs, which are e.g. used for cross certificates. The set of + criteria is given from two X509CertStoreSelector objects, + each of which, if present, must match the respective component of a pair. + + + + + Decides if the given certificate pair should be selected. If + obj is not a X509CertificatePair, this method + returns false. + + The X509CertificatePair to be tested. + true if the object matches this selector. + + + The certificate pair which is used for testing on equality. + + + The certificate selector for the forward part. + + + The certificate selector for the reverse part. + + + A simple collection backed store. + + + Basic constructor. + + @param collection - initial contents for the store, this is copied. + + + Return the matches in the collection for the passed in selector. + + @param selector the selector to match against. + @return a possibly empty collection of matching objects. + + + This class contains a collection for collection based X509Stores. + + + + Constructor. +

    + The collection is copied. +

    +
    + The collection containing X.509 object types. + If collection is null. +
    + + Returns a copy of the ICollection. + + + Returns a formatted string describing the parameters. + + + + An ICollection of X509Name objects + + + + The attribute certificate being checked. This is not a criterion. + Rather, it is optional information that may help a {@link X509Store} find + CRLs that would be relevant when checking revocation for the specified + attribute certificate. If null is specified, then no such + optional information is provided. + + @param attrCert the IX509AttributeCertificate being checked (or + null) + @see #getAttrCertificateChecking() + + + If true only complete CRLs are returned. Defaults to + false. + + @return true if only complete CRLs are returned. + + + Returns if this selector must match CRLs with the delta CRL indicator + extension set. Defaults to false. + + @return Returns true if only CRLs with the delta CRL + indicator extension are selected. + + + The issuing distribution point. +

    + The issuing distribution point extension is a CRL extension which + identifies the scope and the distribution point of a CRL. The scope + contains among others information about revocation reasons contained in + the CRL. Delta CRLs and complete CRLs must have matching issuing + distribution points.

    +

    + The byte array is cloned to protect against subsequent modifications.

    +

    + You must also enable or disable this criteria with + {@link #setIssuingDistributionPointEnabled(bool)}.

    + + @param issuingDistributionPoint The issuing distribution point to set. + This is the DER encoded OCTET STRING extension value. + @see #getIssuingDistributionPoint() +
    + + Whether the issuing distribution point criteria should be applied. + Defaults to false. +

    + You may also set the issuing distribution point criteria if not a missing + issuing distribution point should be assumed.

    + + @return Returns if the issuing distribution point check is enabled. +
    + + The maximum base CRL number. Defaults to null. + + @return Returns the maximum base CRL number. + @see #setMaxBaseCRLNumber(BigInteger) + + + + A factory to produce Public Key Info Objects. + + + + + Create a Subject Public Key Info object for a given public key. + + One of ElGammalPublicKeyParameters, DSAPublicKeyParameter, DHPublicKeyParameters, RsaKeyParameters or ECPublicKeyParameters + A subject public key info object. + Throw exception if object provided is not one of the above. + + + + Create loading data from byte array. + + + + + + Create loading data from byte array. + + + + + Generates a certificate object and initializes it with the data + read from the input stream inStream. + + + Returns a (possibly empty) collection view of the certificates + read from the given input stream inStream. + + + Class for carrying the values in an X.509 Attribute. + + + @param at an object representing an attribute. + + + Create an X.509 Attribute with the type given by the passed in oid and + the value represented by an ASN.1 Set containing value. + + @param oid type of the attribute + @param value value object to go into the atribute's value set. + + + Create an X.59 Attribute with the type given by the passed in oid and the + value represented by an ASN.1 Set containing the objects in value. + + @param oid type of the attribute + @param value vector of values to go in the attribute's value set. + + + + An Object representing an X509 Certificate. + Has static methods for loading Certificates encoded in many forms that return X509Certificate Objects. + + + + + Return true if the nominated time is within the start and end times nominated on the certificate. + + The time to test validity against. + True if certificate is valid for nominated time. + + + + Checks if the current date is within certificate's validity period. + + + + + Checks if the given date is within certificate's validity period. + + if the certificate is expired by given date + if the certificate is not yet valid on given date + + + + Return the Der encoded TbsCertificate data. + This is the certificate component less the signature. + To Get the whole certificate call the GetEncoded() member. + + A byte array containing the Der encoded Certificate component. + + + + The signature. + + A byte array containg the signature of the certificate. + + + + Get the signature algorithms parameters. (EG DSA Parameters) + + A byte array containing the Der encoded version of the parameters or null if there are none. + + + + Get a key usage guidlines. + + + + + Get the public key of the subject of the certificate. + + The public key parameters. + + + + Return a Der encoded version of this certificate. + + A byte array. + + + + Verify the certificate's signature using the nominated public key. + + An appropriate public key parameter object, RsaPublicKeyParameters, DsaPublicKeyParameters or ECDsaPublicKeyParameters + True if the signature is valid. + If key submitted is not of the above nominated types. + + + + Verify the certificate's signature using a verifier created using the passed in verifier provider. + + An appropriate provider for verifying the certificate's signature. + True if the signature is valid. + If verifier provider is not appropriate or the certificate algorithm is invalid. + + + + Return true if the current time is within the start and end times nominated on the certificate. + + true id certificate is valid for the current time. + + + + Return the certificate's version. + + An integer whose value Equals the version of the cerficate. + + + + Return a BigInteger containing the serial number. + + The Serial number. + + + + Get the Issuer Distinguished Name. (Who signed the certificate.) + + And X509Object containing name and value pairs. + + + + Get the subject of this certificate. + + An X509Name object containing name and value pairs. + + + + The time that this certificate is valid from. + + A DateTime object representing that time in the local time zone. + + + + The time that this certificate is valid up to. + + A DateTime object representing that time in the local time zone. + + + + A meaningful version of the Signature Algorithm. (EG SHA1WITHRSA) + + A sting representing the signature algorithm. + + + + Get the Signature Algorithms Object ID. + + A string containg a '.' separated object id. + + + + Get the issuers UID. + + A DerBitString. + + + + Get the subjects UID. + + A DerBitString. + + + + This class contains a cross certificate pair. Cross certificates pairs may + contain two cross signed certificates from two CAs. A certificate from the + other CA to this CA is contained in the forward certificate, the certificate + from this CA to the other CA is contained in the reverse certificate. + + + + Constructor + Certificate from the other CA to this CA. + Certificate from this CA to the other CA. + + + Constructor from a ASN.1 CertificatePair structure. + The CertificatePair ASN.1 object. + + + Returns the certificate from the other CA to this CA. + + + Returns the certificate from this CA to the other CA. + + + class for dealing with X509 certificates. +

    + At the moment this will deal with "-----BEGIN CERTIFICATE-----" to "-----END CERTIFICATE-----" + base 64 encoded certs, as well as the BER binaries of certificates and some classes of PKCS#7 + objects.

    +
    + + + Create loading data from byte array. + + + + + + Create loading data from byte array. + + + + + Generates a certificate object and initializes it with the data + read from the input stream inStream. + + + Returns a (possibly empty) collection view of the certificates + read from the given input stream inStream. + + + + Create loading data from byte array. + + + + + + Create loading data from byte array. + + + + + The following extensions are listed in RFC 2459 as relevant to CRLs + + Authority Key Identifier + Issuer Alternative Name + CRL Number + Delta CRL Indicator (critical) + Issuing Distribution Point (critical) + + + + Verify the CRL's signature using a verifier created using the passed in verifier provider. + + An appropriate provider for verifying the CRL's signature. + True if the signature is valid. + If verifier provider is not appropriate or the CRL algorithm is invalid. + + + Returns a string representation of this CRL. + + @return a string representation of this CRL. + + + Checks whether the given certificate is on this CRL. + + @param cert the certificate to check for. + @return true if the given certificate is on this CRL, + false otherwise. + + + The following extensions are listed in RFC 2459 as relevant to CRL Entries + + ReasonCode Hode Instruction Code Invalidity Date Certificate Issuer + (critical) + + + Constructor for CRLEntries of indirect CRLs. If isIndirect + is false {@link #getCertificateIssuer()} will always + return null, previousCertificateIssuer is + ignored. If this isIndirect is specified and this CrlEntry + has no certificate issuer CRL entry extension + previousCertificateIssuer is returned by + {@link #getCertificateIssuer()}. + + @param c + TbsCertificateList.CrlEntry object. + @param isIndirect + true if the corresponding CRL is a indirect + CRL. + @param previousCertificateIssuer + Certificate issuer of the previous CrlEntry. + + + + Create loading data from byte array. + + + + + + Create loading data from byte array. + + + + + Generates a certificate revocation list (CRL) object and initializes + it with the data read from the input stream inStream. + + + Returns a (possibly empty) collection view of the CRLs read from + the given input stream inStream. + + The inStream may contain a sequence of DER-encoded CRLs, or + a PKCS#7 CRL set. This is a PKCS#7 SignedData object, with the + only significant field being crls. In particular the signature + and the contents are ignored. + + + A holding class for constructing an X509 Key Usage extension. + +
    +                id-ce-keyUsage OBJECT IDENTIFIER ::=  { id-ce 15 }
    +            
    +                KeyUsage ::= BIT STRING {
    +                     digitalSignature        (0),
    +                     nonRepudiation          (1),
    +                     keyEncipherment         (2),
    +                     dataEncipherment        (3),
    +                     keyAgreement            (4),
    +                     keyCertSign             (5),
    +                     cRLSign                 (6),
    +                     encipherOnly            (7),
    +                     decipherOnly            (8) }
    +             
    +
    + + Basic constructor. + + @param usage - the bitwise OR of the Key Usage flags giving the + allowed uses for the key. + e.g. (X509KeyUsage.keyEncipherment | X509KeyUsage.dataEncipherment) + + + Return the digest algorithm using one of the standard JCA string + representations rather than the algorithm identifier (if possible). + + + + Class to Generate X509V1 Certificates. + + + + + Default Constructor. + + + + + Reset the generator. + + + + + Set the certificate's serial number. + + Make serial numbers long, if you have no serial number policy make sure the number is at least 16 bytes of secure random data. + You will be surprised how ugly a serial number collision can get. + The serial number. + + + + Set the issuer distinguished name. + The issuer is the entity whose private key is used to sign the certificate. + + The issuers DN. + + + + Set the date that this certificate is to be valid from. + + + + + + Set the date after which this certificate will no longer be valid. + + + + + + Set the subject distinguished name. + The subject describes the entity associated with the public key. + + + + + + Set the public key that this certificate identifies. + + + + + + Set the signature algorithm that will be used to sign this certificate. + This can be either a name or an OID, names are treated as case insensitive. + + string representation of the algorithm name + + + + Generate a new X509Certificate. + + The private key of the issuer used to sign this certificate. + An X509Certificate. + + + + Generate a new X509Certificate specifying a SecureRandom instance that you would like to use. + + The private key of the issuer used to sign this certificate. + The Secure Random you want to use. + An X509Certificate. + + + + Generate a new X509Certificate using the passed in SignatureCalculator. + + A signature calculator factory with the necessary algorithm details. + An X509Certificate. + + + + Allows enumeration of the signature names supported by the generator. + + + + An implementation of a version 2 X.509 Attribute Certificate. + + + + Verify the certificate's signature using a verifier created using the passed in verifier provider. + + An appropriate provider for verifying the certificate's signature. + True if the signature is valid. + If verifier provider is not appropriate or the certificate algorithm is invalid. + + + Class to produce an X.509 Version 2 AttributeCertificate. + + + Reset the generator + + + Set the Holder of this Attribute Certificate. + + + Set the issuer. + + + Set the serial number for the certificate. + + + + Set the signature algorithm. This can be either a name or an OID, names + are treated as case insensitive. + + The algorithm name. + + + Add an attribute. + + + Add a given extension field for the standard extensions tag. + + + + Add a given extension field for the standard extensions tag. + The value parameter becomes the contents of the octet string associated + with the extension. + + + + + Generate an X509 certificate, based on the current issuer and subject. + + + + + Generate an X509 certificate, based on the current issuer and subject, + using the supplied source of randomness, if required. + + + + + Generate a new X.509 Attribute Certificate using the passed in SignatureCalculator. + + A signature calculator factory with the necessary algorithm details. + An IX509AttributeCertificate. + + + + Allows enumeration of the signature names supported by the generator. + + + + class to produce an X.509 Version 2 CRL. + + + reset the generator + + + Set the issuer distinguished name - the issuer is the entity whose private key is used to sign the + certificate. + + + Reason being as indicated by CrlReason, i.e. CrlReason.KeyCompromise + or 0 if CrlReason is not to be used + + + + Add a CRL entry with an Invalidity Date extension as well as a CrlReason extension. + Reason being as indicated by CrlReason, i.e. CrlReason.KeyCompromise + or 0 if CrlReason is not to be used + + + + Add a CRL entry with extensions. + + + + Add the CRLEntry objects contained in a previous CRL. + + @param other the X509Crl to source the other entries from. + + + + Set the signature algorithm that will be used to sign this CRL. + + + + + add a given extension field for the standard extensions tag (tag 0) + + + add a given extension field for the standard extensions tag (tag 0) + + + add a given extension field for the standard extensions tag (tag 0) + + + add a given extension field for the standard extensions tag (tag 0) + + + + Generate an X.509 CRL, based on the current issuer and subject. + + The private key of the issuer that is signing this certificate. + An X509Crl. + + + + Generate an X.509 CRL, based on the current issuer and subject using the specified secure random. + + The private key of the issuer that is signing this certificate. + Your Secure Random instance. + An X509Crl. + + + + Generate a new X509Crl using the passed in SignatureCalculator. + + A signature calculator factory with the necessary algorithm details. + An X509Crl. + + + + Allows enumeration of the signature names supported by the generator. + + + + + A class to Generate Version 3 X509Certificates. + + + + + Reset the Generator. + + + + + Set the certificate's serial number. + + Make serial numbers long, if you have no serial number policy make sure the number is at least 16 bytes of secure random data. + You will be surprised how ugly a serial number collision can Get. + The serial number. + + + + Set the distinguished name of the issuer. + The issuer is the entity which is signing the certificate. + + The issuer's DN. + + + + Set the date that this certificate is to be valid from. + + + + + + Set the date after which this certificate will no longer be valid. + + + + + + Set the DN of the entity that this certificate is about. + + + + + + Set the public key that this certificate identifies. + + + + + + Set the signature algorithm that will be used to sign this certificate. + + + + + + Set the subject unique ID - note: it is very rare that it is correct to do this. + + + + + + Set the issuer unique ID - note: it is very rare that it is correct to do this. + + + + + + Add a given extension field for the standard extensions tag (tag 3). + + string containing a dotted decimal Object Identifier. + Is it critical. + The value. + + + + Add an extension to this certificate. + + Its Object Identifier. + Is it critical. + The value. + + + + Add an extension using a string with a dotted decimal OID. + + string containing a dotted decimal Object Identifier. + Is it critical. + byte[] containing the value of this extension. + + + + Add an extension to this certificate. + + Its Object Identifier. + Is it critical. + byte[] containing the value of this extension. + + + + Add a given extension field for the standard extensions tag (tag 3), + copying the extension value from another certificate. + + + + add a given extension field for the standard extensions tag (tag 3) + copying the extension value from another certificate. + @throws CertificateParsingException if the extension cannot be extracted. + + + + Generate an X509Certificate. + + The private key of the issuer that is signing this certificate. + An X509Certificate. + + + + Generate an X509Certificate using your own SecureRandom. + + The private key of the issuer that is signing this certificate. + You Secure Random instance. + An X509Certificate. + + + + Generate a new X509Certificate using the passed in SignatureCalculator. + + A signature calculator factory with the necessary algorithm details. + An X509Certificate. + + + + Allows enumeration of the signature names supported by the generator. + + + + diff --git a/src/packages/itext7.7.0.1/lib/net40/itext.layout.dll b/src/packages/itext7.7.0.1/lib/net40/itext.layout.dll new file mode 100644 index 0000000000000000000000000000000000000000..1921355a626b5c0f73a8f108985682a91ca7e16c GIT binary patch literal 161792 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%0Ap_f0%&Vl_8hQ=J>zqcwgdWeal5 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 I? + + + itext.layout + + + + Represents a border. + + + The solid border. + + + + The dashed border. + + + + The dotted border. + + + + The double border. + + + + The round-dots border. + + + + The 3D groove border. + + + + The 3D inset border. + + + + The 3D outset border. + + + + The 3D ridge border. + + + + The null Border, i.e. + The null Border, i.e. the presence of such border is equivalent to the absence of the border + + + The color of the border. + + + + The width of the border. + + + The type of the border. + + + The hash value for the border. + + + + Creates a + border + with the given width. + The + color + to be set by default is black + + the width which the border should have + + + + Creates a + border + with given width and + color + . + + the color which the border should have + the width which the border should have + + + + Draws the border of a cell. + PdfCanvas to be written to + x coordinate of the beginning point of the element side, that should be bordered + y coordinate of the beginning point of the element side, that should be bordered + x coordinate of the ending point of the element side, that should be bordered + y coordinate of the ending point of the element side, that should be bordered + + + + Returns the type of the + border + + + + + Gets the + color + of the + border + + + the + color + + + + + Gets the width of the + border + + the width + + + + Sets the + color + of the + border + + + + + Sets the width of the + border + + + + Indicates whether the border is equal to the given border. + + Indicates whether the border is equal to the given border. + The border type, width and color are considered during the comparison. + + + + + + + + Returns the + side + corresponded to the line between two points. + Notice that we consider the rectangle traversal to be clockwise. + If the rectangle sides are not parallel to the corresponding page sides + the result is Side.NONE + + the abscissa of the left-bottom point + the ordinate of the left-bottom point + the abscissa of the right-top point + the ordinate of the right-top point + + the corresponded + side + + + + Enumerates the different sides of the rectangle. + + Enumerates the different sides of the rectangle. + The rectangle sides are expected to be parallel to corresponding page sides + Otherwise the result is Side.NONE + + + + Represents a border that is displayed using a 3D effect. + + + + Predefined gray + RGB-color + + + + Creates a Border3D instance with the specified width. + Creates a Border3D instance with the specified width. Also sets the color to gray. + with of the border + + + Creates a Border3D instance with the specified width and color. + color of the border + with of the border + + + Creates a Border3D instance with the specified width and color. + color of the border + with of the border + + + Creates a Border3D instance with the specified width and color. + color of the border + with of the border + + + + + + + + + + Makes the + color of the border + darker and returns the result + + + + + Sets the fill color for the inner half of + 3D Border + + PdfCanvas the color will be applied on + + the + side + the color will be applied on + + + + + Sets the fill color for the outer half of + 3D Border + + PdfCanvas the color will be applied on + + the + side + the color will be applied on + + + + Draws a border with dashes around the element it's been set to. + + + The modifier to be applied on the width to have the dash size + + + The modifier to be applied on the width to have the initial gap size + + + Creates a DashedBorder with the specified width and sets the color to black. + width of the border + + + Creates a DashedBorder with the specified width and the specified color. + color of the border + width of the border + + + + + + + + + + + + Adjusts the size of the gap between dots + + the + border + length + + the initial size of the gap + the adjusted size of the gap + + + Draws a dotted border around the element it has been set to. + + Draws a dotted border around the element it has been set to. Do note that this border draw square dots, + if you want to draw round dots, see + + . + + + + The modifier to be applied on the width to have the initial gap size + + + Creates a DottedBorder instance with the specified width. + Creates a DottedBorder instance with the specified width. The color is set to the default: black. + + width of the border + + + Creates a DottedBorder instance with the specified width and color. + color of the border + width of the border + + + + + + + + + + + + Adjusts the size of the gap between dots + + the + border + length + + the initial size of the gap + the adjusted size of the gap + + + Creates a double border around the element it's set to. + + Creates a double border around the element it's set to. The space between the two border lines has + the same width as the two borders. If a background has been set on the element the color will show in + between the two borders. + + + + Creates a DoubleBorder with the specified width for both the two borders as the space in between them. + + + Creates a DoubleBorder with the specified width for both the two borders as the space in between them. + The color is set to the default: black. + + width of the borders and the space between them + + + + Creates a DoubleBorder with the specified width for both the two borders as the space in between them and + the specified color for the two borders. + + + Creates a DoubleBorder with the specified width for both the two borders as the space in between them and + the specified color for the two borders. The space in between the two borders is either colorless or will + be filled with the background color of the element, if a color has been set. + + width of the borders and the space between them + + + + + + + + + + + + Creates a GrooveBorder instance with the specified width. + + Creates a GrooveBorder instance with the specified width. The color is set to the default: + gray + . + + width of the border + + + + Creates a GrooveBorder instance with the specified width and the + rgb color + . + + width of the border + + the + rgb color + of the border + + + + + Creates a GrooveBorder instance with the specified width and the + cmyk color + . + + width of the border + + the + cmyk color + of the border + + + + + Creates a GrooveBorder instance with the specified width and the + gray color + . + + width of the border + + the + gray color + of the border + + + + + + + + + + + + + Creates a InsetBorder instance with the specified width. + + Creates a InsetBorder instance with the specified width. The color is set to the default: + gray + . + + width of the border + + + + Creates a InsetBorder instance with the specified width and the + rgb color + . + + width of the border + + the + rgb color + of the border + + + + + Creates a InsetBorder instance with the specified width and the + cmyk color + . + + width of the border + + the + cmyk color + of the border + + + + + Creates a InsetBorder instance with the specified width and the + gray color + . + + width of the border + + the + gray color + of the border + + + + + + + + + + + + + Creates a OutsetBorder instance with the specified width. + + Creates a OutsetBorder instance with the specified width. The color is set to the default: + gray + . + + width of the border + + + + Creates a OutsetBorder instance with the specified width and the + rgb color + . + + width of the border + + the + rgb color + of the border + + + + + Creates a OutsetBorder instance with the specified width and the + cmyk color + . + + width of the border + + the + cmyk color + of the border + + + + + Creates a OutsetBorder instance with the specified width and the + gray color + . + + width of the border + + the + gray color + of the border + + + + + + + + + + + + + Creates a RidgeBorder instance with the specified width. + + Creates a RidgeBorder instance with the specified width. The color is set to the default: + gray + . + + width of the border + + + + Creates a RidgeBorder instance with the specified width and the + rgb color + . + + width of the border + + the + rgb color + of the border + + + + + Creates a RidgeBorder instance with the specified width and the + cmyk color + . + + width of the border + + the + cmyk color + of the border + + + + + Creates a RidgeBorder instance with the specified width and the + gray color + . + + width of the border + + the + gray color + of the border + + + + + + + + + + + + + Draws a border with rounded dots aroudn the element it's been set to. + + Draws a border with rounded dots aroudn the element it's been set to. For square dots see + + . + + + + The modifier to be applied on the width to have the initial gap size + + + Creates a RoundDotsBorder with the specified wit?dth and sets the color to black. + width of the border + + + Creates a RoundDotsBorder with the specified wit?dth and the specified color. + color of the border + width of the border + + + + + + + + + + + + Adjusts the size of the gap between dots + + the + border + length + + the initial size of the gap + the adjusted size of the gap + + + Draws a solid border around the element it's set to. + + + Creates a SolidBorder with the specified width and sets the color to black. + width of the border + + + Creates a SolidBorder with the specified width and the specified color. + color of the border + width of the border + + + + + + + + + + + + + This class is used for adding content directly onto a specified + + . + + does not know the concept of a page, so it can't reflow to a 'next' + + . + This class effectively acts as a bridge between the high-level layout + API and the low-level kernel API. + + + + A generic abstract root element for a PDF layout object hierarchy. + + + + A generic abstract element that fits in a PDF layout object hierarchy. + + A generic abstract element that fits in a PDF layout object hierarchy. + A superclass of all + layout object + implementations. + + + + + + A generic Map-like interface that defines methods for storing and retrieving + objects by an enum key of the + + type. + + + + Checks if this entity has the specified property. + + Checks if this entity has the specified property. Compared to + + , + this method can check parent's properties, styles, etc, depending on the origin of the instance + + the property to be checked + + + + if this instance has given property, + + otherwise + + + + Checks if this entity has the specified property, i.e. + Checks if this entity has the specified property, i.e. if it was set to this very element earlier + + the property to be checked + + + + if this instance has given own property, + + otherwise + + + + Gets the property from this entity. + + Gets the property from this entity. Compared to + + , + this method can check parent's properties, styles, etc, depending on the origin of the instance + + + the property to be retrieved + + the value of the given property. + + will be returned if the property value was not found + + + + Gets own property from this entity. + + Gets own property from this entity. The property must have been set earlier to this entity. + If the property is not found, + + will be returned. + + + the property to be retrieved + + the value of the given own property. + + will be returned if the property value was not found + + + + Gets the default property from this entity. + + the property to be retrieved + + the default property value. If the default property is not defined, + + will be returned + + + + Sets a property for this entity. + the property to be set + the value of the property + + + Deletes the own property of this entity. + the property to be deleted + + + Gets the width property of the Element. + the width of the element, with a value and a measurement unit. + + + + Sets the width property of the Element, measured in points. + a value measured in points. + this Element. + + + Sets the width property of the Element, measured in percentage. + a value measured in percentage. + this Element. + + + + Sets the width property of the Element with a + + . + + + a + + object + + this Element. + + + Gets the height property of the Element. + the height of the element, as a floating point value. + + + Sets the height property of the Element. + a floating point value for the new height + this Element. + + + Sets values for a relative repositioning of the Element. + + Sets values for a relative repositioning of the Element. Also has as a + side effect that the Element's + + is changed to + relative + . + The default implementation in + + treats + left and top as the most important values. Only + if left == 0 will right be used for the + calculation; ditto for top vs. bottom. + + movement to the left + movement upwards on the page + movement to the right + movement downwards on the page + this Element. + + + + Sets values for a absolute repositioning of the Element. + + Sets values for a absolute repositioning of the Element. Also has as a + side effect that the Element's + + is changed to + fixed + . + + horizontal position on the page + vertical position on the page + a floating point value measured in points. + this Element. + + + Sets values for a absolute repositioning of the Element. + + Sets values for a absolute repositioning of the Element. Also has as a + side effect that the Element's + + is changed to + fixed + . + + horizontal position on the page + vertical position on the page + + a + + + this Element. + + + + Sets values for a absolute repositioning of the Element, on a specific + page. + + + Sets values for a absolute repositioning of the Element, on a specific + page. Also has as a side effect that the Element's + + is changed to + fixed + . + + the page where the element must be positioned + horizontal position on the page + vertical position on the page + a floating point value measured in points. + this Element. + + + + Sets values for a absolute repositioning of the Element, on a specific + page. + + + Sets values for a absolute repositioning of the Element, on a specific + page. Also has as a side effect that the Element's + + is changed to + fixed + . + + the page where the element must be positioned + horizontal position on the page + vertical position on the page + a floating point value measured in points. + this Element. + + + Sets the horizontal alignment of this Element. + + an enum value of type + + + this Element. + + + Sets the font of this Element. + + a + font program + + this Element. + + + Sets the font color of this Element. + + a + + for the text in this Element. + + this Element. + + + Sets the font size of this Element. + a floating point value + this Element. + + + Sets the text alignment of this Element. + + an enum value of type + + + this Element. + + + Defines a custom spacing distance between all characters of a textual element. + + Defines a custom spacing distance between all characters of a textual element. + The character-spacing parameter is added to the glyph???s horizontal or vertical displacement (depending on the writing mode). + + a floating point value + this Element. + + + Defines a custom spacing distance between words of a textual element. + + Defines a custom spacing distance between words of a textual element. + This value works exactly like the character spacing, but only kicks in at word boundaries. + + a floating point value + this Element. + + + Enable or disable kerning. + + Enable or disable kerning. + Some fonts may specify kern pairs, i.e. pair of glyphs, between which the amount of horizontal space is adjusted. + This adjustment is typically negative, e.g. in "AV" pair the glyphs will typically be moved closer to each other. + + an enum value as a boolean wrapper specifying whether or not to apply kerning + this Element. + + + Specifies a background color for the Element. + the background color + this Element. + + + + Specifies a background color for the Element, and extra space that + must be counted as part of the background and therefore colored. + + the background color + extra coloring to the left side + extra coloring at the top + extra coloring to the right side + extra coloring at the bottom + this Element. + + + Sets a border for all four edges of this Element with customizable color, width, pattern type. + + a customized + + + this Element. + + + Sets a border for the upper limit of this Element with customizable color, width, pattern type. + + a customized + + + this Element. + + + Sets a border for the right limit of this Element with customizable color, width, pattern type. + + a customized + + + this Element. + + + Sets a border for the bottom limit of this Element with customizable color, width, pattern type. + + a customized + + + this Element. + + + Sets a border for the left limit of this Element with customizable color, width, pattern type. + + a customized + + + this Element. + + + Sets a rule for splitting strings when they don't fit into one line. + + Sets a rule for splitting strings when they don't fit into one line. + The default implementation is + + + + an implementation of + + + this Element. + + + Gets a rule for splitting strings when they don't fit into one line. + + the current string splitting rule, an implementation of + + + + + + Gets the text rendering mode, a variable that determines whether showing + text causes glyph outlines to be stroked, filled, used as a clipping + boundary, or some combination of the three. + + the current text rendering mode + + + + + Sets the text rendering mode, a variable that determines whether showing + text causes glyph outlines to be stroked, filled, used as a clipping + boundary, or some combination of the three. + + an int value + this Element. + + + + Gets the stroke color for the current element. + + Gets the stroke color for the current element. + The stroke color is the color of the outlines or edges of a shape. + + the current stroke color + + + Sets the stroke color for the current element. + + Sets the stroke color for the current element. + The stroke color is the color of the outlines or edges of a shape. + + a new stroke color + this Element. + + + Gets the stroke width for the current element. + + Gets the stroke width for the current element. + The stroke width is the width of the outlines or edges of a shape. + + the current stroke width + + + Sets the stroke width for the current element. + + Sets the stroke width for the current element. + The stroke width is the width of the outlines or edges of a shape. + + a new stroke width + this Element. + + + Switch on the simulation of bold style for a font. + + Switch on the simulation of bold style for a font. + Be aware that using correct bold font is highly preferred over this option. + + this element + + + Switch on the simulation of italic style for a font. + + Switch on the simulation of italic style for a font. + Be aware that using correct italic (oblique) font is highly preferred over this option. + + this element + + + Sets default line-through attributes for text. + + Sets default line-through attributes for text. + See + + for more fine tuning. + + this element + + + Sets default underline attributes for text. + + Sets default underline attributes for text. + See other overloads for more fine tuning. + + this element + + + Sets an horizontal line that can be an underline or a strikethrough. + + Sets an horizontal line that can be an underline or a strikethrough. + Actually, the line can be anywhere vertically and has always the text width. + Multiple call to this method will produce multiple lines. + + the absolute thickness of the line + the absolute y position relative to the baseline + this element + + + + + This attribute specifies the base direction of directionally neutral text + (i.e., text that doesn't have inherent directionality as defined in Unicode) + in an element's content and attribute values. + + base direction + this element + + + + Sets a custom hyphenation configuration which will hyphenate words automatically accordingly to the + language and country. + + + this element + + + Sets the writing system for this text element. + a new script type + this Element. + + + Sets a destination name that will be created when this element is drawn to content. + the destination name to be created + this Element. + + + Adds an element to the root. + Adds an element to the root. The element is immediately placed in the contents. + an element with spacial margins, tabbing, and alignment + this element + + + + Adds an image to the root. + Adds an image to the root. The element is immediately placed in the contents. + a graphical image element + this element + + + + + Gets the rootRenderer attribute, a specialized + + that + acts as the root object that other + renderers + descend + from. + + + the + + attribute + + + + Convenience method to write a text aligned about the specified point + text to be placed to the page + the point about which the text will be aligned and rotated + the point about which the text will be aligned and rotated + horizontal alignment about the specified point + this object + + + Convenience method to write a text aligned about the specified point + text to be placed to the page + the point about which the text will be aligned and rotated + the point about which the text will be aligned and rotated + horizontal alignment about the specified point + the angle of rotation applied to the text, in radians + this object + + + Convenience method to write a text aligned about the specified point + text to be placed to the page + the point about which the text will be aligned and rotated + the point about which the text will be aligned and rotated + horizontal alignment about the specified point + vertical alignment about the specified point + the angle of rotation applied to the text, in radians + this object + + + Convenience method to write a kerned text aligned about the specified point + text to be placed to the page + the point about which the text will be aligned and rotated + the point about which the text will be aligned and rotated + horizontal alignment about the specified point + vertical alignment about the specified point + the angle of rotation applied to the text, in radians + this object + + + Convenience method to write a text aligned about the specified point + + paragraph of text to be placed to the page. By default it has no leading and is written in single line. + Set width to write multiline text. + + the point about which the text will be aligned and rotated + the point about which the text will be aligned and rotated + horizontal alignment about the specified point + this object + + + Convenience method to write a text aligned about the specified point + + paragraph of text to be placed to the page. By default it has no leading and is written in single line. + Set width to write multiline text. + + the point about which the text will be aligned and rotated + the point about which the text will be aligned and rotated + horizontal alignment about the specified point + vertical alignment about the specified point + this object + + + Convenience method to write a text aligned about the specified point + + paragraph of text to be placed to the page. By default it has no leading and is written in single line. + Set width to write multiline text. + + the point about which the text will be aligned and rotated + the point about which the text will be aligned and rotated + the page number to write the text + horizontal alignment about the specified point + vertical alignment about the specified point + the angle of rotation applied to the text, in radians + this object + + + + Is initialized and used only when Canvas element autotagging is enabled, see + + . + It is also used to determine if autotagging is enabled. + + + + Creates a new Canvas to manipulate a specific document and page. + the low-level content stream writer + the document that the resulting content stream will be written to + the maximum area that the Canvas may write upon + + + Creates a new Canvas to manipulate a specific document and page. + the low-level content stream writer + the document that the resulting content stream will be written to + the maximum area that the Canvas may write upon + + + + Creates a new Canvas to manipulate a specific + + . + + the form + the document that the resulting content stream will be written to + + + + Gets the + + for this canvas. + + the document that the resulting content stream will be written to + + + Gets the root area rectangle. + the maximum area that the Canvas may write upon + + + + Gets the + + . + + the low-level content stream writer + + + + Sets the + + for this Canvas. + + a renderer specific for canvas operations + + + Returned value is not null only in case when autotagging is enabled. + the page, on which this canvas will be rendered, or null if autotagging is not enabled. + + + Enables canvas content autotagging. + Enables canvas content autotagging. By default it is disabled. + the page, on which this canvas will be rendered. + + + true if autotagging of canvas content is enabled. Default value - false. + + + + Performs an entire recalculation of the element flow on the canvas, + taking into account all its current child elements. + + + Performs an entire recalculation of the element flow on the canvas, + taking into account all its current child elements. May become very + resource-intensive for large documents. + Do not use when you have set + + to true. + + + + + Forces all registered renderers (including child element renderers) to + flush their contents to the content stream. + + + + + Closes the + + . Although not completely necessary in all cases, it is still recommended to call this + method when you are done working with + + object, as due to some properties set there might be some + 'hanging' elements, which are waiting other elements to be added and processed. + + tells the + + that no more elements will be added and it is time to finish processing all the elements. + + + + This class is used for convenient multi-column Document Layouting + + + + Defines the most common properties and behavior that are shared by most + + implementations. All default Renderers are subclasses of + this default implementation. + + + + + A renderer object is responsible for drawing a corresponding layout object on + a document or canvas. + + + A renderer object is responsible for drawing a corresponding layout object on + a document or canvas. Every layout object has a renderer, by default one of + the corresponding type, e.g. you can ask an + + for its + + . + Renderers are designed to be extensible, and custom implementations can be + seeded to layout objects (or their custom subclasses) at runtime. + + + + Adds a child to the current renderer + a child to be added + + + + This method simulates positioning of the renderer, including all of its children, and returns + the + + , representing the layout result, including occupied area, status, i.e. + if there was enough place to fit the renderer subtree, etc. + + can be extended to return custom layout results for custom elements, e.g. + + uses + + as its result. + This method can be called standalone to learn how much area the renderer subtree needs, or can be called + before + + , to prepare the renderer to be flushed to the output stream. + + the description of layout area and any other additional information + result of the layout process + + + Flushes the renderer subtree contents, i.e. + + Flushes the renderer subtree contents, i.e. draws itself on canvas, + adds necessary objects to the + + etc. + + + contains the + + to which the renderer subtree if flushed, + the + + on which the renderer subtree is drawn and other additional parameters + needed to perform drawing + + + + + Gets the resultant occupied area after the last call to the + + method. + + + + + instance + + + + Gets a property from this entity or one of its hierarchical parents. + + Gets a property from this entity or one of its hierarchical parents. + If the property is not found, + + will be returned. + + + the property to be retrieved + a fallback value + the value of the given property + + + + Explicitly sets this object as the child of another + + in + the renderer hierarchy. Some implementations also use this method + internally to create a consistent hierarchy tree. + + the object to place higher in the renderer hierarchy + by default, this object + + + Gets the model element associated with this renderer. + + the model element, as a + container of properties + + + + + Gets the child + + s. + + + a list of direct child + renderers + of this instance + + + + Indicates whether this renderer is flushed or not, i.e. + + Indicates whether this renderer is flushed or not, i.e. if + + has already + been called. + + whether the renderer has been flushed + + + Moves the renderer subtree by the specified offset. + Moves the renderer subtree by the specified offset. This method affects occupied area of the renderer. + + the x-axis offset in points. Positive value will move the renderer subtree to the right. + the y-axis offset in points. Positive value will move the renderer subtree to the top. + + + + Gets a new instance of this class to be used as a next renderer, after this renderer is used, if + + is called more than once. + + new renderer instance + + + + The maximum difference between + + coordinates to consider rectangles equal + + + + The infinity value which is used while layouting + + + Creates a renderer. + + + Creates a renderer for the specified layout element. + the layout element that will be drawn by this renderer + + + + + + + + + + + + + + + + + + + Checks if this renderer or its model element have the specified property, + i.e. if it was set to this very element or its very model element earlier. + + the property to be checked + + true if this instance or its model element have given own property, false otherwise + + + + + + + + Deletes property from this very renderer, or in case the property is specified on its model element, the + property of the model element is deleted + + the property key to be deleted + + + + + + + + + + + + + + + Returns a property with a certain key, as a font object. + + an + enum value + + + a + + + + + Returns a property with a certain key, as a color. + + an + enum value + + + a + + + + + Returns a property with a certain key, as a boolean value. + + an + enum value + + + a + + + + + + + + + + + + Draws a background layer if it is defined by a key + + in either the layout element or this + + itself. + + the context (canvas, document, etc) of this drawing operation. + + + + Performs the drawing operation for all + children + of this renderer. + + the context (canvas, document, etc) of this drawing operation. + + + + Performs the drawing operation for the border of this renderer, if + defined by any of the + + values in either the layout + element or this + + itself. + + the context (canvas, document, etc) of this drawing operation. + + + Indicates whether this renderer is flushed or not, i.e. + + Indicates whether this renderer is flushed or not, i.e. if + + has already + been called. + + whether the renderer has been flushed + + + + + + + + Gets all rectangles that this + + can draw upon in the given area. + + + a physical area on the + + + + a list of + rectangles + + + + + Gets the bounding box that contains all content written to the + + by this + + . + + + the smallest + + that surrounds the content + + + + Gets the border box of a renderer. + + Gets the border box of a renderer. + This is a box used to draw borders. + + border box of a renderer + + + Gets the first yLine of the nested children recursively. + + Gets the first yLine of the nested children recursively. E.g. for a list, this will be the yLine of the + first item (if the first item is indeed a paragraph). + NOTE: this method will no go further than the first child. + Returns null if there is no text found. + + + + Applies margins of the renderer on the given rectangle + a rectangle margins will be applied on. + + indicates whether margins will be applied + inside (in case of false) or outside (in case of false) the rectangle. + + + a + border box + of the renderer + + + + + Applies given margins on the given rectangle + a rectangle margins will be applied on. + the margins to be applied on the given rectangle + + indicates whether margins will be applied + inside (in case of false) or outside (in case of false) the rectangle. + + + a + border box + of the renderer + + + + Returns margins of the renderer + + a + float[] margins + of the renderer + + + + Returns paddings of the renderer + + a + float[] paddings + of the renderer + + + + Applies paddings of the renderer on the given rectangle + a rectangle paddings will be applied on. + + indicates whether paddings will be applied + inside (in case of false) or outside (in case of false) the rectangle. + + + a + border box + of the renderer + + + + + Applies given paddings on the given rectangle + a rectangle paddings will be applied on. + the paddings to be applied on the given rectangle + + indicates whether paddings will be applied + inside (in case of false) or outside (in case of false) the rectangle. + + + a + border box + of the renderer + + + + + Applies the border box of the renderer on the given rectangle + If the border of a certain side is null, the side will remain as it was. + + a rectangle the border box will be applied on. + + indicates whether the border box will be applied + inside (in case of false) or outside (in case of false) the rectangle. + + + a + border box + of the renderer + + + + + Applies the given border box (borders) on the given rectangle + a rectangle paddings will be applied on. + + the + borders + to be applied on the given rectangle + + + indicates whether the border box will be applied + inside (in case of false) or outside (in case of false) the rectangle. + + + a + border box + of the renderer + + + + Indicates whether the renderer's position is fixed or not. + + a + boolean + + + + Indicates whether the renderer's position is fixed or not. + + a + boolean + + + + Gets borders of the element in the specified order: top, right, bottom, left. + + + an array of BorderDrawer objects. + In case when certain border isn't set Property.BORDER is used, + and if Property.BORDER is also not set then null is returned + on position of this border + + + + Calculates bounding box around points. + @param points list of the points calculated bbox will enclose. + @return array of float values which denote left, bottom, right, top lines of bbox in this specific order + + + + Calculates the bounding box of the content in the coordinate system of the pdf entity on which content is placed, + e.g. document page or form xObject. This is particularly useful for the cases when element is nested in the rotated + element. + + a which is a bbox of the content not relative to the parent's layout area but rather to + the some pdf entity coordinate system. + + + Draws (flushes) the content. + + + + + This method correctly closes the + + instance. + There might be hanging elements, like in case of + + is set to true + and when no consequent element has been added. This method addresses such situations. + + + + + + + Adds some pages so that the overall number is at least n. + + Adds some pages so that the overall number is at least n. + Returns the page size of the n'th page. + + + + Creates a ColumnDocumentRenderer. + + Creates a ColumnDocumentRenderer. Sets + + to true. + + + the + + on which this Renderer will calculate + and execute element placements + + + an array of + + specifying the acceptable + positions for elements on a page + + + + + Creates a ColumnDocumentRenderer whose elements need not be flushed + immediately. + + + the + + on which this Renderer will calculate + and execute element placements + + whether or not to flush contents as soon as possible + + an array of + + specifying the acceptable + positions for elements on a page + + + + + Gets the array index of the next area that will be written on after the + current one is full (overflowed). + + the array index of the next area that will be written on + + + + + Creates a document from a + + . Initializes the first page + with the + + 's current default + + . + + the in-memory representation of the PDF document + + + + Creates a document from a + + with a manually set + + . + + the in-memory representation of the PDF document + the page size + + + + Creates a document from a + + with a manually set + + . + + the in-memory representation of the PDF document + the page size + + if true, write pages and page-related instructions + to the + + as soon as possible. + + + + Closes the document and associated PdfDocument. + + + Terminates the current element, usually a page. + + Terminates the current element, usually a page. Sets the next element + to be the size specified in the argument. + + + an + + , optionally with a specified size + + this element + + + Gets PDF document. + the in-memory representation of the PDF document + + + + Changes the + + at runtime. Use this to customize + the Document's + + behavior. + + + + + + Forces all registered renderers (including child element renderers) to + flush their contents to the content stream. + + + + + Gets the left margin, measured in points + a float containing the left margin value + + + Sets the left margin, measured in points + a float containing the new left margin value + + + Gets the right margin, measured in points + a float containing the right margin value + + + Sets the right margin, measured in points + a float containing the new right margin value + + + Gets the top margin, measured in points + a float containing the top margin value + + + Sets the top margin, measured in points + a float containing the new top margin value + + + Gets the bottom margin, measured in points + a float containing the bottom margin value + + + Sets the bottom margin, measured in points + a float containing the new bottom margin value + + + Convenience method to set all margins with one method. + the upper margin + the right margin + the left margin + the lower margin + + + + Returns the area that will actually be used to write on the page, given + the current margins. + + + Returns the area that will actually be used to write on the page, given + the current margins. Does not have any side effects on the document. + + the size of the page to + + a + + with the required dimensions and origin point + + + + Checks whether a method is invoked at the closed document + + + + Defines the most common properties that most + + implementations + share. + + + + + This class represents a layout element, i.e. + + This class represents a layout element, i.e. a piece of content that will + take up 'physical' space on a canvas or document. Its presence and positioning + may influence the position of other + + s on the layout surface. + + + + + Overrides the + IRenderer + instance which will be returned by the next call to the + + . + + the renderer instance + + + Gets a renderer for this element. + + Gets a renderer for this element. Note that this method can be called more than once. + By default each element should define its own renderer, but the renderer can be overridden by + + method call. + + a renderer for this element + + + Creates a renderer subtree with root in the current element. + + Creates a renderer subtree with root in the current element. + Compared to + + , the renderer returned by this method should contain all the child + renderers for children of the current element. + + a renderer subtree for this element + + + Add a new style to this element. + + Add a new style to this element. A style can be used as an effective way + to define multiple equal properties to several elements. + + the style to be added + this element + + + Marks all child elements as artifacts recursively. + + + Returns true if this list contains no elements. + true if this list contains no elements + + + Sets an action on this Element. + + Sets an action on this Element. An action is a general PDF concept that + signifies anything that makes the document interactive, e.g. a hyperlink + or a button. + + + the + + that should be performed + + this Element + + + Explicitly sets the page number this element should be put on. + + Explicitly sets the page number this element should be put on. The location + on the page will be the same as if it were added at the end of the document, + but it will be located on the specified page. + This method should be used very carefully in client code. + + the page number of the page this element should be placed on + this Element + + + + A layout object that terminates the current content area and creates a new + one. + + + A layout object that terminates the current content area and creates a new + one. If no + + is given, the new content area will have the same + size as the current one. + + + + Creates an AreaBreak. + + Creates an AreaBreak. The new content area will have the same size as the + current one. + + + + Creates an AreaBreak that terminates a specified area type. + + an + area break type + + + + Creates an AreaBreak. + + Creates an AreaBreak. The new content area will have the specified page + size. + + the size of the new content area + + + Gets the page size. + + the + page size + of the next content area. + + + + Sets the page size. + + the new + page size + of the next content area. + + + + Gets the type of area that this AreaBreak will terminate. + + the current + area break type + + + + + A + + will try to take up as much horizontal space as + available to it on the canvas or page. The concept is comparable to the block + element in HTML. Also like in HTML, the visual representation of the object + can be delimited by padding, a border, and/or a margin. + + + + + Creates a BlockElement. + + + Gets the current left margin width of the element. + the left margin width, as a float + + + Sets the left margin width of the element. + the new left margin width + this element + + + Gets the current right margin width of the element. + the right margin width, as a float + + + Sets the right margin width of the element. + the new right margin width + this element + + + Gets the current top margin width of the element. + the top margin width, as a float + + + Sets the top margin width of the element. + the new top margin width + this element + + + Gets the current bottom margin width of the element. + the bottom margin width, as a float + + + Sets the bottom margin width of the element. + the new bottom margin width + this element + + + Sets all margins around the element to the same width. + the new margin width + this element + + + Sets the margins around the element to a series of new widths. + the new margin top width + the new margin right width + the new margin bottom width + the new margin left width + this element + + + Gets the current left padding width of the element. + the left padding width, as a float + + + Sets the left padding width of the element. + the new left padding width + this element + + + Gets the current right padding width of the element. + the right padding width, as a float + + + Sets the right padding width of the element. + the new right padding width + this element + + + Gets the current top padding width of the element. + the top padding width, as a float + + + Sets the top padding width of the element. + the new top padding width + this element + + + Gets the current bottom padding width of the element. + the bottom padding width, as a float + + + Sets the bottom padding width of the element. + the new bottom padding width + this element + + + Sets all paddings around the element to the same width. + the new padding width + this element + + + Sets the paddings around the element to a series of new widths. + the new padding top width + the new padding right width + the new padding bottom width + the new padding left width + this element + + + Sets the vertical alignment of the element. + the vertical alignment setting + this element + + + + Sets a ratio which determines in which proportion will word spacing and character spacing + be applied when horizontal alignment is justified. + + + the ratio coefficient. It must be between 0 and 1, inclusive. + It means that ratio part of the free space will + be compensated by word spacing, and 1-ratio part of the free space will + be compensated by character spacing. + If ratio is 1, additional character spacing will not be applied. + If ratio is 0, additional word spacing will not be applied. + + + + + Returns whether the + + should be kept together as much + as possible. + + + the current value of the + + property + + + + + Sets whether the + + should be kept together as much + as possible. + + + the new value of the + + property + + this element + + + + Returns whether the end of this + + and the start of the next sibling of this element + should be placed in the same area. + + + the current value of the + + property + + + + + Sets whether the end of this + + and the start of the next sibling of this element + should be placed in the same area. + Note that this will only work for high-level elements, i.e. elements added to the + + . + + + the new value of the + + property + + this element + + + Sets the rotation radAngle. + the new rotation radAngle, as a float, in radians + this element + + + Sets the rotation angle. + the new rotation angle, as a double, in radians + this element + + + + A + + is one piece of data in an enclosing grid, the + + . + This object is a + + , giving it a number of visual layout + properties. + A cell can act as a container for a number of layout elements; it can only + contain other + + objects or images. Other types of layout + elements must be wrapped in a + + . + + + + Creates a cell which takes a custom amount of cell spaces in the table. + the number of rows this cell must occupy. Negative numbers will make the argument default to 1. + + the number of columns this cell must occupy. Negative numbers will make the argument default to 1. + + + + Creates a cell. + + + Gets a cell renderer for this element. + + Gets a cell renderer for this element. Note that this method can be called more than once. + By default each element should define its own renderer, but the renderer can be overridden by + + method call. + + a cell renderer for this element + + + + Gets + the number of the row + in which the cell is located. + + the row number + + + + Gets + the number of the column + in which the cell is located. + + the column number + + + + Gets the + rowspan + of the cell. + + the rowspan + + + + Gets the + colspan + of the cell. + + the colspan + + + Adds any block element to the cell's contents. + + a + + + this Element + + + Adds an image to the cell's contents. + + an + + + this Element + + + Adds an embedded table to the cell's contents. + + a nested + + + this Element + + + Directly adds a String of text to this cell. + + Directly adds a String of text to this cell. The content is wrapped in a + layout element. + + + a + + + this Element + + + Clones a cell with its position, properties, and optionally its contents. + whether or not to also include the contents of the cell. + a clone of this Element + + + + A + + is a container object that defines a section in a document, + which will have some shared layout properties. Like all + + types, it will try to take up as much horizontal space as possible. + The concept is very similar to that of the div tag in HTML. + + + + Adds any block element to the div's contents. + + a + + + this Element + + + Adds an image to the div's contents. + + an + + + this Element + + + + A + + is a layout element which may get added to + indefinitely, making the object prohibitively large. + In order to avoid consuming and holding on to undesirable amounts of + resources, the contents of a + + can be flushed regularly + by client code, e.g. at page boundaries or after a certain amount of additions. + + + + Checks whether an element has already been marked as complete. + the completion marker boolean + + + Indicates that all the desired content has been added to this large element. + + + Writes the newly added content to the document. + + + Flushes the content which has just been added to the document. + + Flushes the content which has just been added to the document. + This is a method for internal usage and is called automatically by the document. + + + + Sets the document this element is bound to. + + Sets the document this element is bound to. + We cannot write a large element into several documents simultaneously because we would need + more bulky interfaces for this feature. For now we went for simplicity. + + the document + + + + A marker subinterface of + + that specifies that the layout object + is, by definition, on the lowest tier in the object hierarchy. A + leaf element + must not act as a container for other + elements. + + + + A layout element that represents an image for inclusion in the document model. + + + + Creates an + + from an image XObject, the representation of an + image in PDF syntax. + + + an internal + + + + + + Creates an + + from a form XObject, the representation of a + form in PDF syntax. + + + an internal + + + + + + Creates an + + from an image XObject, the representation of an + image in PDF syntax, with a custom width. + + + an internal + + + a float value + + + + Creates an + + from an image XObject, the representation of an + image in PDF syntax, with a custom width and on a fixed position. + + + an internal + + + a float value representing the horizontal offset of the lower left corner of the image + a float value representing the vertical offset of the lower left corner of the image + a float value + + + + Creates an + + from an image XObject, the representation of an + image in PDF syntax, on a fixed position. + + + an internal + + + a float value representing the horizontal offset of the lower left corner of the image + a float value representing the vertical offset of the lower left corner of the image + + + + Creates an + + from a form XObject, the representation of a + form in PDF syntax. + + + an internal + + + a float value representing the horizontal offset of the lower left corner of the form + a float value representing the vertical offset of the lower left corner of the form + + + + Creates an + + from an image resource, read in from a file + with the iText I/O module. + + + an internal representation of the + image resource + + + + + Creates an + + from an image resource, read in from a file + with the iText I/O module, on a fixed position. + + + an internal representation of the + image resource + + a float value representing the horizontal offset of the lower left corner of the image + a float value representing the vertical offset of the lower left corner of the image + + + + Creates an + + from an image resource, read in from a file + with the iText I/O module, with a custom width and on a fixed position. + + + an internal representation of the + image resource + + a float value representing the horizontal offset of the lower left corner of the image + a float value representing the vertical offset of the lower left corner of the image + a float value + + + Gets the XObject contained in this image object + + a + + + + + Sets the rotation radAngle. + a value in radians + this element + + + Gets the current left margin width of the element. + the left margin width, as a float + + + Sets the left margin width of the element. + the new left margin width + this element + + + Gets the current right margin width of the element. + the right margin width, as a float + + + Sets the right margin width of the element. + the new right margin width + this element + + + Gets the current top margin width of the element. + the top margin width, as a float + + + Sets the top margin width of the element. + the new top margin width + this element + + + Gets the current bottom margin width of the element. + the bottom margin width, as a float + + + Sets the bottom margin width of the element. + the new bottom margin width + this element + + + Sets the margins around the element to a series of new widths. + the new margin top width + the new margin right width + the new margin bottom width + the new margin left width + this element + + + Scale the image relative to its default size. + the horizontal scaling coefficient. default value 1 = 100% + the vertical scaling coefficient. default value 1 = 100% + this element + + + Scale the image to an absolute size. + + Scale the image to an absolute size. This method will preserve the + width-height ratio of the image. + + the new maximum width of the image + the new maximum height of the image + this element + + + Scale the image to an absolute size. + + Scale the image to an absolute size. This method will not + preserve the width-height ratio of the image. + + the new absolute width of the image + the new absolute height of the image + this element + + + Sets the autoscale property for both width and height. + whether or not to let the image resize automatically + this image + + + Sets the autoscale property for the height of the image. + whether or not to let the image height resize automatically + this image + + + Sets the autoscale property for the width of the image. + whether or not to let the image width resize automatically + this image + + + Sets values for a absolute repositioning of the Element. + + Sets values for a absolute repositioning of the Element. Also has as a + side effect that the Element's + + is changed to + fixed + . + + horizontal position on the page + vertical position on the page + this image. + + + + Sets values for a absolute repositioning of the Element, on a specific + page. + + + Sets values for a absolute repositioning of the Element, on a specific + page. Also has as a side effect that the Element's + + is changed to + fixed + . + + the page where the element must be positioned + horizontal position on the page + vertical position on the page + this Element. + + + Gets width of the image. + + Gets width of the image. It returns width of image or form XObject, + not the width set by one of the #setWidth methods + + the original width of the image + + + Gets height of the image. + + Gets height of the image. It returns height of image or form XObject, + not the height set by one of the #setHeight methods + + the original height of the image + + + Gets scaled width of the image. + the current scaled width + + + Gets scaled height of the image. + the current scaled height + + + + + Creates a custom line separator with line style defined by custom + + interface instance + + line drawer instance + + + + A clickable piece of + + which contains a + link annotation dictionary + . The concept is largely similar to that of the + HTML anchor tag. + + + + + A + + is a piece of text of any length. As a + leaf element + , + it is the smallest piece of content that may bear specific layout attributes. + + + + Constructs a Text with its role initialized. + + the contents, as a + + + + + Gets the contents of the Text object that will be rendered. + the string with the contents + + + Sets the contents of the Text object. + the new contents + + + Gets the text rise. + the vertical distance from the text's default base line, as a float. + + + Sets the text rise. + a vertical distance from the text's default base line. + this Text + + + + Gets the horizontal scaling property, which determines how wide the text + should be stretched. + + the horizontal spacing, as a float + + + Skews the text to simulate italic and other effects. + + Skews the text to simulate italic and other effects. Try alpha=0 + and beta=12. + + the first angle in degrees + the second angle in degrees + this Text + + + + The horizontal scaling parameter adjusts the width of glyphs by stretching or + compressing them in the horizontal direction. + + + the scaling parameter. 1 means no scaling will be applied, + 0.5 means the text will be scaled by half. + 2 means the text will be twice as wide as normal one. + + this Text + + + Creates a Link with a fully constructed link annotation dictionary. + the textual contents of the link + + a + + + + + Creates a Link which can execute an action. + the textual contents of the link + + a + + + + + Creates a Link to another location in the document. + the textual contents of the link + + a + + + + + Gets the link annotation dictionary associated with this link. + + a + + + + + + A List is a layout element representing a series of objects that are vertically + outlined with the same or very similar layout properties, giving it a sense + of unity. + + + A List is a layout element representing a series of objects that are vertically + outlined with the same or very similar layout properties, giving it a sense + of unity. It contains + + objects that can optionally be prefixed + with a symbol and/or numbered. + + + + + Creates a List with the + + as a prefix. + + + + Creates a List with a custom numbering type. + a prefix style + + + + Adds a new + + to the bottom of the List. + + a new list item + this list. + + + + Adds a new + + to the bottom of the List. + + textual contents of the new list item + this list. + + + Customizes the index of the first item in the list. + the custom index, as an int + this list. + + + Sets the list symbol to be used. + + Sets the list symbol to be used. This will create an unordered list, i.e. + all + list items + will be shown with the same prefix. + + the textual symbol to be used for all items. + this list. + + + Sets the list symbol to be used. + + Sets the list symbol to be used. This will create an unordered list, i.e. + all + list items + will be shown with the same prefix. + + + the + + object to be used for all items. + + this list. + + + Sets the list symbol to be used. + + Sets the list symbol to be used. This will create an unordered list, i.e. + all + list items + will be shown with the same prefix. + + + the + + object to be used for all items. + + this list. + + + Sets the list numbering type to be used. + + Sets the list numbering type to be used. This will create an ordered list, + i.e. every + + will have a unique prefix. + + + the + + that will generate appropriate prefixes for the + + s. + + this list. + + + A specialized enum containing alignment properties for list symbols. + + A specialized enum containing alignment properties for list symbols. + + means that the items will be aligned as follows: + 9. Item 9 + 10. Item 10 + Whereas + + means the items will be aligned as follows: + 9. Item 9 + 10. Item 10 + + + + + Gets the indent offset of the + + symbols. + + the indent offset as a float. + + + + Sets the indent offset of the + + symbols. + + the new indent offset. + this list. + + + + Gets the piece of text that is added after the + + symbol. + + the post symbol text + + + + Sets a piece of text that should be added after the + + symbol. + + the post symbol text + + + + Gets the piece of text that is added before the + + symbol. + + the pre symbol text + + + + Sets a piece of text that should be added before the + + symbol. + + the pre symbol text + + + + A list item is a layout element that is one entry in a + + . The list + object controls the prefix, postfix, and numbering of the list items. + + + + Creates a ListItem. + + + Creates a list item with text. + the textual contents of the list item + + + Creates a list item with an image. + the graphical contents of the list item + + + Sets the list item symbol to be used. + the textual symbol to be used for the item. + this list item. + + + Sets the list item symbol to be used. + + the + + object to be used for the item. + + this list item. + + + Sets the list item symbol to be used. + + the + + object to be used for the item. + + this list. + + + Sets the list item numbering type to be used. + + the + + that will generate appropriate prefixes for the + + . + + this list item. + + + + A layout element that represents a self-contained block of textual and + grpahical information. + + + A layout element that represents a self-contained block of textual and + grpahical information. + It is a + + which essentially acts as a container for + leaf elements + . + + + + Creates a Paragraph. + + + Creates a Paragraph, initialized with a piece of text. + + the initial textual content, as a + + + + + Creates a Paragraph, initialized with a piece of text. + + the initial textual content, as a + + + + + Adds a piece of text to the Paragraph + + the content to be added, as a + + + this Paragraph + + + Adds a layout element to the Paragraph. + + the content to be added, any + + + this Paragraph + + + + Adds a + + of layout elements to the Paragraph. + + + the content to be added, any + + + this Paragraph + + + Adds an unspecified amount of tabstop elements as properties to the Paragraph. + + the + tabstop(s) + to be added as properties + + this Paragraph + + + + + Adds a + + of tabstop elements as properties to the Paragraph. + + + the list of + + s to be added as properties + + this Paragraph + + + + + Removes a tabstop position from the Paragraph, if it is present in the + + property. + + + the + + position to be removed. + + this Paragraph + + + + + Sets the indent value for the first line of the + + . + + + the indent value that must be applied to the first line of + the Paragraph, as a float + + this Paragraph + + + + Sets the leading value, using the + + strategy. + + the new leading value + this Paragraph + + + + + Sets the leading value, using the + + strategy. + + the new leading value + this Paragraph + + + + + This class represents the empty space from a + + to the following + + , if any. Using this class will not have any effect unless + there are + + objects defined for the enveloping element. + + + + + A + + is a layout element that represents data in a two-dimensional + grid. It is filled with + cells + , ordered in rows and columns. + It is an implementation of + + , which means it can be flushed + to the canvas, in order to reclaim memory that is locked up. + + + + + Constructs a + Table + with the relative column widths. + + the relative column widths + whether parts of the table will be written before all data is added. + + + + + Constructs a + Table + with the relative column widths. + + the relative column widths + + + + Constructs a + Table + with + + columns. + + the number of columns + whether parts of the table will be written before all data is added. + + + + + Constructs a + Table + with + + columns. + + the number of columns + + + Sets the full width of the table. + the full width of the table. + this element + + + Returns the column width for the specified column. + index of the column + the width of the column + + + Returns the number of columns. + the number of columns. + + + Returns the number of rows. + the number of rows. + + + Adds a new cell to the header of the table. + + Adds a new cell to the header of the table. + The header will be displayed in the top of every area of this table. + See also + + . + + a header cell to be added + + + Adds a new cell with received blockElement as a content to the header of the table. + + + Adds a new cell with received blockElement as a content to the header of the table. + The header will be displayed in the top of every area of this table. + See also + + . + + an element to be added to a header cell + + + Adds a new cell with received image to the header of the table. + + Adds a new cell with received image to the header of the table. + The header will be displayed in the top of every area of this table. + See also + + . + + an element to be added to a header cell + + + Adds a new cell with received string as a content to the header of the table. + + + Adds a new cell with received string as a content to the header of the table. + The header will be displayed in the top of every area of this table. + See also + + . + + a string to be added to a header cell + + + Gets the header of the table. + Gets the header of the table. The header is represented as a distinct table and might have its own properties. + + + table header or + + , if + + hasn't been called. + + + + Adds a new cell to the footer of the table. + + Adds a new cell to the footer of the table. + The footer will be displayed in the bottom of every area of this table. + See also + + . + + a footer cell + + + Adds a new cell with received blockElement as a content to the footer of the table. + + + Adds a new cell with received blockElement as a content to the footer of the table. + The header will be displayed in the top of every area of this table. + See also + + . + + an element to be added to a footer cell + + + Adds a new cell with received image as a content to the footer of the table. + + + Adds a new cell with received image as a content to the footer of the table. + The header will be displayed in the top of every area of this table. + See also + + . + + an image to be added to a footer cell + + + Adds a new cell with received string as a content to the footer of the table. + + + Adds a new cell with received string as a content to the footer of the table. + The header will be displayed in the top of every area of this table. + See also + + . + + a content string to be added to a footer cell + + + Gets the footer of the table. + Gets the footer of the table. The footer is represented as a distinct table and might have its own properties. + + + table footer or + + , if + + hasn't been called. + + + + + Tells you if the first header needs to be skipped (for instance if the + header says "continued from the previous page"). + + Value of property skipFirstHeader. + + + + Tells you if the last footer needs to be skipped (for instance if the + footer says "continued on the next page") + + Value of property skipLastFooter. + + + Skips the printing of the first header. + + Skips the printing of the first header. Used when printing tables in + succession belonging to the same printed table aspect. + + New value of property skipFirstHeader. + this element + + + Skips the printing of the last footer. + + Skips the printing of the last footer. Used when printing tables in + succession belonging to the same printed table aspect. + + New value of property skipLastFooter. + this element + + + Starts new row. + Starts new row. This mean that next cell will be added at the beginning of next line. + + this element + + + Adds a new cell to the table. + + Adds a new cell to the table. The implementation decides for itself which + row the cell will be placed on. + + + + Cell + to add. + + this element + + + Adds a new cell with received blockElement as a content. + a blockElement to add to the cell and then to the table + + this element + + + Adds a new cell with received image as a content. + an image to add to the cell and then to the table + this element + + + Adds a new cell with received string as a content. + a string to add to the cell and then to the table + this element + + + Returns a cell as specified by its location. + + Returns a cell as specified by its location. If the cell is in a col-span + or row-span and is not the top left cell, then null is returned. + + the row of the cell. indexes are zero-based + the column of the cell. indexes are zero-based + the cell at the specified position. + + + Creates a renderer subtree with root in the current table element. + + Creates a renderer subtree with root in the current table element. + Compared to + + , the renderer returned by this method should contain all the child + renderers for children of the current element. + + + a + + subtree for this element + + + + Gets a table renderer for this element. + + Gets a table renderer for this element. Note that this method can be called more than once. + By default each element should define its own renderer, but the renderer can be overridden by + + method call. + + a table renderer for this element + + + Indicates that all the desired content has been added to this large element. + + + Indicates that all the desired content has been added to this large element. + After this method is called, more precise rendering is activated. + For instance, a table may have a + + method set to true, + and in case of large table on + + we do not know if any more content will be added, + so we might not place the content in the bottom of the page where it would fit, but instead add a footer, and + place that content in the start of the page. Technically such result would look all right, but it would be + more concise if we placed the content in the bottom and did not start new page. For such cases to be + renderered more accurately, one can call + + when some content is still there and not flushed. + + + + Writes the newly added content to the document. + + + Flushes the content which has just been added to the document. + + Flushes the content which has just been added to the document. + This is a method for internal usage and is called automatically by the docunent. + + + + Gets the markup properties of the bottom border of the (current) last row. + + + an array of + + objects + + + + A simple object which holds the row numbers of a section of a table. + + + + Creates a + + + the start number of the row group, inclusive + the finish number of the row group, inclusive + + + Gets the starting row number of the table section + the start number of the row group, inclusive + + + Gets the finishing row number of the table section + the finish number of the row group, inclusive + + + + A TabStop is the closest location on a line of text that the text will jump + to if a + + is inserted. At least one TabStop must be defined on an + element if you want to use + Tabs + . + This object can be added to a + + with the method + + . + + + + Creates a TabStop at the appropriate position. + a float, measured in points + + + + Creates a TabStop at the appropriate position, with a specified tab + alignment. + + + Creates a TabStop at the appropriate position, with a specified tab + alignment. A tab alignment defines the way the textual content should be + positioned with regards to this tab stop. + + a float, measured in points + + a + + value + + + + + Creates a TabStop at the appropriate position, with a specified tab + alignment and an explicitly given line pattern. + + + Creates a TabStop at the appropriate position, with a specified tab + alignment and an explicitly given line pattern. A tab alignment defines + the way the textual content should be positioned with regards to this tab + stop. The line pattern defines a pattern that should be repeated until + the TabStop is reached. If null, the space leading up to the TabStop will + be empty. + + a float, measured in points + + a + + value + + + the + + value, a pattern drawing object + + + + +

    This class implements a simple byte vector with access to the + underlying array.

    +

    This work was authored by Carlos Villegas (cav@uniscope.co.jp).

    +
    +
    + + Capacity increment size + + + The encapsulated array + + + Points to next free item + + + Construct byte vector instance with default block size. + + + Construct byte vector instance. + initial block size + + + Construct byte vector instance. + + byte array to use + TODO should n should be initialized to a.length to be consistent with + CharVector behavior? [GA] + + + + Construct byte vector instance. + byte array to use + + initial block size + TODO should n should be initialized to a.length to be consistent with + CharVector behavior? [GA] + + + + Obtain byte vector array. + byte array + + + Obtain number of items in array. + number of items + + + Obtain capacity of array. + current capacity of array + + + Pet byte at index. + the index + a byte + + + Get byte at index. + the index + a byte + + + This is to implement memory allocation in the array. + This is to implement memory allocation in the array. Like malloc(). + to allocate + previous length + + + Trim byte vector to current length. + + + +

    This class implements a simple char vector with access to the + underlying array.

    +

    This work was authored by Carlos Villegas (cav@uniscope.co.jp).

    +
    +
    + + Capacity increment size + + + The encapsulated array + + + Points to next free item + + + Construct char vector instance with default block size. + + + Construct char vector instance. + initial block size + + + Construct char vector instance. + char array to use + + + Construct char vector instance. + char array to use + initial block size + + + Copy constructor + the CharVector that should be cloned + + + Reset length of vector, but don't clear contents. + + + Obtain char vector array. + char array + + + Obtain number of items in array. + number of items + + + Obtain capacity of array. + current capacity of array + + + Pet char at index. + the index + a char + + + Get char at index. + the index + a char + + + This is to implement memory allocation in the array. + This is to implement memory allocation in the array. Like malloc(). + to allocate + previous length + + + Trim char vector to current length. + + + Represents a hyphen. + + + pre break string + + + no break string + + + post break string + + + Construct a hyphen. + break string + break string + break string + + + Construct a hyphen. + break string + + + + + + + + + + number of hyphenation points in word + + + + rawWord as made of alternating strings and + Hyphen + instances + + + + the number of hyphenation points in the word + + + an index position + the pre-break text, not including the hyphen character + + + an index position + the post-break text + + + the hyphenation points + + + + + + + + + This is the class used to configure hyphenation on layout level + + + The Hyphenator object. + + + The hyphenation symbol used when hyphenating. + + + + Constructs a new + + . No language hyphenation files will be used. + Only soft hyphen symbols ('\u00ad') will be taken into account. + + the minimum number of characters before the hyphenation point + the minimum number of characters after the hyphenation point + + + + Constructs a new + + by a + + which will be used to + find hyphenation points. + + + the + + instance + + + + + Constructs a new + + instance. + + the language + the optional country code (may be null or "none") + the minimum number of characters before the hyphenation point + the minimum number of characters after the hyphenation point + + + Hyphenates a given word. + + + + object representing possible hyphenation points + or + + if no hyphenation points are found. + + + + Gets the hyphenation symbol. + the hyphenation symbol + + + Sets the hyphenation symbol to the specified value. + the new hyphenation symbol + + + A hyphenation exception. + + A hyphenation exception. +

    This work was authored by Carlos Villegas (cav@uniscope.co.jp).

    +
    +
    + + Construct a hyphenation exception. + a message string + + + + + allocation size for arrays + + + + Pointer to low branch and to rest of the key when it is + stored directly in this node, we don't have unions in java! + + + + Pointer to high branch. + + + Pointer to equal branch and to data when this node is a string terminator. + + + + This vector holds the trailing of the keys when the branch is compressed. + + + root + + + free node + + + number of items in tree + + + default constructor + + + initialize + + + + Branches are initially compressed, needing + one node per key plus the size of the string + key. + + + Branches are initially compressed, needing + one node per key plus the size of the string + key. They are decompressed as needed when + another key with same prefix + is inserted. This saves a lot of space, + specially for long keys. + + the key + a value + + + Insert key. + the key + offset into key array + a value + + + The actual insertion function, recursive version. + + + Compares 2 null terminated char arrays + a character array + an index into character array + a character array + an index into character array + an integer + + + Compares a string with null terminated char array + a string + a character array + an index into character array + an integer + + + a character array + an index into character array + a character array + an index into character array + + + a character array + an index into character array + an integer + + + a character array + an integer + + + Find key. + the key + result + + + Find key. + the key + offset into key array + result + + + a key + trye if key present + + + length + + + + Recursively insert the median first and then the median of the + lower and upper halves, and so on in order to get a balanced + tree. + + + Recursively insert the median first and then the median of the + lower and upper halves, and so on in order to get a balanced + tree. The array of keys is assumed to be sorted in ascending + order. + + array of keys + array of values + where to insert + count to insert + + + Balance the tree for best search performance + + + + Each node stores a character (splitchar) which is part of + some key(s). + + + Each node stores a character (splitchar) which is part of + some key(s). In a compressed branch (one that only contain + a single string key) the trailer of the key which is not + already in nodes is stored externally in the kv array. + As items are inserted, key substrings decrease. + Some substrings may completely disappear when the whole + branch is totally decompressed. + The tree is traversed to find the key substrings actually + used. In addition, duplicate substrings are removed using + a map (implemented with a TernaryTree!). + + + + the keys + + + +

    This interface is used to connect the XML pattern file parser to + the hyphenation tree.

    +

    This work was authored by Carlos Villegas (cav@uniscope.co.jp).

    +
    +
    + + Add a character class. + + Add a character class. + A character class defines characters that are considered + equivalent for the purpose of hyphenation (e.g. "aA"). It + usually means to ignore case. + + character group + + + Add a hyphenation exception. + + Add a hyphenation exception. An exception replaces the + result obtained by the algorithm for cases for which this + fails or the user wants to provide his own hyphenation. + A hyphenatedword is a vector of alternating String's and + Hyphen + instances + + word to add as an exception + pre-hyphenated word + + + Add hyphenation patterns. + the pattern + + interletter values expressed as a string of + digit characters. + + + + value space: stores the interletter values + + + This map stores hyphenation exceptions + + + This map stores the character classes + + + Temporary map to store interletter values on pattern loading. + + + Default constructor. + + + + Packs the values by storing them in 4 bits, two values into a byte + Values range is from 0 to 9. + + + Packs the values by storing them in 4 bits, two values into a byte + Values range is from 0 to 9. We use zero as terminator, + so we'll add 1 to the value. + + + a string of digits from '0' to '9' representing the + interletter values. + + + the index into the vspace array where the packed values + are stored. + + + + Unpack values. + an integer + a string + + + Read hyphenation patterns from an XML file. + the filename + In case the parsing fails + + + + + Read hyphenation patterns from an XML file. + the InputSource for the file + unique key representing country-language combination + In case the parsing fails + + + + Find pattern. + a pattern + a string + + + + String compare, returns 0 if equal or + t is a substring of s. + + first character array + starting index into first array + second character array + starting index into second array + an integer + + + Get values. + an integer + a byte array + + + + Hyphenate word and return a Hyphenation object. + the word to be hyphenated + + Minimum number of characters allowed + before the hyphenation point. + + + Minimum number of characters allowed after + the hyphenation point. + + + a + Hyphenation + object representing + the hyphenated word or null if word is not hyphenated. + + + + Hyphenate word and return an array of hyphenation points. + char array that contains the word + Offset to first character in word + Length of word + + Minimum number of characters allowed + before the hyphenation point. + + + Minimum number of characters allowed after + the hyphenation point. + + + a + Hyphenation + object representing + the hyphenated word or null if word is not hyphenated. + + + + Add a character class to the tree. + + Add a character class to the tree. It is used by + PatternParser + as callback to + add character classes. Character classes define the + valid word characters for hyphenation. If a word contains + a character not defined in any of the classes, it is not hyphenated. + It also defines a way to normalize the characters in order + to compare them with the stored patterns. Usually pattern + files use only lower case characters, in this case a class + for letter 'a', for example, should be defined as "aA", the first + character being the normalization char. + + a character class (group) + + + Add an exception to the tree. + + Add an exception to the tree. It is used by + PatternParser + class as callback to + store the hyphenation exceptions. + + normalized word + + a vector of alternating strings and + hyphen + objects. + + + + Add a pattern to the tree. + + Add a pattern to the tree. Mainly, to be used by + PatternParser + class as callback to + add a pattern to the tree. + + the hyphenation pattern + + interletter weight values indicating the + desirability and priority of hyphenating at a given point + within the pattern. It should contain only digit characters. + (i.e. '0' to '9'). + + + +

    This is a cache for HyphenationTree instances.

    +
    + + Contains the cached hyphenation trees + + + Used to avoid multiple error messages for the same language if a pattern file is missing. + + + Looks in the cache if a hyphenation tree is available and returns it if it is found. + the language + the country (may be null or "none") + the HyhenationTree instance or null if it's not in the cache + + + Constructs the key for the hyphenation pattern file. + the language + the country (may be null or "none") + the resulting key + + + + If the user configured a hyphenation pattern file name + for this (lang,country) value, return it. + + + If the user configured a hyphenation pattern file name + for this (lang,country) value, return it. If not, return null. + + the language + the country (may be null or "none") + the map of user-configured hyphenation pattern file names + the hyphenation pattern file name or null + + + Cache a hyphenation tree under its key. + the key (ex. "de_CH" or "en") + the hyphenation tree + + + Notes a key to a hyphenation tree as missing. + + Notes a key to a hyphenation tree as missing. + This is to avoid searching a second time for a hyphenation pattern file which is not + available. + + the key (ex. "de_CH" or "en") + + + Indicates whether a hyphenation file has been requested before but it wasn't available. + + Indicates whether a hyphenation file has been requested before but it wasn't available. + This is to avoid searching a second time for a hyphenation pattern file which is not + available. + + the key (ex. "de_CH" or "en") + true if the hyphenation tree is unavailable + + + + Logging instance. + + + Creates a new hyphenator. + the language + the optional country code (may be null or "none") + the minimum number of characters before the hyphenation point + the minimum number of characters after the hyphenation point + + + Creates a new hyphenator. + the language + the optional country code (may be null or "none") + the map with user-configured hyphenation pattern file names + the minimum number of characters before the hyphenation point + the minimum number of characters after the hyphenation point + + + Registers additional file directories. + directory to register + + + Returns the default hyphenation tree cache. + the default (static) hyphenation tree cache + + + Clears the default hyphenation tree cache. + Clears the default hyphenation tree cache. This method can be used if the underlying data files are changed at runtime. + + + + + Returns a hyphenation tree for a given language and country, + with fallback from (lang,country) to (lang). + + + Returns a hyphenation tree for a given language and country, + with fallback from (lang,country) to (lang). + The hyphenation trees are cached. + + the language + the country (may be null or "none") + the map with user-configured hyphenation pattern file names + the hyphenation tree + + + Returns a hyphenation tree for a given language and country. + Returns a hyphenation tree for a given language and country. The hyphenation trees are cached. + the language + the country (may be null or "none") + the map with user-configured hyphenation pattern file names + the hyphenation tree + + + Load tree from xml file using configuration settings. + the directory to search the file into + language key for the requested hyphenation file + the requested HyphenationTree or null if it is not available + + + Load tree from the stream. + the input stream to load the tree from + unique key representing country-language combination + the requested HyphenationTree or null if it is not available + + + Hyphenates a word. + the language + the optional country code (may be null or "none") + the map with user-configured hyphenation pattern file names + the word to hyphenate + the minimum number of characters before the hyphenation point + the minimum number of characters after the hyphenation point + the hyphenation result + + + Hyphenates a word. + the language + the optional country code (may be null or "none") + the word to hyphenate + the minimum number of characters before the hyphenation point + the minimum number of characters after the hyphenation point + the hyphenation result + + + Hyphenates a word. + the word to hyphenate + the hyphenation result + + + +

    A SAX document handler to read and parse hyphenation patterns + from a XML file.

    +

    This work was authored by Carlos Villegas (cav@uniscope.co.jp).

    +
    +
    + + Construct a pattern parser. + if a hyphenation exception is raised + + + + Construct a pattern parser. + a pattern consumer + if a hyphenation exception is raised + + + + Parses a hyphenation pattern file. + the filename + In case of an exception while parsing + + + + + Parses a hyphenation pattern file. + the InputStream for the file + unique key representing country-language combination + In case of an exception while parsing + + + + if not caught + + + + An object that iterates over the + + . + + + + current node index + + + current key + + + Node stack + + + key stack implemented with a StringBuffer + + + default constructor + + + rewind iterator + + + Resets the Iterator to its initial state. + + + value + + + true if more elements + + + traverse upwards + + + traverse the tree to find next key + + + next element + + + parent + + + child + + + default constructor + + + Construct item. + a char + a char + + + + Represents the area for content + layouting + . + + + + The number of page on which the area is located. + + + The area's bounding box + + + Indicates whether the area already has some placed content or not. + + + + Creates the area for content + layouting + . + + the number of page on which the area is located. + the area's bounding box + + + Gets the number of page on which the area is located. + page number + + + + Gets the + box + which bounds the area. + + the bounding box + + + + Sets the + box + which bounds the area. + + + + Indicates whether the area already has some placed content or not. + whether the area is empty or not + + + Defines whether the area already has some placed content or not. + + + + + + + + + + + + + + + + Represents the context for content + layouting + . + + + + + The + area + the content to be placed on. + + + + + Gets the + area + the content to be placed on. + + the area for content layouting. + + + + + + We use a simplified version of CSS positioning. + + We use a simplified version of CSS positioning. + See https://www.webkit.org/blog/117/webcore-rendering-iv-absolutefixed-and-relative-positioning + + + + Default positioning by normal rules of block and line layout. + + + + Relative positioning is exactly like static positioning except that the left, top, right and bottom properties + can be used to apply a translation to the object. + + + Relative positioning is exactly like static positioning except that the left, top, right and bottom properties + can be used to apply a translation to the object. Relative positioning is literally nothing more than a paint-time translation. + As far as layout is concerned, the object is at its original position. + + + + Fixed positioned objects are positioned relative to the viewport, i.e., the page area of the current page. + + + + Calculates the common rectangle which includes all the input rectangles. + + list of input rectangles. + common rectangle. + + + + Represents the result of content + layouting + . + + + + + The status of + + which indicates that the content was fully placed. + + + + + The status of + + which indicates that the content was placed partially. + + + + + The status of + + which indicates that the content was not placed. + + + + + The status of + + which indicates whether the content was added or not + and, if yes, was it added fully or partially. + + + + + The area occupied by the content during its + layouting + . + which indicates whether the content was added or not and, if yes, was it added fully or partially. + + + + + The split renderer created during + layouting + . + This renderer will be used to draw the splitted part of content. + + + + + The overflow renderer created during + layouting + . + This renderer will be used to draw the overflowed part of content. + + + + + The first renderer to produce + + during + + . + + + + + Creates the + + result of + layouting + }. + The + + will be set as null. + + + the status of + + + the area occupied by the content + the renderer to draw the splitted part of the content + the renderer to draw the overflowed part of the content + + + + Creates the + + result of + layouting + }. + + + the status of + + + the area occupied by the content + the renderer to draw the splitted part of the content + the renderer to draw the overflowed part of the content + + the first renderer to produce + + + + + + Gets the status of + + . + + the status + + + + Gets the + layout area + occupied by the content during + layouting + . + + + the + layout area + occupied by the content + + + + + Gets the split + renderer + created during + layouting + . + + + the + renderer + + + + + Sets the split + renderer + . + + + + + Gets the overflow renderer created during + layouting + . + + + the + renderer + + + + + Sets the overflow + renderer + . + + + + + Gets the first renderer to produce + + during + + + + the + renderer + + + + + + + + Represents the result of a line + layouting + . + + + + Indicates whether split was forced by new line symbol or not. + + + + Creates the + + result of + layouting + }. + The + + will be set as null. + + + the status of + + + the area occupied by the content + the renderer to draw the splitted part of the content + the renderer to draw the overflowed part of the content + + + + Creates the + + result of + layouting + }. + + + the status of + + + the area occupied by the content + the renderer to draw the splitted part of the content + the renderer to draw the overflowed part of the content + + the first renderer to produce + + + + + Indicates whether split was forced by new line symbol in rendered text. + + Indicates whether split was forced by new line symbol in rendered text. + The value will be set as true if, for example, + the rendered text of one of the child renderers contains '\n' symbol. + + whether split was forced by new line or not + + + + Sets + + + indicates that split was forced by new line symbol in rendered text. + + + this layout result + the setting was applied on. + + + + + + Represents the result of a text + layout + . + + + + + Indicates whether some word was splitted during + layout + . + + + + Indicates whether split was forced by new line symbol in text or not. + + + + Creates the + + result of + layouting + }. + The + + will be set as null. + + + the status of + + + the area occupied by the content + the renderer to draw the splitted part of the content + the renderer to draw the overflowed part of the content + + + + Creates the + + result of + layouting + }. + + + the status of + + + the area occupied by the content + the renderer to draw the splitted part of the content + the renderer to draw the overflowed part of the content + + the first renderer to produce + + + + + + Indicates whether some word in a rendered text was splitted during + layout + . + The value will be set as true if, for example, the rendered words width is bigger than the width of layout area. + + whether some word was splitted or not. + + + + Sets + + + + indicates that some word was splitted during + layout + . + + + + this layout result + the setting was applied on + + + + + Indicates whether split was forced by new line symbol in rendered text. + + Indicates whether split was forced by new line symbol in rendered text. + The value will be set as true if, for example, the rendered text contains '\n' symbol. + + whether split was forced by new line or not. + + + + Sets + + + indicates that split was forced by new line symbol in rendered text. + + + this layout result + the setting was applied on. + + + + + + A specialized class holding configurable properties related to an + + 's background. This class is meant to be used as the value for the + + key in an + + . Allows + to define a background color, and positive or negative changes to the + location of the edges of the background coloring. + + + + Creates a background with a specified color. + the background color + + + + Creates a background with a specified color, and extra space that + must be counted as part of the background and therefore colored. + + + Creates a background with a specified color, and extra space that + must be counted as part of the background and therefore colored. + These values are allowed to be negative. + + the background color + extra coloring to the left side + extra coloring at the top + extra coloring to the right side + extra coloring at the bottom + + + Gets the background's color. + + a + + of any supported kind + + + + Gets the extra space that must be filled to the left of the Element. + a float value + + + Gets the extra space that must be filled to the right of the Element. + a float value + + + Gets the extra space that must be filled at the top of the Element. + a float value + + + Gets the extra space that must be filled at the bottom of the Element. + a float value + + + + A specialized class that specifies the leading, "the vertical distance between + the baselines of adjacent lines of text" (ISO-32000-1, section 9.3.5). + + + A specialized class that specifies the leading, "the vertical distance between + the baselines of adjacent lines of text" (ISO-32000-1, section 9.3.5). + Allows to use either an absolute (constant) leading value, or one + determined by font size. Pronounce as 'ledding' (cfr. Led Zeppelin). + This class is meant to be used as the value for the + + key in an + + . + + + + A leading type independent of font size. + + + A leading type related to the font size and the resulting bounding box. + + + Creates a Leading object. + + a constant type that defines the calculation of actual + leading distance. Either + + or + + + to be used as a basis for the leading calculation. + + + Gets the calculation type of the Leading object. + + the calculation type. Either + + or + + + + + Gets the value to be used as the basis for the leading calculation. + a calculation value + + + A specialized enum containing alignment properties for list symbols. + + + + An enum of property names that are used for graphical properties of layout + elements. + + + An enum of property names that are used for graphical properties of layout + elements. The + + performs the same function as an + + , with the values of + + as its potential keys. + + + + Value of 1 is equivalent to no scaling + + + + Use values from + + . + + + + Value of 1 is equivalent to no scaling + + + + Some properties must be passed to + + objects that + are lower in the document's hierarchy. Most inherited properties are + related to textual operations. Indicates whether or not this type of property is inheritable. + + + + + This method checks whether a Property, in order to be picked up by the + rendering engine, must be defined on the current element or renderer + (return false), or may be defined in one of its parent + elements or renderers (return true). + + the ID, defined in this class, of the property to check + whether the property type is inheritable + + + A POJO that describes the underline of a layout element. + + A POJO that describes the underline of a layout element. + This class is to be used as a property for an element or renderer, + as the value for + + + + + Creates an Underline. + + Creates an Underline. Both the thickness and vertical positioning under + the text element's base line can be set to a fixed value, or a variable + one depending on the element's font size. + If you want a fixed-width thickness, set thicknessMul to 0; + if you want a thickness solely dependent on the font size, set + thickness to 0. + Mutatis mutandis for the y-position. + + + the + + of the underline + + a float defining the minimum thickness in points of the underline + a float defining the font size dependent component of the thickness of the underline + + a float defining the default absolute vertical distance in points from the text's base line + + a float defining the font size dependent component of the vertical positioning of the underline + + + the way the underline finishes at its edges. + + + + + Gets the color of the underline. + + a + + + + + Gets the total thickness of the underline (fixed + variable part). + the font size for which to calculate the variable thickness + the total thickness, as a float, in points + + + Gets the vertical position of the underline (fixed + variable part). + the font size for which to calculate the variable position + the y-position, as a float, in points + + + Gets the multiplier for the vertical positioning of the text underline. + the Y-position multiplier, as a float + + + + Gets the + + of the text underline. + + + the line cap style, as an int referring to + the values of + + + + + A specialized class that holds a value and the unit it is measured in. + + + Creates a UnitValue object with a specified type and value. + + either + + or a + + + the value to be stored. + + + Creates a UnitValue POINT object with a specified value. + the value to be stored. + + a new + + + + + + + Creates a UnitValue PERCENT object with a specified value. + the value to be stored. + + a new + + + + + + + Returns the unit this value is stored in, either points (pt) or percent(%) + + either 1 for + + or 2 for + + + + + Sets the unit this value is stored in, either points (pt) or percent(%) + + either + + or + + + + + Gets the measured value stored in this object + the value, as a float + + + Sets the measured value stored in this object + a float + + + Returns whether or not the value is stored in points (pt) + true if stored in points + + + Returns whether or not the value is stored in percent (%) + true if stored in percent + + + Returns a property with a certain key, as a floating point value. + + an + enum value + + + a + + + + + Returns a property with a certain key, as a floating point value. + + an + enum value + + default value to be returned if property is not found + + a + + + + + Returns a property with a certain key, as an integer value. + + an + enum value + + + a + + + + + + Writes standard structure attributes to the IAccessibleElement based on the layout element properties + and renderer layout result. + + + + The same layout element instance can be added several times to the document. + + The same layout element instance can be added several times to the document. + In that case it will already have attributes which belong to the previous positioning on the page, and because of + that we want to remove those old irrelevant attributes. + + + + + Renderer object for the + + layout element. Will terminate the + current content area and initialize a new one. + + + + Creates an AreaBreakRenderer. + + the + + that will be rendered by this object + + + + + This method creates + + instance that could be used + to rotate content inside the occupied area. Be aware that it should be used only after + layout rendering is finished and correct occupied area for the rotated element is calculated. + + + + + that rotates the content and places it inside occupied area. + + + + + This method calculates the shift needed to be applied to the points in order to position + upper and left borders of their bounding box at the given lines. + + x coordinate at which points bbox left border is to be aligned + y coordinate at which points bbox upper border is to be aligned + the points, which bbox will be aligned at the given position + + array of two floats, where first element denotes x-coordinate shift and the second + element denotes y-coordinate shift which are needed to align points bbox at the given lines. + + + + Creates a CanvasRenderer from its corresponding layout object. + + Creates a CanvasRenderer from its corresponding layout object. + Sets + + to true. + + + the + + which this object should manage + + + + Creates a CanvasRenderer from its corresponding layout object. + + Creates a CanvasRenderer from its corresponding layout object. + Defines whether the content should be flushed immediately after addition + + or not + + + the + + which this object should manage + + the value which stands for immediate flushing + + + + + + + + + + + + Creates a CellRenderer from its corresponding layout object. + + the + + which this object should manage + + + + + + + + + + + + + + + + + + + Creates a DivRenderer from its corresponding layout object. + + the + + which this object should manage + + + + + + + Creates an ImageRenderer from its corresponding layout object. + + the + + which this object should manage + + + + Gets the total lengths of characters in this line. + + Gets the total lengths of characters in this line. Other elements (images, tables) are not taken + into account. + + + + Returns the number of base characters, i.e. + Returns the number of base characters, i.e. non-mark characters + + + Calculates and sets encountered tab size. + + Calculates and sets encountered tab size. + Returns null, if processing is finished and layout can be performed for the tab renderer; + otherwise, in case when the tab should be processed after the next element in the line, this method returns corresponding tab stop. + + + + Calculates and sets tab size with the account of the element that is next in the line after the tab. + + + Calculates and sets tab size with the account of the element that is next in the line after the tab. + Returns resulting width of the tab. + + + + Creates a LineSeparatorRenderer from its corresponding layout object. + + the + + which this object should manage + + + + + + + + + + + + + + This class represents the + renderer + object for a + + object. It will draw the glyphs of the textual content on the + + . + + + + Creates a TextRenderer from its corresponding layout object. + + the + + which this object should manage + + + + + Creates a TextRenderer from its corresponding layout object, with a custom + text to replace the contents of the + + . + + + the + + which this object should manage + + the replacement text + + + + Trims any whitespace characters from the start of the + + to be rendered. + + + + + Trims any whitespace characters from the end of the + + to + be rendered. + + the amount of space in points which the text was trimmed by + + + Gets the maximum offset above the base line that this Text extends to. + + the upwards vertical offset of this + + + + + Gets the maximum offset below the base line that this Text extends to. + + the downwards vertical offset of this + + + + + + Gets the position on the canvas of the imaginary horizontal line upon which + the + + 's contents will be written. + + + the y position of this text on the + + + + + Moves the vertical position to the parameter's value. + the new vertical position of the Text + + + + Manually sets the contents of the Text's representation on the canvas, + regardless of the Text's own contents. + + the replacement text + + + + Manually sets a GlyphLine to be rendered with a specific start and end + point. + + + a + + + the leftmost end of the GlyphLine + the rightmost end of the GlyphLine + + + The length of the whole text assigned to this renderer. + the text length + + + Gets char code at given position for the text belonging to this renderer. + + the position in range [0; length()) + Unicode char code + + + + Returns the length of the which is the result of the layout call. + + the length of the line + + + This method return a LinkedHashMap with glyphlines as its keys. + + This method return a LinkedHashMap with glyphlines as its keys. Values are boolean flags indicating if a + glyphline is written in a reversed order (right to left text). + + + + Creates a LinkRenderer from its corresponding layout object. + + the + + which this object should manage + + + + + Creates a LinkRenderer from its corresponding layout object, with a custom + text to replace the contents of the + + . + + + the + + which this object should manage + + the replacement text + + + Creates a ListItemRenderer from its corresponding layout object. + + the + + which this object should manage + + + + Creates a ListRenderer from its corresponding layout object. + + the + + which this object should manage + + + + +

    + Corrects split and overflow renderers when + + is applied. + We assume that + + is applied when the first + + cannot be fully layouted. + This means that the problem has occurred in one of first list item renderer's child. + We consider the right solution to force placement of all first item renderer's childs before the one, + which was the cause of + + , including this child. +

    +

    + Notice that we do not expect + + to be applied + if we can render the first item renderer and strongly recommend not to set + + manually. +

    +
    + + the + split renderer + before correction + + + the + overflow renderer + before correction + + + the + cause of nothing renderer + + the area occupied by layouting before correction + + corrected + layout result + +
    + + + This class represents the + renderer + object for a + + object. It will draw the glyphs of the textual content on the + + . + + + + Creates a ParagraphRenderer from its corresponding layout object. + + the + + which this object should manage + + + + + + + + + + + + + + + + + + + + + + + This class represents the + renderer + object for a + + object. It will delegate its drawing operations on to the + + instances associated with the + table cells + . + + + + True for newly created renderer. + True for newly created renderer. For split renderers this is set to false. Used for tricky layout. + + + + + Creates a TableRenderer from a + + which will partially render + the table. + + the table to be rendered by this renderer + the table rows to be rendered + + + + Creates a TableRenderer from a + + . + + the table to be rendered by this renderer + + + + + + + + + + + + + + + + + + + + + If there is some space left, we move footer up, because initially footer will be at the very bottom of the area. + + + If there is some space left, we move footer up, because initially footer will be at the very bottom of the area. + We also adjust occupied area by footer size if it is present. + + the layout box which represents the area which is left free. + + + This method checks if we can completely fit the rows in the given area, staring from the startRow. + + + + This method is used to set row range for table renderer during creating a new renderer. + + This method is used to set row range for table renderer during creating a new renderer. + The purpose to use this method is to remove input argument RowRange from createOverflowRenderer + and createSplitRenderer methods. + + + + This is a struct used for convenience in layout. + + + Creates a TabRenderer from its corresponding layout object + + the + + which this object should manage + + + + + + + + + + + + + + + + The default implementation of + ISplitCharacters interface + . + + + + Interface for customizing the split character. + + + Returns true if the character can split a line. + + Returns true if the character can split a line. The splitting implementation + is free to look ahead or look behind characters to make a decision. + + + the position of + Glyph + in the + GlyphLine + + an array of unicode char codes which represent current text + + + Container object for style properties of an element. + + Container object for style properties of an element. A style can be used as + an effective way to define multiple equal properties to several elements. + Used in + + + + + Gets the current left margin width of the element. + the left margin width, as a float + + + Sets the left margin width of the element. + the new left margin width + this element + + + Gets the current right margin width of the element. + the right margin width, as a float + + + Sets the right margin width of the element. + the new right margin width + this element + + + Gets the current top margin width of the element. + the top margin width, as a float + + + Sets the top margin width of the element. + the new top margin width + this element + + + Gets the current bottom margin width of the element. + the bottom margin width, as a float + + + Sets the bottom margin width of the element. + the new bottom margin width + this element + + + Sets all margins around the element to the same width. + the new margin width + this element + + + Sets the margins around the element to a series of new widths. + the new margin top width + the new margin right width + the new margin bottom width + the new margin left width + this element + + + Gets the current left padding width of the element. + the left padding width, as a float + + + Sets the left padding width of the element. + the new left padding width + this element + + + Gets the current right padding width of the element. + the right padding width, as a float + + + Sets the right padding width of the element. + the new right padding width + this element + + + Gets the current top padding width of the element. + the top padding width, as a float + + + Sets the top padding width of the element. + the new top padding width + this element + + + Gets the current bottom padding width of the element. + the bottom padding width, as a float + + + Sets the bottom padding width of the element. + the new bottom padding width + this element + + + Sets all paddings around the element to the same width. + the new padding width + this element + + + Sets the paddings around the element to a series of new widths. + the new padding top width + the new padding right width + the new padding bottom width + the new padding left width + this element + + + Sets the vertical alignment of the element. + the vertical alignment setting + this element + + + + Sets a ratio which determines in which proportion will word spacing and character spacing + be applied when horizontal alignment is justified. + + + the ratio coefficient. It must be between 0 and 1, inclusive. + It means that ratio part of the free space will + be compensated by word spacing, and 1-ratio part of the free space will + be compensated by character spacing. + If ratio is 1, additional character spacing will not be applied. + If ratio is 0, additional word spacing will not be applied. + + + + + Returns whether the + + should be kept together as much + as possible. + + + the current value of the + + property + + + + + Sets whether the + + should be kept together as much + as possible. + + + the new value of the + + property + + this element + + + Sets the rotation radAngle. + the new rotation radAngle, as a float + this element + + + Sets the rotation angle. + the new rotation angle, as a double + this element + +
    +
    diff --git a/src/packages/itext7.7.0.1/lib/net40/itext.pdfa.dll b/src/packages/itext7.7.0.1/lib/net40/itext.pdfa.dll new file mode 100644 index 0000000000000000000000000000000000000000..039ce40bcee641f8b468d76918ac4b06f998aa8e GIT binary patch literal 77824 zcmeEv3w)eang01^lF4L}hECEZy(gu&G`BV>P+H0*O>en}CZ#Q)WtvRVX_NWF%%rpw zhE(x_;svjupumC)qA22ecfnnYh>Ezns|X@0-u|)*u6JGeKhJx6{9R~i=`Ii)*UZjrBu*UA4GneC*P2Y9!eqqjupTc zb+T6A?fp{)tQr!@L_Z1zH{d7>t-+_~&jr}7hD3DOMnmGNfHP^J8YZ;c$ZGGOQPLHE zh?`I4t2x+(XMd?wp_ICyrwYTm*(p%f`K8Ubeea+4@11Aw4t;mg2O^7qy|VmzO#V#k zeO;%0eoo_$KY8ru_di+w$@TY^&%X8Qol8HS|6jj-@Q>f!)%)1`4?KGB!7ImIZP`^$2E$tRVrKj)5Worr(u+7V8i$I~Xca18b*QcYtHYWDHH&;l zgd%78j*w!0`*c*-WO7((sHPzS3`UcX*wB?zrPch^`RZ+G2ZY1^daK5tUWWEUmykcb z+@y>A=@lkj99VY)mK3n7SprIh%8F_ago|ttB{ZW@mR6QGt1Lg9XM=2^vOqXcUlPts z*RegRS(cv$u|wf}Syg>rI8Zm+7tZsi8`whj1lt~{El*=(O|!Znns6fnm})i3>#u*R zI9ya$fNUeOPZreK1sT*RxIpMyh9QDB780nhM@mm8+uMWzJkwW{&iLzW z5O{ugh7Eldz(6wNy_m5WGZyO^gLSV6dS)!CT^laZGZuym?KK#)ZduqNNXQos>8VSR zN}qvBy;AH?0NrH#2QhvL#xK$1gP<0O>{I8cL-%VH;Zt8%7cH|yUHDY9wdplF4W8Ol z{#OhC*TjA4GckHY*q1(wpM}2kT706WqUK63zts6EYD}ISQ;zxOL%xQwyU@$v>C6s; zm_b=tC@@A2ty>=uiWU{vXQO9bp-)I3s6D+vuX`Q}gwSDMLC#`>daWOMDio{>1f7*y z(DgnnaYsajFulk**dLlrg)2g}VV^%>uft3td4Y;up@83Bk9^}#YNYLcw1vrWb%M-Z zXVv)7@EkN0T|puB=8J$O=@01#b5eIe?$BTZIuw`Bs4aKMQ)r*dAxNacid{1bgH1oF z+3T;H<=3k(hCq`lb{M^hk56p@K474*KYbp1o(~WT)SXpcRJ$@zU|)c2u`g(sqJ1#k z%ogPO8HJGWLK&r?VpmZ?z=qxrH9ac(;!`~sGbm%Wm>wAO0w52$3D}!BLhZsr8>Tr_ zRNxL<9K^6e8MaW<{)wkTg>`|#EZQ$nD>u8e--@|lOTtMk1ep766hK0@0Sc-`1$8q5 zz5Xg=wDM} z8ts-QKtXu(5I(^kSoeiQfC>j<1ctZj<^oeK zjf#Vw4n>|0nqj1+=9(}2{CPIjXw61g!9MiOtH>8^M!fu>&8zUqKmIY$%&I%hpW>&z z15IaIwX^+WU^A<3hCjtm`ywg!kAb_ay7|5%*qyw(qQv(yDElg|4v~odG4PBuVK%o3 zyP*l#$pTkaT#e1itC^REt6ROg&$H(07C#>dJgaxMB50iG7yY z0o}B2VWPmNMs>Zt6G)>`7$tg}9K07MZk*8Q_DmxXh`dt)Rw&`V*}E_d7EfS@i*??ED{ z$D5PB%*_-fZp<_<_51Azs;c(_l=`dt@ljV-Se93sS3Q9Afz#pjoxi3SSysU+)LIo- zz@M^4h@vWYg#6O{>=E*_N4PBCpj>m1{z4jF4cw1G4214azrZC!- zHG|%&qC`z$X4#x4U%h~Nz_a05a89+Usq!5Gv*h`!eb9z*O7kj8ecFi#YN>Pl3yMD$ zu)LtUiO5MqziwD44V`fU_9*K8>B|A_1OWE$i3}S4G_+2pd~?X4=T8HFr+l%K8AFM% z284>E+?HwW^QW(1b1GHMsc9%Hr~B!`tbSc0Ef%8=yZl0?e+~4AUp)X?@Q=xGkbv4XKpf2K1U(HE4Bt*V4kWMpu4M1I~Cu5JJ1oXin#?dzZ z^ij6t?$ytK42kisr4M%ux)W(n|A55DmI3xN21=+L{6oh5j`0gd(g=K0fa z%w%Tx)7P5}#@d}3>qOSF&tv=?YuOyo_U}y?u(-BTd#vz;wsQh9FD7tJtmB71K8YWi zP_?&cUnt?X-;9oLj`{3uSbt+t;tv@NYvxrk^imVx_PvF3IAXdyoFNEx;91ys7-H;Q z9UGCsLxK7wiJMI`tqO5q$}dm+5QVqnWF#su4b_HL6w*f%MEG)3!j?}Phic-&=kIjD8OnRk1?QTg?;!ULnXQ%uf=vh`!T2TE?B#MLc zs+Um~76j)=qP{Fxp%bBCWtFZgN_-_VNFe<#bkjs%6kMbmpy^EAsVH$rnLFRR*;IrC z+;F4GOlrwD&vLtAD>Hucw!-v#Ovm~eh3WU247U8yOrOH^%`EQ%D9a1eeQ@jGcvoYE z>03~#?Kw8gWS0*kicSds9 zAQ%po2X4nfRaije$glnha#T(pk~8|P>`wio9occDF!krmKvzP!-Nq)=SaQJer*CKS zd;su89LJsuu~wYkC@N)nwVQFY#!T2}Imm#3nYEId-UYe~_Z8;Mf#fdjnWKjuzgTwj`odW59 zU{{gy^Za_BDQQ^lDxVGNL;s1FvN-XD?4f29mKSI#D%2zor0+08)z1i|?=%^Z*qUQ@ z9n_+qdKuQhKORm&vjdrC{`3df-BAo*@|6E$rW_{|$%x5z>eGd{vSS{;$$b_4Mqn>Qc554EUmuxz*bZ0;3tJBZbdJ-D& zL&U;a=shmxI;PNvK68P4^22Bq@(bV1s9jYkTvw#SGwFLd0(rC;=zj#CK^`)L!mIX2 zk$0ByW$!WqHJZ0(sB^Zt=hrq%DG(q$_f6P(IFIG&`_TXPKt=jv@;N7czkF8v;jJYh zU7>O6+C|d5sGwZb*X;!rX)tN%_JTR-Ps-=q^rz%=Ui#DWIY0dw`CO3xtb8s^e@;F< z!(#rtg)%I7HvM^42P-r-!8sb6;N0{-OZmL?1M)dP{h)j`mmAe>i`{*q42Nk6O;bJJhe2~TG@9(grg zzT_jQxfK4oca2M-w$e~O>>SAi7H<~v!|8A$Qd{u;{QL^Kdz z@vVG8aozJODw2<~ut@Y2v~wu^b>=B}GYXS7ip%mCw48wylpJIr>kQ7=>pb`d7&LrZ zUiD+BuA7U*K_&`)_^#!eaK%2_EoS+ z7opO{9NGYw@8(ui<*&PyP5dcl?Z?@!!mJQJD%xGlcJuP`qfWj zTNi?FDyqMaVasrA_uEe+Rs92iy0iTD5BZ5O3CDs(I-HR(zx^Ypio@qr?P67SpyL@` zbn$#6q9=}h`0XE~lMWOob;gMnr+xDj zLcjIbQEv4alId|002v+g3%^X60l^Nqj z976u~ZvTw%47&X@G@<w(ye!YVrd8=* zpv<3smfb1|76a7HfuCLo3w1U!IFbHvks-fR4t8Ti!=1o6CDoLd<@?kB$#KpH;1yq8 z6o=4_$jn=F2@aw};im^AEP_YcBux1z)G zPEP*xb8O)d_S`Bn+6hintrJdERiG;6-<+tXJS=^C!eL44`%zVYi_!OfmG4&y+XfkZ z-hH-unpppI^t4YrK1<3tAlPf{)4<-j5{%{60c%{4O zq!#LXjQeWmr2mM9nW*{R)6F7)ApME;y)V4*0;o6(`gE4or=Am)@aEibApIxLY}OJp zhI2q8A$U)`?)}gta_LnT3>RoAlRDh!LQ;ZY&8=vPsy}l$Vg3+UcO7nb-^4Ly*H(mQ zYt3JT=0*BqQ2NZ)-p|uObv96)tx=VC4`pzO(tpW}2whNj22_EJSYBHiF4rTLgmFJC z*N`aT((8YW!OAgMxii>*6Nl6Em@^?ho0zYtJs7T#Yr(Q`IGp|~=Xrq@rQuS@%-b@= z2%i=%wV_KxwgrHyxl0Y5(9g#TqjJ0zepU!6%ED!Jxalqn&#WG%wX4ur9tD;az_LPP z8J=n9p$m*cK0XkXiY?1bGc#OVorg2oaoW&sS!ciVgxt~rAc2ruMY=#I=A?rEUyqA#03{L&Esce%j(i_o-TlFNXc0Umbr_@ zVTH^W4!k+mbf>&4c?0l-hK9Vqg}WtGzseJ_t3wdA1T*Ox#HLO&8SGl~azjq1yB2e8 zYr8O>*xE-ktqNSjU!0zaft>rNqC|3q83{zE|8X(I0}B9Nz|axwmmJR>n#SpH9qbm| zgfN%KWfT>xgZr-vTrMsSuKN;7g6UGW0ntM1KZ$(eUo*JtL*=^LP+C6YZ2BEKP+TZi zs?;@8gosh`6)uy5*7Z182%|%_)tz66IPFGMtvWNDU#L|c455n|_q1*plMmYTpVLdrQ-^HMssp= z6F08JpiT}KG{`wu9`n)_aDu&e*Eawy{_!2*A;WW5J*UR>I^nhUCv%!k8VvtHtvg;3 zr<&|q`#_y(1lE%GUMt~CEW=*8cbAJN-dz?t51{m&ByRK?KqFxcL{wLJFE2w*OC1O~ zHMf^VrA0a{GM(E?YB+gYK=S+39f{?C3Qq1jmv+y z%NLqOiwaoxYiK{eem61)^{KruM8!dSF48j!kIcgd1pKUeL-4{Q^HEgMuuv!FHq6qA zc`$kJ^0ZojR_me|yYL8;_CoeLuMY+Ar<}4yC@Y(BHdtK2F{oq?I>@eK(+hA6R>p0r zShEu4i&@@K>7?c~%yv?9F?D%^LC~|S>+j?k`S$4?xg=atp&u~@QTjUkUBlXv%HoDy zwbvwp=!fwQ$)FGhD!9|^M25u@134NMjQ;3F`kIi4&NGO-DM=nJdWl* zId|Z$Zcm;_w{JhLZueY%7X|PZp>By2;G_*Gc2s?^qBQ$qf~S|H{#hb+hvQbHzeRG*F}oqH;&0p)n%L`_T@*k6%%Lt{^RsfarPta#$7-FReIAsJ6kcgF(yl^yebK1ck~a5b90q z_JJDS>$G6Jl^D-2{(e#IvI6n;amvMm@lbssA}nwPLbWq|+F8gG-`a_#e)FkNK^?A3 z8qwm(8Yfm*pc>CMcOm(T4i9k0-J!3%#-A@N=_IwG7sFmuFouO$b&C^;H4YInPjvF- z(%(GHDX?pSS4mz&ZQ>!*bdJ2{!Igy5v=HIh>k&1xZg(!#dm@(exwG-yNHFeHS(Le~`8?a>vPSyhvgTuwcWzY*oLiLw z=T@b_xm77xm~PCcy;>(i=|)L-W~7m?3}^0f_HYK=<6I`6bJ9(0k9!=! zI5;o8M)LF1XUOM*^qKOxFnyMME=sSJPt16>PK46ybfPG|UMGsv=jgXMo1^CaQHlLA?@-&cJ;D)4T8A+kPy@&e@SW&nBO*h@*%e%p7!8+GK3^hP$R z#B$*H&EWfUC$I-GEj`0Xh37c3jxMU_81UjxB)C+01Ll+K_~IbuaZe;ceIlXqMEL`m z`Ge^eqDd_^qYyQC>FoGnh3QSKc|rI1Yo=V*iEoqu*?O!~pZmybo6(%ShA)T+ui*<7 z_?(;GCZF@tt@7!iEJ-5d!`))2W;w2ts@s{m7`m$i316Ol6omEV+np?4j|Ryuq^hsb zP#f;kgV4ZL7jDmW95cgJr!aO}O z=N*8I!!Czs73J4K??P@vo-;*WaS%_}fDrydS;&h3u!L&E-?jMrC;TnLUwEqu$7_6z z;>fkw1^=B0rCT<96TJV^B`GYyvh%zpGg$%~?Y2P%&u9x-V6+3?@pMajYe0yRy#kqwI|?;*)==YeuC4Pl?X z6DZc5nW$K+Myq!rjYr)T<<+~9daAPMG0@WUHmx|An72)h7T7rMhn}MGU`&#)&&wC7 z${}PqHu%~Z`w|&1F@QdaI{-Y0*q5Tteg!~4*Wa5{|4LJjtIIt|r=ZiF31P_PFgI(L+?Q1 zAyA<@hHU-(K;jW^=215TAx;9Jz}1|Z87l5fW;ByI=<|6fUX9j)_q>aVjGv&Z$P10sItFI9E|?U&OZ%;P&-d52G( zX(v$*3*`&c%%(E7Qyh1Z{+<~2abs^A3>o!__RkNV(XhH<&FVFc>sXR6dhh~Rxg}|( zK7`K>5MfDgDiMnhCE4N++m-s(Hk9q?RbPFt{?6f&tvgybq3%iMLAWIwhwZ&=j6dKq zYv)2=u#hVAPs`TuvVpV1=@kZe4L;w9zjxvQM@q``^72CJ*8+R;+42xzKQyR^Yn?-YW2ZfsYCd zR{nkwdQ~2(45^zdInL)QxA_9xYjO3JJsAzfzuRN%9K0rmWfuVM4QSpA9Wa`k<{kSbf5mshTqtYpb*z>xa$ zO3r(^%)7XT?YGx#t|?cS0b&QGUDKD`Yy3PvW(+@u5KGJ`8k}m z0_%CekZP&F(Pvv1!mSCYi|dK`5x|i8VZ&n$CTTP#73aP&|9fE{t-XyftQRuO`u*Uh4vyeL*xgcKn zH=$&v+KgN=-uw0{KNKNE3Ev@Fi0??U?f~j$!b2IA+##IuX5dCEDY=`G3#)6@7`_5K zQ{9bRzIqM*W4}*1xi_kd#@nqjz&!#}0yH8v83A94cH{` zT!BY?+fni_zKhkF>hpm`)|u+x1pZjy?*xVl7FoUHy#+0*cRT`UTIx17YC(^JZs7P1 zfgct4XuGz$*m4 zTi|^Hzb|n9X>56sz&8neN?^rImLCxKA%VXZh)W@qw+XyP;9Ua0FK|UEYxfGgSKto> z7L>8PPGDT%zX{wni{2e75GkppBGqO!Sd|_uM+rS zfrWEe-XQQ2f!7JVS>Q7Q|15C9Tp2;&+Xeo+z?t({zD?jQ0{<#-&3u;c6Zl4f9~Ss6 zfqxa4Sim;_D)2uARxD&mRNw~%{#xLsMda|*qSvpzXnbW=nR={>vih1iPxuDFlYfxh zL)Npt{jgC-7PIal%jX}(H+`>la;5%*_@3trPHwUPDvVbE6U>%p`i~+v+sSQ4OP&w- z=b`*p`d`QIHp-ue@?Yn_p5IDza-a9V16su79`@hHmx1!_hp#_5TyT zX|cqaVNu>^#nWfqvHa@1FYsFf^5iiSV%eIhKDvayU~uxzFc)OTM(t?6LeuQAbaOe~#rpgWN*3O0Yg=`SQQZ zmoxI`Sbk;xBfO(`a!vV9tEx^5PaU@F4(9pLx7E=kn1+8 zSHn|ejvU#d?r?JC!A)uwLQ>cV#l79EE^~6+hb`(VCr4h|s$LH(%$DSrR@DbP%pCcp zO?}8{DlHoXNTy=3HL;osNrD;wg+%w$zl5sx>^0AxF9ls`H#2*FLDWIXSLxP%S-Qx8(YU z)U!^GE83^dXwh|~QcOMRa`ur9aYa1d_*PGtxhf&Y_*c=J5Fv{u-7`M z4t465YlFM3E7ZeIZd)*79ahy{y6zRh5$lM0P;z=7u2GA6SjT;ML|voSWpV>*Ty2uv zF~zkXRdpA#rSA7Cbx3mTclFv=siT?PtJc0+y)l!!e(h`2?U~%W*1leSJd=Ch+Bc{w zI(*pa&n&uY?VD7clY3&(N7lYY-I1yL#M&FwtUhUJ(dN8EEtT9C)y&EV*1kiHJGr{b z$Dq|_?O@9Tz;yX!&k+m*kFFJ1Pf|n{koucNyw#RJBg-hRUz5 zy-{tD+zsj9&~cgSN>`3`_#{z zTxnJ4>|0g-PR{o(RarIr>^oG6Mh5*2U_KWvcC{I)7PyTTE?RR*3X2Nk3mXvu&N~U$y`x zmrKdPWmQOzBVDMjTehNYse0?Oa{zBz*4*|u=DK?AQT30gJ*qw;@RQQ(fn{6U>eRQE z?P@zi{Sfo6Q@>ocA8r01ZPaqM4+snc{@glq`Tn-&t+wTl!9Uu*JPsTpC^@S3qjsfA zEpLa%dbPmU0SQxM#+8ZztQHX@~y~k+awrv2!?$^rAuX9M^!0mkE%HW7YeKuSTArbU`(~H*w7wP z->htDw|s|JY->NN-Ym861iVijT-MiqxqAPKz3pM&F~GS3n|;?U8)^@$KdcxAEUq3# zuf^5*ZDF;t`f`+Pkdkdu(kbbSkv^&-QZih9uzdh{Hn$C^tE=||zM=YetzmUD%74eFm({;u_?dO*q_68KetUl;ftflmqi zg}~nc#t>RRt>bcESq*n{K}~+!Vc(jXijM1i`)aB>Ztx{)$Uj%0{3bPCb56%izPHqz z-%+UEUUN@-v+u2-)lqdbYLBY-3H$)yeb}{49k;2E*R-JaU!=SY?T@NsNXOK}HJ5hW zq`p;iusx=}U(?@lt%Lc`q~(7K{FT683;eCX-wXVsz&{K0)pFi^fh7VL30y3&T3~~~ zwE{N?Y!%oI7*nsP{erq)cOS}sz2M11(l;&eTEN?U zAFO?{<7Tw~UB^-NNhyC&;4y(;75Ghoj|=>sz#j?xe*%9a@CAXPIw9_`Dx!Rb*gQH@ZnK)7s@vXH-1Po%u)3*l*H6m>eh9B zT-N?MS^F2U3zv7^r=G7o*tt%9tM01KFN;Kc!xyaIi@mF=CvLR@*8<)L`Ff=7=f1u5 zcXx)>{`wm0dC1PaNFS}=4|#c;z&qBY*NQ}3EBJsPP;!&{H}pNKo{*8h zFYt$e_kmCTz4NI0sgyh`@Hv4m2rOr}(){cWx4EYu!V9jl3RkHDP*`vr~&ObSd3e4W6z2z-~o_X)gP z;QazWFYrqO|4rbN0zc&EEKegHmc3Z%=Q$^=${NSJkE#`otUXKMxdJx}>=1aNz*h*| z2RIiaT)md?RcjXm-q^iZr5fMY{kV+GlJ}wf+QydlM*r=NcXY2*i~aYY&1V*UVr^JG z&{$)I)#HuFy2I)xfQ!{{8~3+0`v2Pa&F)rzMbqQmH~6ZXeuVOdrr)9O)~0*fpI6;Y ze@6Neq!-IsXR+Gf^Z;gAU*+prtPVFFREyQyo4NsczFDkpX$tiGM)vD3dcPD;I?cX& zn`(P5#M&Ft>t{{tdv^P8s64;NvWnJh1uO&1v*xe)T}Rkjy5_Q;u=UN#!JY+H{hA{^ zbFFo2uIpK9orl^IcrxYqeWM^^Rwb$!k)#qt+ZzFAsr4wGgLp>}k%cp@AmRk;trB&W zx(0AueGKqgTpgCE*Q-N-*Q@UVzEzF+OK_Sayiu*oE5Td)F9F`Fs`5+J9qK6HUFsRY z52Y9HWN)t!KksiUD1+=mqv zm8kEiVZbNVmjItuJBv%yGwKn*pQ&?al&EJ_K}m^vPF)808}$I-A5`gSCF(Eg6@be6 z7$7J#6Kl8r0kGJr4`c1t{eZKqD@t)k1u~YX1=d#ptE{0}C2FZvG`j>Z|1JisvmOUr zZS|Fxs57ju0G@4at0++$tX~6OV5R4js7+SQ+!D3TdOu)?^(Vj{t9@RH>a%_hxXb$X z{1SDk_56Yo{DA2X7M9?h#5Idb)IRGkfWy}LmG}V|>%Re0)`qGQb;!C0@G9%bV$k23 zdphWE#Q|S$eGTw>>&_*hzg4>w^tY}DywS3jf&SJH0B^OD(BHbQ9`v{J8bE*R z)qqc1{{i@nwPF?MZ(R%cto5IO&sl$54fjVijZ0kObxz!oX#RU47M zMAH50&g~7jSNb~O5%nbCtJRMI->81MeTl`9Z=Ff_pundEsxV8c1YRJpqOGp&NZW_o zzR>nq+fUk5`_lGR?Ps-jwO`ym-hQn8Ywdq*U)j;v(bbXcctgi69iQrWxWnpP(s{J= z{>~>li@NrArMq6&byL@!T|eon>u&4b-TjL0Sa+)Xb=`04{#f^CyC3QPMR#RSN6)oA zPqseO`pecc+IrgdwvDwt*Y=0DneB7huWY}u{S)oqX&>phtK*{`zw9XMZ0v09+|@bM z`G?MjyB_O$ylZ{;X+0G^yLyIu?4Cr=4Ly0|@_U0gyYT#?)cV$+Eah3n{bNC<_@@AW z*@AyU>IwPGZuhlG%D#lra{KOB!Lod3mLlsLmCT2&-&S(`u=OX&&qjI3s%)QWeWN{Q z{k&tA73i92wREG&hE986Y&d$Jit$%<8%75PBMk$?!>VUsu(@SlwEsXfp<;ckLP?~- z6tTL|n`ttcJuQ*=!ANpzA~L!!)}QQ6MN-i{jb}E=h-*wM8QzW$+KG`!yg$16P=9nZ z6|>`>xunA;yMJsX8c#`P^PyBUo`29i)kw16P7KHPs@}uNRCGjj@7*8m zPdN(${_*IrGiLyrU(C z_O>Q>B%=eWf8_81CyAe`KI|kl=^M~}7_{dWHEfN>qls8QFwYcT)=0FR{plC(8qm3< zYTXo*j*-OS%bK$GK<^h3OA2Bb!h>e^FjAqLHT0+O%$AKKsmQQBq=fe}+l7jr$WT<5 z_TufyaMaDaSZdnLOU6B!Ib=#xy z0c=HlNbj+33}MS|D<~ZK{M3Wx;!DS$TQ=*BX(TEL4dq<=Fx+#zuaZ|e- zmXd}Y_E4-pGMw2A-K;a3ifjTSknnwpv1s4n(Wtw|E<4_Pcx10V9P6K0>6H%$FLH`^ zPUd@KS4KH{*VxEjC{4j-V%FV$q_8EKcHZpNxb!6=po25|_Gr=`OY|FJZ5fU@!?|m3 zO*RjM-v$l~IuKv^%yjG?Y#tqrMiSJ)8Z*xVJxSfId(>>c&bu4yGzF!|!Vu<;STY3; z1&d_LdZQ_jV=8UKss!{2yW}=g=&JEgc=tlVdN>@J;jkoC92J=tH@BU`xsgfO)+x)8 zl_c@$j2zk?jSPcf$D&SN?^Z`NJ_J>ZCgjI0vFPwXQgt@(+SAj#b@QIy){8f*&ekqZ z&Y5K6FeDO&7nDkY_Rv>}XuRL((*CgovZ*bx;o+8KYqGV!e`5riS8Rt>39f)p<9*p%zyYy!FW-42H71=7) z(%YKs+ZXMno*}Z`322$^QJ|QN!Pp$u{We7hBV%l=;6!MRL&c{YQZ#Sb9vv2xllx+$ zNwufZ(Id@?XqRmijA|YprnDwI$C9ay(an%BXrclBn+e@enaOynQ8jZ9cG+V5UDevV z4-;V^DDhMz7VjR7wMP$6uWe87w43&h?M*?UG@--JSZZIhuuAt}Pp@j;ny|-4wXTq= zI7jt1f=FjLz+k&E+8Xa49>Wq_SCV4ROyjG9{!%V|U_A)f{)_FlSGk4Z*lugv?X7 zba$`0dD=}eZc8Ma(y4vXL?0FkR*A#<4JGNC=3wQ{g#otp_2~Jg)w*wc&i0%690{22 ztbGYRso1_L(S!WKnZ*K<(`jwkKBFhQ2WbEsd-vI6!vox3ZU>Z(Gg{m+*wUyq399r& z6C<%y3KqoOQQ3EAAA!Xd8=^&r#W>W)+-fVJbplLwb&!0&_VF3a3B!p#GPEaxNOnfOqT_^ z?L_U&{){G~Nf1`6I66wn=?&1`(vz@9qlpyUBX04DCfpvqd<;sRerNU!vH(Y&bVd>f zTH(#Q%Oj;(3S|nfNR!K%%1hjiNPH*@Z*L`is{zo%X^=TWfW4r6H#&9%Mkfjf0uE$K zyRzb`>Ne!SChh{%3Gi1fiNm8bfZ*{Q?t}J$$dg9VH=B39&72-6!%aglfZHA=SArK~1G`2> z4R@){G!)%~tmi=>HV8MLr;422a}<(Wni)(Cjqx317|owB)B}T^u@N~Gs0j_60ej$D zjZhS`l~Z$43(d5;$(rE!L0X~|>do zA~MN-E^(S&fg)_uzGzP(IuISiAz~ogGsIBI6OQGoX^bT7qMSICXbJ!)I?{^xR1|w4 z{y@{&$~Z3Cm2rnL<#GDBt%F*gAZKK6jC`1)-wJCs4Dv;UbA_IUW1wfU4lFNwgp7Q7 zaL5s})YMbfu3!`wmA}Qw- z+&buRRdisxi8eUB%+78>7|6Z?M_%plAB^_XP(bQ%4(@N>o3w|=QdwniIT#fjqlNB` zAg&_Pj%@%2CXBPmj?z1E2rin$OL4Dn-I~M!L7x|uad9`t5naT%I9-V!tWDCome$Q} zk-dU{Mk-au#XC0vzX7W7iFV1T4`Lj}y{RYu}HbVadsnWKJ^p#s5!5upTh1P*LGKIM$L8EQD15E4Kx>Bd9{ zuoqfuZix&hqpHiMWOlRNj>MV>Or_o5IWX$9aeB4Jv6mbh%~P%P0Ai`bQ`>kN%eK)& zJoPf107DP04{=3gO$(^jlZx#i)A4~wVj$zwfs8I&0|DE)50RNTqRp8|frp=Uh!!fDtukXU z9GC=Y`)oRXil@SyrD1Sqb;jvEZX@n>5&`&8$NgmE4iv4~5s#%Hq>jdIJmZYjt6SrG zeVZfv2e+OxIBw*T|rPdtw(iC*s^(0b>?nI}J+3bW<)(Kx- zF?OSaa;-`9=pNh-XEv&OW6(ql!Wv3)v72IuJ*LFeD`(=p`$-pXBg7HFSzELo=uT|W zYC-!4igzWhp$0edghe{zWMf}x+auTroI7~Hic$=5qE2d+K<$g)kU+y`%B-1Di!I^+ z@x#;OOO9a9=`ym>c3B5{9itFSFZ5{av)7!>t|vNZa3Rnp$%N7Kz?J=I_me$jPiI-q zCJB!R~SNgyR3eRgLc^*5kvRcLkQ}}B|GH=$5()^ z2w;J`1{qKwR+^i-A#-=sw)H@PM9>4%{pm7vA7DT7QF9E+Q?x z9Nej=)>YXy9y{jHwx!t_Z1d%CAw;q~kv8uxwCFGw;)F#JOb@R zB|aT1!OIOD0CAOqt7oA+7&LYZWFoA=Ij=W&CyegJgj3v7NFVKR9PW(tZ?|!Z!ok8G zfQs)Hr|CzzG#lwh`U07s%X$LjaN%cF|%24?`6S#JkOFFMH zq%q(b5j8OHBcoUc^tlrYLF}0>hYl_y4Xz{i@&-fq%eoVR`n`f0Ij3RP^&0Mx75?&^ zshS4{S`cEkN6e`c@@ejzcZ~Ab<~@;a86HdSb39;4ON`ocU7=cGjl4I5v@t*@`_2tF zR)~m<(G}xef=Pl37ex_Ehjm1$n}bv!)2vz@?-hy^S1rIC8-Zs+XuI(+M9#SmZ{YHI z&=h`Pt?CmsCOmE$wRSt^PPLKpsufxrgKh^+z(0HsuxZPl9nE_-@9Nv!)!W+LrEvC^ zTh-ABY^l9>KmYRDJ;_5BZi*alJ?6R8bS^JkUx&9fyuE~(=iM}tqIfiiWR#JD#(+|B z?+?`Iz@XgS(QyTJBn|@^GB{D3R7~zs#ev|hA$bIsEuM=%BECIygNB0uZ|#!$b`~FC zI$qAe0ZAx$HqJ%cAS`xL5vl>qg4uCjts)uETKf!1!C=6=T>$Gkm!^D_FrsYFg?ln9 zr-+nvj>u&gwpL#hDfene4d`nbmyD()OSP*0WF!7I;b$BmNTR=W60P-(Oj6V*7EVK! z3B4ge$qQI@g%(X5lPQOzuKXQ<@Ey`&8E%Bb{{4|SFD;PkO^v8w*a6i~YO1K=JnXO9 zJ2r?LCuI)~Cc%gOxV@;DGSIBd@?=!+zVaJ`1mOY{mmaUf$+GF_sB&Wi)p zoJ>YX_9BKHOTo1;rYBR3SYUr5COUg!CACP##h>}+^;dGH%curqw zYL8~RVl1bV+hyxmY{1PKf;!tVu>u}&bV8*w?WR~{C~hZHcmsqu~bD(NZ95%Y31wp;DKZ9=3cxlijhikX&Q zyf=YD)r`Co@0Osv^xbu)R6%W;kOR}$LQxFO{RtcDc!XumBK3|&fC4lkk19PhkMT-? zv=-?j1(VcI;(8$P4f_C%o1{9&O3bS$^S}Y7o>m>FHC)9c;!1QlxWL3QYzJ{SuZE(j zJuUi18KzUL6aolNI6^rOEL_zqtI3cv^P0yyh3d2q;s^k%f;T(KQ9H?vRXQ0}oe^A2 zyW`=Esa2}0YH3QEanCC@rFl=2NvwfM^U@Gh;u(^(H4ZoPNfx-HAtOdLj@tkzs}ZrD zY?4)~Z>^+jvyO?WW}E==VgrtcezV{_S=bUwKzGUVygmucsoL7ty9dZOH-e!Na}_P_ z95gv$vstQUx0vl%`POc}i^A(Ag{yo|d*oa~8E&8tkcl+uWn|1E*in2b1SWt^=02XS zcLN z%;Xti1RFYc6U2qvlBvd0H^pfGU=o25?B1SMx&s+U#}$7%Xx%-nTD|LY9*1jAZnR-V zhtN|K&Dn`w*#o>bFz*l$_QtD&tfLVGQ{Vkb0xAIO`|xR-ipC!+`Fou6{_E(x$L z5+8t|0}mkwE^wR@?M;Kt>B!>m5SR+CnGMgVRcG1BN$re4ZyE0zt>Z&_w$2D%(jW}1 z`fP_swa}R!CV7uya zPGzdOS(GjqIti)Iijt~L+i~R}k9x>N(3;MbBiOxrRC#Y`B_f;5x;siL>>_%YOCHr8 z!|R}ietDMI3)g3WiZmu~XJ{8A{rlw6V1}CVxV0^EFd_+k0&h-IO{p!3XjJ_A4#Y=Q zFOID+d@Zp=KlVqCOyHaYs>dD$6Ldr%;QQdpgW*KGx5C#;q&C5SR~)@pyds6r45GQv z>OB$YTmeC z=@?S<*?|^`;ln7DDfCIiT&?0QualH#SWa3rDsXR!MRnj+RJ|k6Tw|ba&}KHmTVhsQki2Njf!{>(S8z0xSu8p8EqD^ zSlsh+5N;|z={>@6Kx*S?zuG&>wt^I%{h2qK#v`MBhwZ04?pd=*L77COaVkpeH8fF* z>H@C~<(w`ahuUf*(!A*+#AsC~jythYycDEl48uFw#Fr>>uq$>0pMv=&8_$Pya^o0m z$2Q#dNCQRhL#JH3N~al78HNS6;aa0(Y$S$L2)&K0m=CtK0Xjv$0i*hH6^st^@O^?? zmVFnkX^)ql_5jENjVr+cAkP4j^X>;1YOu!OBmEM52cG!JlLe?Cxb|Mpl63Y4m7GwN zVF~PPGD>VV>a#Pd-E#rD(hOt-S`-T&^GO_udPOM68MIE(SzNJ1V5H#!n6Vwn!`QSn zIDOy@6d!<7g`*H3AgRrX1mZNBG>-D~;*1F}7S8eb)ScuZFXi6E=1}GBL2pDKi<+HB zo#to~OSTMS$F!4b4qbGt5GonnBj=<^w-aj`6XYk0x1~I<^W|WG(~A>l%McpJlEyt} z$f=}p&suJiz9<@kyS1Zl3ob_zhp|wt?R1okr|%>*3S-hrowFOiH#4TH)CeG+_9A7g zgDQ$&n!%T7)BsXdDBXv2Qd&hNXQM`4GK4k}{4yVF5jj!({uExx;%_vQ?#FNBG{`uk z(z8KEYQQh+L z=wYJ-ztS(IBPdNt3*a|?9lISwQNL6Usl8~(Ne1xgt?S3~4-2B4Y#4tLR>P1H>B(vO zrJtE>FUD&Sf<wM@kZ^!tMB7s*TuRHU1heT?~RF=uV_^ z*UgEBrA9B0sCn1jAZU?(oZgH!IYO;j2({oRu6E-muyQuhrPRqm$sKPRNX0aNM%5wE zY$%J@2ZYHG!4=v^uvC)EyDNrua(Bm&EQLM|V8JMQs_}bHH#ZPsOVIt>F&hguce zHg~7cY5>?@ftq@hHQ|?B@oTjU4@G6j;ZHasp{*tdrbf(?yJdAv&OuIcZ>1OmI`Kf%ku{7NYK#hDe z%95y=*orc$jXGy{<(>TpJv9@1QbxGJ#D;UaqnI^R2{!#`m8}Z7ZsSC7ePj$e9drSR zryhx0Tr~;CFLmB*dU8)VKAE9P=B+|1mxhz8a%=n}W@}&0mKqdtx0XaWnG0`F%CT0I z$_SW)igNO$xh&^;Q@K`(Tc-W4I7VZNlIB>eONc$y=E<1J2Ghsfv0q_yk;|r3>(o+Y zK#kKVO0-VmDvjxxY+YJalZ7dQ>OwR6OEq1GF!NsgCM2~R1y~JHZBrO%gc{%}c!KHzrp&EbpshTE<4cH5kX&pFSZ-m=U1l9Pz5;1zq^l3+t&Ze{1r6F&+ z+M_a$C9D}JjioDnl9vsWq7?x;VZ&HygV-*LusNtugtdd> zVyE^@e`L~evICHIOCp$4Q`@_Gb#$)wou)b)k#k5M@&uhjl7wY+O6dzVU;!-SZX0jH zMc>P9^uc&O_RchlF{sUWuJ!6JS1&~{6KlA+WDm|sU6af4-fh!bj^}y3r5a;1=49K* z737Yo{IDWxIUfm2+b{-PI5wSX<3G{2Ga6aT4Q&k$yj-g8OhuS2o+sw>IC^m4Ttj3Q zK|vjK2&atz=OAK(Ad_}B`Kf(-646xwP}BALxJSi6EJ<9-pI#KVh*CtUWVsr1H^^(a z>CcgD+S8dz>bx6sqE&6ik0)i6?`ATU?%|!q=uA4Q24gd>cBHd{AcfpE%7q$#zvj}( zhkf~5LSh)?y?j4Wb?8mxW-+C)g4ccS~ zYnt%#PQz~_I$BeHrceS`0|2l)L zv~ZK{IvcwDY$nw5V3>?NqK~S2D%VpxF~RYBH;^0?95dwC>`0YETdz^pHdt?hmw>u0 z18F=C!)C6bFc^%;fm{PQnLKP}(8|h$(A4>Hw4jGT1?BBaQS*{_ivBTeBsqsRW_l#8 zLCD?QHqK8r;T#-?XOZmLrh(}tFW!+eI2#r)j^YGX#{DPpByFbaNKZAkk#`&P(B7Fg zsx1 zTu(4t3DflHn&^CJb;Q7zz7BCC1Zw>I=9 znhEq`jFZjN3`w9_vjIO9-Gcu%VnJkHvZ}d5&|V~2gJDG4TywKy{3n+{hRxm4C*sNF zxZa``72_{tPDBT^cAXg8o}A3to`6|#F5?^K+=ggc4_4x6+1$ zGkbHVViq2i97Y*`-f)mx$Q|{{pldWwt&hwly1ucIJ96$2{?oTV-WIud<#M9d4%yO3 z@A-+zyo0HPQ2Pnlu3L7fN~;^H7lyB>ey9_Tqvnc{SL||khI~IA?{e}z_js9C^Lm@Q zn~{mh`NoTfEB@px9&qU?)5%br=y7mWoQcP>lAlbjo;5R<`Am5eY9nC z%m@eZS0l><54%myI$ma&is0HyN#=;>5|l-c{po)JFnN_wr(+xr=u_6uz|b=8THaQ71k%Fz(Gi zP!*c2VcS!M;N&XSjSXo5Vi1Tvg2764PkfKy#ZWuWlzMaAVP6xjW@wj6gU1bhy2tBq zh!-umeyvAE!^i|Zf|q`B!?Vc@7frrSFW(vra<_28Ilcd6jB1oB4HQ*ujsh)YJ&$3k za0F^cQsW8KnJBPQss#{W}a z+ED|T_nq$hLbHkns6hreIFx(B*NV;M1TIm#!)}?Eez{C-BG=>^mj{dstoMb~+yva* zS905d3tykMfQ0&`>DCNx8Y5z86t|gEM-On@?OmwZGG5zqX__J31-hpTO(e62?((=` z?ws~KHDZ%5t5q1yu$yKQ;^HpMU~}R{eA&AQcaKiaGTiZA>O$NRPPmjkE{XR;(htcd z=(v*`>@p`&?K{y!cfEEJ-^*j!Tj(BcvaekANaPJSmenuZ%YXropd43YZ#JFy;dk-z zbd6@6hTXjdpNIjUxvjy|A=7)J4`^zP@9%XQ&DLk94CUvHq>FSC2k=eAOq3lxE z(ST;J+8e=Z#6JMnfPaPt-;~44u{QMCD_Yk(r!i1DbRNH(0;Z$p`aI5!rFETpy5l)H z-YMO)wjr69n5!}GYUcvido8BDQkp7~jS{OP3u^oZGmMVP)PpMexg_81YR)h%Cen2B zJ$F&0BJiS@%|qM=`tKx6qu8%Cc%pBOzsdNZ+0;5&Ea{?W7DffBy`PChu*SdbN+qqk z_7dr8>tKzJC^2oK%*U5Pl3y!4GrgBW*%aT1x0%MlGg83$DIL7HB~Np|IJ>q<=1UWl z4fm2|GFM9-Euu$9!_p@vgO<|0snW}&F3&xD_CoZ@+uSydE>#lCv|yPdy?Yv&s6Ncj zY8KFE2R)WonRj6ZFYjwHV_)LRTLRZ*as6X=H!0?HmUw}#-wG3x$>vex54?P0Ohnb} zu!~ae-Wyh*oP_$zFYeMNj%Uu!__i4qHkr@H%fE@3dC9CfOUu3CD6)lWpYE8hNHt+L z_TsyEt@Jn&j*;W{xPHR%blqgEn!NzM|86AD=+<6gsk@k7eWn@_>G` zGEp$HPZyd%Mk`V$@_6KKlq;HE)D8*z(;)DXhWkKJ-C$MWf{Oobfc{>OzmyZxtV;63 zqTE<_t~y;h^Nm{+t*P|ZXIig9?Roeov&@KkR9dA$wAzky!WPtV1~a0rHqM*Rd6Vp0Gse1znZ}D_4f}*j0k3^7tEGSf=@P1o3IM} z%oWCt@ce2cIp)|*w4U*yDBrvD#3XbFv=Z` zbj_xl>C+3`c{j|H#mkwKG%(|kYLm~_1G+F^$j~0cE-z0Ui@ZA@I&Rx+KH*u%%h%`OdW9NjmD+)L6Tj6%Q%sGazeMJ}{o%K1T#1+(Pj9PS&F7_# zNgt0&UdfuySR8XhMpN}-Etj8=qYWO}eNP>CXU}IiJy(yc2d(w_cSv;oVXQ+R1E^^z z3By^uGM)Y>=9%f@MlRt#XWNKeZqk?2M*mObF0RDOM-$gD)jTI}B}S)E4tW$xod6@Z z{mH|@^~sFxJP%rpQpniNiK#SM|LJ4L9Wed~xqiBG_J8u?l@{uqsbzU8KF-#@+PX1T zKwp#l|MxC4#5mh#O_bsPDT(O=&FCc_U!(k=tQ^L9;(xzw;gL`ONC`D0WhU}>FD(Y= z<8$c$$qRL+x0BLo;We(oSUKyz`&PCVJZ*{|x2D@1>pEymM^=-%sX) zWm|r44;^jruAQ|LBOK=`tCjfi#O93v@Kv!Iqlkp(1XIp` zac8sKlbOPWzV^#h#l$cVF;sqhdCcF0zqq?n>d@*N{EHs{am#g05C8P1fBfS+Rq&Hn zUV72oGoHRK`0BoUM;52P_lPf05;Q+~6Ex4ngXYdXNM>O?I%sa>eSu)!0u1dBl+N}A zd?>VZx-7rSvPx$QTq&?#;KIBrCB4yzQ+16BOk?X0C4qt3wJAd7dG?#{aaKf;vf$C{l*Hka-$ zB+|RF+*imXyD>2rzp#jl+O0`~wfLN6l{N=KuQQuo{I^cxIRfBbsGhtxYK|IXdL zjWK-g-ak#e;M`@9VYgxMFQetWFCpo?3wMXjZOQN?19SQdcXXc`@T)eL>6=PIO*5_2 zIXGVn@LSF%225)6$%%J5t&`nr9HZdu3_Vdo&2_3*v?-s|yZYP1u0k*(q+<&%)|-g+#G$kG#fiS{w_)-@ zj;AY(#^0jGzw3RF&5`uOg~|1gUNdco`wi=Cd(|MK$4kV&{nvEHU;kp(c077fCr{^# zL)mxFu7$qrRR65r#X7D)1NCyhh@1O(c>K*LbL?CRrpr%LswKmBn5 zeao$5MegGfBBzi!eLnif#KB(;bewiqdV&sE*t4 z?vNVGoB~|zCtqFe`$haqSlPdtl1*+M{qTN&cDmDhi8uN4#e*8>|LGrNx$1BEegE;h z7{2)5@NJ3lH=HW5b46*oUrHVS;HknzNB@kXF=q(vHffWkJu9k!>Aj~;oh(%2kazR_ zM6cW%B)z|Lcyaa@BB&6&E}l-mw0}jSN2`FTL#0#UncFW80#fyj-~9Ldy~FWm|DLfU zqn^xfMIN1_SL^lM$?(R9G~Pe&3K+BIp0bPzohM(KF_zJ{H=^X8y0Tx>)3ZMQ8Nt{v z!{KfupE%K;aZa91IGg*E(b=(dZFXI6U1My($Hz5 zkNCIzvhr0EzmCT(_@DWTnlBbqhx1w-Q|+c>Gby9VK_5Z2-mh1SnjNiHU z%-&<0w#qvG7>5L$x}QC|jIMz;j?@J1ufBT;m@D2iffIj#OMjQm1bi9Umup|Ix1s}} z#$VCvn=H`lom|`J7x!x(<4;Ze4NL6@ajDtdVMHR^4{(1CQ~OJQU%$24$8duFdiqOO z(z(}7dr!uo=2nT4%7`{)(e*J?#xS^3X>-74Yw-6}{9;p`3Fi>2UaT z{(*(@_mScG7@vC7Tt{SIt5U7GH*NZIM;~dZl&FYK-EY>9-)PpUk67mT=2f*u6?$DU zt+Mr+=qH?fqc#3kgW1FQ7U~W zZmE7*`ak>cI*dQ}_p>hU)2aV0-Yd<5PsU;8M68wld$AcF&Nd5LoJ`C@|2p`|`uQ5o zb@~^Twwsl|)PHAe;={q7mpf$kKTVcYmzn{+`au)O-LIGWFRkGpD(9&$Tlr)wGyMjF zzh>lmNJic29pdrpM87C?G^D?e@P9v+^Z(nsw%9nXGJIyd>lttCwLQiqjdrXJN@yZt zYi9O3Rik7{>?8_tZdRHoE@iICwobfRJG6@w*ol;f(ne~k7I;DG6HtUu#RC--kSg9P zTEPQ|i%=;ls!$u!2!vEbTfYCyjAwmG6sS}Z*jan#+|Pgh%bA&T&U|N@wSV&AdC_?r z2{k&FWHQ3J@(nV6>7gDsp6QFyct{ZW=WJv6KCXR97-RV0O-*NICgQBL9~&GJ#WAGVD2}l)L}yq^ zR@#@=e4d~%qQxH*248zjs~z6?>fUiJcdmd z^C{uy&A6Go5ypLI7xO9MT&L@1Tnxvg2_=i(5${Bdv@Yh;t;%{WiEXmSRl6@9~XSPwsf>rMtgk_66^M z;>9;Nn9seg-nZ@A?|=1|r=A-8*1326hyL)7r#HN_WuW)yYpE~)@i#l3`0V(_fgj#? z`Q58iPdxrcX8wb>jBoGVu=(KN5B~avZ~pY{T;`Q+?5k5R{`Q|QoP6a+Z@=*J^>_aE z{WpI;^5nzcSm(c$yZE&?u04O`<-foB>i+9NUHqAZfuGk2i3!&~aU>=R`7@A>Fj6$a zXt-#YP9+RJ)k4H0eo>WLNBV zaEa<9Sf=)ML{Z3fgrGFB7lTuq6*bMVoOEfBpqB7cf%>Epf!rCTk1qrQ6}bmM01`z$ z)U1rlJ(3_(BWNg0(PddmQIT>4)l{R8P(}_1sO=EB^i!-8ijOc-9GI2{qDk5$(@h?j*4thm+QvI9> z{U4Wl2_;HVi6#BXDXbslGbI3k7S3CdDNEuQP;>IA5{wH<0FW!hl&GqGqM8FQvb`BD zX`~3jw`eGFgpCLwN>plnfVd)tDz#B5O`_nw_N zVO%F53jQN*syXNv`Ub8~hoPfjj(SX54;sMI$W(~8q1Ka7LIktWMLBtX&XU71(`%e! zO3FnLUP@f0E-1ATNd}kk3oi8chx~@MQjY=~8U`1Ly@#PCr~{65k*$-qu7i=le9_iy zRwB*31XCl;zbvC2pfR`tu>f=~xB|VW{wH+6z=JD2y=r<=Qb^gA;4#XBKqY_&Mszo^ zErga^a#0aHCueL&hzx=0LJI@6WO2A;VIjn3P)TWEi=Z2}7;%Gi8CZD4do*oH z-(Vvk@|jT6i52Ku1jIswf@1NrN4;iCP19vmX@O^IT4^bxebCE3Q5-(U70D!C_9>;| zbLfhcR<@4;H?O6%M3H&`4ESHoAVEtBz78WM&BLrgkC5dv4_o8zdonUJegzLEsRff^ z3nmFRFPoKVu3;Ec45?lvc#foT0lfn}?VuNl+d(!Z;8sWly~igV80H$#4oo^=x&d!(gH=C?Ro^SWtP#|=#{=T~M6EP(|l>x)!H1652c zdJBsS+YX(#=jzUWueDv%jG{(IvC1!I=+m^aX7GS0zGRdNMZKU?N*+!w{8w#5_uP_Q z@EzUJK}fk^YGzgUJ;ShVucBK8(=t6>cMFc;c)shpWzRDW)6jgYTG4&8TrOK4JZ4e1 ze9fq8u2wD;Tptu!p5dAWzv`6Ws2ZAUTLq`;`lja=%T=dpdqvGE`@Zkl1;et6Roya5 zMPKu6(=SvktKt+qkfm9UYdD%_R*GfYb}iqrD<#`;ZO4Plv3wm_igwwnxS9^~%eGb4 z>~h(&b*E~WRj=szwpKEbvWzTV(f0MKUN$wuaTwbwuuZ1#7`|o~#;vMrj2FwUHD0Y4 z#qo;aX_{BASXGj4zrdu*gf^j1V0Uv_U?K^d95Q!|(PP<+c-Q?+t1&%)cJfijYb-*i zW==QfA6ej|@TV17zq`7HvT?RFpC{H5CQndYv^i<2jhKk%JT@25P@ zy|Xi(b!Jz6u{CR-ug|%)Q{LIdSyI|rR|g{su3>tP>FX73mMhA_X{*@?oz8sI3ti!v z>(K1H{m|?YZ_cw8qR3FS8m>)*-G+w?dyS7Tk~<&!mR}nk=@@%(*L-vL+}r`HewLkC zAXa!2?%W*D^1)`5F|<7QN9hxIKe_IpNE|8rhYHQT{InZq0j1L7 zhWL}IeOM$y%Z}*S#BjDVzC)` zz;?1hgcXXrV>nupO0O$A%~2AXJ|10Tr~SxdFjWgJwE*vUSKG*S8yKSA+Ia zx}=;5Y|0UCe?s9JEIY}64EdWVA%u>g_zD($(8i9mq&1|WqjChY>luy+k#~~ThZ=tL!v6S8=_4o zbyh}L(zdmEw3qYvU{nV!0iv2?o{^812NI8NPWv{7M+k6_q~Jmhv@|AZc_)t<A zo^~a7SMs*X^VGn9THHqH69mVWhoqNFWo16%>uquD6j(n9d9rd_P}3#iBj&)r0O!;t ADF6Tf literal 0 HcmV?d00001 diff --git a/src/packages/itext7.7.0.1/lib/net40/itext.pdfa.xml b/src/packages/itext7.7.0.1/lib/net40/itext.pdfa.xml new file mode 100644 index 00000000000..a1afb4466f7 --- /dev/null +++ b/src/packages/itext7.7.0.1/lib/net40/itext.pdfa.xml @@ -0,0 +1,316 @@ + + + + itext.pdfa + + + + + + An abstract class that will run through all necessary checks defined in the + different PDF/A standards and levels. + + + An abstract class that will run through all necessary checks defined in the + different PDF/A standards and levels. A number of common checks are executed + in this class, while standard-dependent specifications are implemented in the + available subclasses. The standard that is followed is the series of ISO + 19005 specifications, currently generations 1 through 3. The ZUGFeRD standard + is derived from ISO 19005-3. + While it is possible to subclass this method and implement its abstract + methods in client code, this is not encouraged and will have little effect. + It is not possible to plug custom implementations into iText, because iText + should always refuse to create non-compliant PDF/A, which would be possible + with client code implementations. Any future generations of the PDF/A + standard and its derivates will get their own implementation in the + iText 7 - pdfa project. + + + + + The Red-Green-Blue color profile as defined by the International Color + Consortium. + + + + + The Cyan-Magenta-Yellow-Key (black) color profile as defined by the + International Color Consortium. + + + + + The Grayscale color profile as defined by the International Color + Consortium. + + + + The Output device class + + + The Monitor device class + + + The maximum Graphics State stack depth in PDF/A documents, i.e. + + The maximum Graphics State stack depth in PDF/A documents, i.e. the + maximum number of graphics state operators with code q that + may be opened (i.e. not yet closed by a corresponding Q) at + any point in a content stream sequence. + Defined as 28 by PDF/A-1 section 6.1.12, by referring to the PDF spec + Appendix C table 1 "architectural limits". + + + + Contains some objects that are already checked. + + Contains some objects that are already checked. + NOTE: Not all objects that were checked are stored in that set. This set is used for avoiding double checks for + actions, xObjects and page objects; and for letting those objects to be manually flushed. + Use this mechanism carefully: objects that are able to be changed (or at least if object's properties + that shall be checked are able to be changed) shouldn't be marked as checked if they are not to be + flushed immediately. + + + + + This method checks a number of document-wide requirements of the PDF/A + standard. + + + This method checks a number of document-wide requirements of the PDF/A + standard. The algorithms of some of these checks vary with the PDF/A + level and thus are implemented in subclasses; others are implemented + as private methods in this class. + + + + + + This method checks all requirements that must be fulfilled by a page in a + PDF/A document. + + the page that must be checked + + + + This method checks the requirements that must be fulfilled by a COS + object in a PDF/A document. + + the COS object that must be checked + + + + Gets the + + for this file. + + the defined conformance level for this document. + + + + Remembers which objects have already been checked, in order to avoid + redundant checks. + + the object to check + whether or not the object has already been checked + + + + This method checks compliance of the tag structure elements, such as struct elements + or parent tree entries. + + an object that represents tag structure element. + + + + This method checks compliance with the graphics state architectural + limitation, explained by + + . + + the operation to check the graphics state counter for + + + + This method checks compliance with the inline image restrictions in the + PDF/A specs, specifically filter parameters. + + + a + + containing the inline image + + + a + + containing the color spaces used in the document + + + + + This method checks compliance with the color restrictions imposed by the + available color spaces in the document. + + the color to check + + a + + containing the color spaces used in the document + + whether the color is used for fill or stroke operations + + + + This method performs a range of checks on the given color space, depending + on the type and properties of that color space. + + the color space to check + + a + + containing the color spaces used in the document + + whether or not to also check the parent color space + whether the color space is used for fill or stroke operations + + + + Checks whether the rendering intent of the document is within the allowed + range of intents. + + + Checks whether the rendering intent of the document is within the allowed + range of intents. This is defined in ISO 19005-1 section 6.2.9, and + unchanged in newer generations of the PDF/A specification. + + the intent to be analyzed + + + + Performs a number of checks on the graphics state, among others ISO + 19005-1 section 6.2.8 and 6.4 and ISO 19005-2 section 6.2.5 and 6.2.10. + + the graphics state to be checked + + + Performs a number of checks on the font. + + Performs a number of checks on the font. See ISO 19005-1 section 6.3, + ISO 19005-2 and ISO 19005-3 section 6.2.11. + Be aware that not all constraints defined in the ISO are checked in this method, + for most of them we consider that iText always creates valid fonts. + + font to be checked + + + Creates a PdfA1Checker with the required conformance level + + the required conformance level, a or + b + + + + + Creates a PdfA2Checker with the required conformance level + + the required conformance level, a or + u or b + + + + + PdfA3Checker defines the requirements of the PDF/A-3 standard and contains a + number of methods that override the implementations of its superclass + + . + The specification implemented by this class is ISO 19005-3 + + + + Creates a PdfA3Checker with the required conformance level + + the required conformance level, a or + u or b + + + + Exception that is thrown when the PDF Document doesn't adhere to the PDF/A specification. + + + Creates a PdfAConformanceException. + the error message + + + Creates a PdfAConformanceException. + the error message + an object + + + + This class extends + + and is in charge of creating files + that comply with the PDF/A standard. + Client code is still responsible for making sure the file is actually PDF/A + compliant: multiple steps must be undertaken (depending on the + + ) to ensure that the PDF/A standard is followed. + This class will throw exceptions, mostly + + , + and thus refuse to output a PDF/A file if at any point the document does not + adhere to the PDF/A guidelines specified by the + + . + + + + Constructs a new PdfADocument for writing purposes, i.e. + + Constructs a new PdfADocument for writing purposes, i.e. from scratch. A + PDF/A file has a conformance level, and must have an explicit output + intent. + + + the + + object to write to + + the generation and strictness level of the PDF/A that must be followed. + + a + + + + + Opens a PDF/A document in the stamping mode. + PDF reader. + PDF writer. + + + Open a PDF/A document in stamping mode. + PDF reader. + PDF writer. + properties of the stamping process + + + + Gets the PdfAConformanceLevel set in the constructor or in the metadata + of the + + . + + + a + + + + + + + + Utilities to construct an XMP for a PDF/A file. + + + diff --git a/src/packages/itext7.7.0.1/lib/net40/itext.sign.dll b/src/packages/itext7.7.0.1/lib/net40/itext.sign.dll new file mode 100644 index 0000000000000000000000000000000000000000..4f45b9e272a3578bef8183ee418a3cd373273847 GIT binary patch literal 86016 zcmd44cYu`D^*?-{Iy29d-DhWZW*e}yWtf>=%2IYgX)2(gs3=k_SU^DG!5J*D?h3YO zVlSxJF(gKfU4urA1yQ3(Y*AxbHTIIkn4&RJf1h*iotbAA$oKnu-}jGqmHXUtZaeqf zbI(1u&b|vT5{3}M#J}&q7h)^E^f!gw|L&|uaBRSXF>!C`Hv_k7vwt)2pk>Po&8zd? zvH2rcHXnWDs#V^a=A)K0=TBVKynI#jo^$3luk?;t(iV+6LsZuL?_Z8~~Kfd9oUv~cb7c)OHK3skBLqGZF0iV5i z;v1(e{?|YMb<2uH*0w2cA9$7fK)hw-O%H#v-#ue)d3-S<9h+n=i6-8A@bTl62Edcua5 z)l2R=G4S4T+X5GiPA+I1sfj_L4z+T1FCm1J1}E$_;EPrWz!&ZfI(DHJ0qX)8AXzqq zsH+pfIRG0+8iJUFM6O;)B3dB@$FaRSd^nqMAdvLx0iAdt60{n1S*@PoHE3`x+8c}n zW&R;w{v916IG6L+#PA}0xjIE6q_={^NSVk)hR7_@*{|RKV>%nMTI2tk&i;D30aeTx z;KwL1gZ}bh>gqxx&@6~~w*(@BnIBvN(T~jh*b<1^nfVzd&>|3hPGyA-Zsw0IfoLN$ ze{u-~OU?WbN+872%wJIg9Rhz=0--X@{4Yx&+T6_lrUXU_e7gk30QQixzKQ}uT9F_~ zgqfdQ0#Qq|fUfVXuYs?J9WcVo7ZAnkhX9FV9-Lf>;+Q$7RpOW>XI0{u5kIQL zF*h!(#4-Oauf$Qc@>f^lxLVg&;;3%0vBiU7fpf`{Z)N!NC;gIZgYe1lu zMhj%)rfcS=>RptfYf?YY#x2)sO%?_rW!x%;xRwXmbX+TxlJXDX)4U--c|+j@Q}ZLi zfY$===yPpv7`_u$egFOU8KV71APud!6WR-qC)>#`vpvX<9S#iG-Af~BBv{*S7ly;P zE|3hShibx3gAtCGg%b^+FfRvR+?3RX%4FA8kR2*f?V3!c?V(le){8RWsV>ktp{+p| z*WTb<96J}ZlU^&b&m=PA{6U%i63UF0U$2cKovs%~;M)x~r&>fex}h^oy|x<~(##yL zchOhxBBVDGalg}&-)X21(eMB@8`IDyQoEkW$dsx1P7=+jcTo{ZZxnLK9xb!8z0rW8 zp#g*18-uOTso!bgY*fY!dF=>mfgFquwi8+B?#ogP7*(DPiq2l~tL#=%LWcDB=NwIrNg zGyv*mr^f>y4tO2-M1pZA84qW>T*nP>iiW&SAW1a63GlQY4$nk*)*Jz$Bk}Zhqj1}` zmap>jO!nF;&EZoxU*0v!u7%wWS5>w&hC)R4A+_ZCo~J{JPiT9 zI@_(mL_F};#njGn=tGMX&@*ZDlRY!!?E!yX`vTQFPo&ckAvMiE+w3l$GEW=+;*=T@(u+eZ#xx^g;A$HuCpcw@;s;hIeJ9aWX6PYuW`8iaR zy#b;Z%Tak8irfc?G#bLrP`yzrvi)2=JqK0J+4&_%l+mm7^eoC}Hk@9vV)lh6(mTP( zjWxXe;E!hOLLLksa7OpQJSP{---@J>-eibXVe31+PAF%GyaN#UW+D^J2#pQln5gO} z93<6rF-ip z9He__NykVZNZy0sjR&)%ApHlE7oAF_$mzX}K}gg*pgpK_2cp7q+R|&ZpuvKY_U*k9 zH<$|mWa>FA8Mm^7T`SO5r@PjsxE*xuayGryl-fd{LQmDv!Cv7{yK4vYFM%y(5(BCE zU>Q1HJ35{1%?AywuARuwGyJh^meVPyub3lhj}Zp(OTfY$daU&8V!W7iqBPru$cP1J z6yt0ZRa>OL)Y>pM>`X>C5@uR*SL!UN2!XM|^>HndO-PWMyg?zl5k2&VV9q5z(BJ?MflAVVQoCw<&;kD6_U)CZ;uZs-n>Mc3Nn$%y4tL z%z>@tGCMmZz9{+g^i(%GRe88d0mux(xJ6hR8!ev8R1rTcmU}Z7GklVgjTlC`!3zt} zaE{(Z+2j%!HppX`Y%$nr115!?S*I5kB33s#Td~|O@*CbFprsDQpi9$C*a^FcdMKiD z{IY3JeP8M@SHy=%yB^~-Wu4haFC0c?qMcQla@?nKTrQ_iut8Q+RUo(tElw7OE%hcu z_%kYp+6>xVV5kF)ebVY(RE&2x3Jhms0S`kJcw&Q#jzt6 z6LmCp&^!l2g3N>2VR+EnPA(~F`5|Xfq8H{3vCQz6AdZZ&y-y6{xhY`$3m8wX!NV^n zDlA1BsN!SsX5w|7($ay*iS%Iy+LPen(Lo6TedpMM+}6}JlDITBIV$Gbgba8q5GDH-6f}}Jq+yT8m&ISPv$B}p$?+fuG@K89X>rGOlKFcP zXS3_1puP7+YIy7N&r4i=2vB+@5l`RLW2_vA4q4Pt~`D(BoCd;wwa`DxBAE99b-mYN{J&-_~- zc+6V)gODnnN6}@j{&yu$d{R1X=rlPVlTJV<Y*nmFWs)?O7(9Mq z7;?>4!>C4^xJUyd)=Y$ZM@ydEhD3I`U)!t{%4U7I*J(jy%vi#`0(LS)5m|_FyUDo# z`=<(v+MLFPE`-upZsrCR2g95T(3|9kNQhrS;QmR1~vX0%A!)a zG?y)p-U=hYYDtEBkrUY-n2^ybL3Lcz5M4s-Evgq3<#mc^Sz46W5n_+Lq_Ha3yXz;ri6$Qo{AA z$HRo{Q;&}b*QXwF2e>}?iawht}8BwQa_J|SEmT2f))`p~i$ z;rh^0AY31wUQD6DrpS|U z$^00XMU=icYDN}JU%astj~MI25$p0Km_FQOiq{86?97v}`r^%^cztlh;yekkFCJC3 zFOKU=LhXx3)zA0AF%L-4eesAT`93&mo;(S^FWw@G*9S*UmZuKki+2LW>w}|a%u^@v z#rpxp>w}}F%~KdLt|xWtwPV(nT0(h z8G0H~<;o$4mgfIb$H89UKdIR;&YC^s>pY3SNzc zBW0qpt%#eIZB?9_PLPW;uo7W!nwi%XSm;I@GD`2FfSw+}hIbm=7KK?4zbP@9{z*2o zGvf>y=xsn?hR3uSvblx+JSAhZja2#!3du20NJ0R+ zv+$FxVTpGkLyeF@y{iqlTsS`q^)8%=1ncxJie`8zq`(ZB3dx^^P~S|-_BNpxgl+E{ ze4+)+8!=bF0!zpK$TooG6|DSnx?dn&&|8db%SK$xY>fm%Y&mr{Abuil!?285skTlt zs0RaSEdSUEvi*5sFgMsWyU_Ve*G^;E$L86D%Kjy4ESd>eoezRZlt&_F)wNZ{tokW8 z1LN8gZp6n5CTJa%XDgi-5kBCZg=AR1UFC+ov&kDw#zQD+0!or$k!-yikztsZxIxUg zL^fxo;-PE^0T2kMwHNkn1omx2+P7|~wZF;(vzJAPp$Pq?NGM9Y_<&nI`8uI#69*(oc3EN~Jw+HyO^hnSo6ZQZj9AZZ>J)#*3C6vUco9Zxt*%UI;TW zHGhs42j#rFQB-G(Yej~53;Dt{+nJ29$T((hg5tFaUYpG8u+6m!l-s&EmmpsI<`PzJ ztR)-D$|kV9PK_9`x&0*Tfn!#`hjbQLZ7Eipxp6U7tc{d5r;OUn{E+0k+zo1_lr~b@ zGNs<-W3cVxFS@rbhKQDZvwnvw@ML&Paw5s9sY zWGWdbK>D4+#c(!5LOBAXc;n&{pdm1{|Fs%)4&b(v%Ak&vf!%W{VtALq!5a7F_$1OB z;YJeCo-6RRd)CACt|W&iM*V~3g>NnaLn$`sP%kPf5=_sBs76ES!|^Fx1r(3DG(OUp z%l2U!9K))Kp5Bc|P#QmHA-uUHSFM;+QoDFpBVwzT!9rmhK)s8!ASo!vyOy))xsLqT z!wCmk1A6*U6q?b~3+V%$XbVi<#6ZrsZ&L3Dq-wEc4p1n_p=UGYb`zXPpy$U$e>Bju zrRa~FD$-AYX;G1spCc*%PbmRqN{s1*TEarK2j%5>KOBE?Nkl||T0~dYk-^ys_b zg^IZwpKu^I0c)78deFNEAmgwzP#^H_1sGztJ&e%%00vVFBD}(>d-nqz{p+H3xV2I{ zpam68Rbs(1NfWj`yOBmZR5h*)JEeNiJp4H*NG$0l( zkkH&vk&ZuTNEP9>3QOLx8)Y|ca>2AcO$h^O8^eijxQS*v-KZRH(9sLOM834RR_i?k zpBqrbJWNHSS-nT#iJ>vsgM_T=9sv`gP*_pF!cryiSOMvB;<0STjmeUr6=X>vHzZ36 zVgZfvw$v9fIqTmb>lm_*?Ii2nsHbt(RoMw{mCPDrrpy|f zkdljf7)4c~s47`hkXt%AsxerR++tY|O7kgHj1%e;honCw<3LhKWRj!$xkc?D39-0C zAee14Z2b7BG-=$Bcsb-xXv9Da0)3-?MQ%n9^=FlrrAln5$kSlmp|ZtLB&lkO^~r~n z3PQQ?STU!jSf8v&lOU91!E`M1S86N6`xPRzWTqZN{Zj}J|EA*~`jYGv_!9mP_%|N^ zsv*J=eCdzyV*t;sVU4qOu=74f8-JAsF#iW^X#MSV5ZJ(8=iV1&}l&i1x}l+*$~NYT9qg{Pkcrh6KBb=nrN zyZ3~@XLmUHZ(-8uT?CW9waZJ8qhb_5>C@Yhob6LyL{0(4o=HUyku3Ljjzv9&vd)o& zQSzb%?!z>L)SN2`Sb}mm^K2xMzXO0=PP9D?#idB!7bDr3ME>BaVx%7$t+l+gTdiSV zO!t-|39mWJ8nu3IK#*u31Ls&o3+R$X{vH`{k;TPsuKv|X9M%K0v0s{3Jx&Fv4HLA|FLu!rvd#ZJ5jU6Kzin= zYJZ~MXiF%61)*rGdL8sLGYezj-LlD_gurPRk4BKHD z+75#*=eo>B!FF;~jbW!2Y(hY}Dy40&=asCXX#G@5^j#7It;Vb=uLGm4s4tw3a)z-C zk)##SMPahZ)ml1%W7|6I&)9`_I7CDfVsQZc#JW}}SlC@=R!ez<5CYPT9KZo{j+3x3(ZZjJ_C> z)CxSNmMnE=Qh;6~_@IOAqvh@l^K=`sOilVJc%la+ROj#7vhT}&>J31v7M?+Z!mr^- zr6I{vlW5i)zU+%xR%na=mnuRwbytZ^305}DOP$Xu4+k#`&w@bADv3j_I_y{Wl?_ms zw!p%i8ieCI&mnFUM>u{%HA}+b!$00{;Zvt;*oOK&+rPS42VS8#x9~ee*s+Ywz_Mvs zDr2u=8LEiq5rfVA7vO0fOq0+T;agKfBKs12y;W@dQ^zj63}>C6jJrv0WdE5d5{HVa zQvuF|Qx<^(ZExL)FU3Vlr}bd+ zy$4?!cP;FQO!F(G37%ICDN4Uj1sw$^W(MV^<^maKU|g(@nH)()-Cm5EJs@Tl3rLSf zwz;7@q$!1$Q#BHAgV_hJ}Lw#sk?gm^mE`FP1KX-iPpD#@QIkFGuyf zk0?b-FWu8C~}1Yqcv3yQu;c9D15EGM9j1Z@x!(J>C&WUH4q^II~u_bD>PKJ66Z`*<bk^2uHoT2)4byAgDB>`cN-?K~W)KC_=ha5T7D9a;tEnpaX2| zp3mXr%FQBJQx3*J-ag{M>>hd-2lIYQF_?CeWblxDRic7$;#*Rt*$lDf3dGD@KV9x= zbRR|~>aoH z*|Wrmr3f?13y^fO4U+DxK^l!u*mgr=Og%pXwMid9ITIavmvo8wY>}tOB1q5EPL*uF zJZ&;DD|&+=nQk=Y13nfR*iUcFzmw5!$B?-u^ z=KT$!oB=cy+EyO?K8r&Bju7u3aHNcvMb0%<2JcR4gvyVYwTG+NwsUEYYnlG{nBSFXXW0_F{36~m71 zx-9M(J$9FD`y~dZ-7zrjUR*IdXP4*=qo^n(9>nJgabS~#6eR3GbVedNC zeC2LG4+V7Kx{^T3r;8~&&w=WxOkD5Q`^8XSIvw?tc$3@Y{Tl_hCgCY} zn|`|6FMU0@r-D=MmErw$w|{ywg*QS-8{I~?fp1idzM%usiPKip?>jFcl2Z9EOEhlS zfyM#u06&e*ZgcuS#NdJMK<``f3~~o0H^m3*U98YhNhdxePe(=gkVAZ^JJcP*n=A1a zcd+*zg%5LE++n=k2@R1>6*WXEUeob&5TJ+y^+eo8gfr_qFW!N;w43%5H{2bb{+tFF@SwiisQWPi4D8h1 zPJbXp@f=Dv!JXiRshCo8#3$2GiR3*jI7B^#qKeqg5x z(F}*emg5V(T>9G`z8a|7Ir!(mPj)KK;qQwQc$tL$Mz{3n%fe1bvchl~k1ePCGRULu{S?`= zmX>FbFsBtA5GI^HS%Anm!n+V3D|f8rGwvcj<1S;#<3Ow|5l}Mg5RgX+Qi~#>2LKCo zrmwk&MN4b5Er$rQd#L`k_;wN<-j9etvZi2or?Gt?*F`DL2*iQNs5nkfw2z3`Y>8o| zf>MVA1{+=h9H^19Rjs-$WQ^q&11Q^(SnQ$mR#*xn*`T!_7~-^+L~{s?E&?Kly*y$M zW_8lTmR}YU$Z5P7C}9+zamPLJDj;Zyl)5eN)O*+%aRPj-T^?%R#eMIC4wAOtXgz_y zq<1N}fo3(z0MX@$4olFviy~b^{dCFtcAg8;fu{CknOfn0}-kQ?a_Et;*=4b$Vm zfF0_Vov6|cLJ)C|52Z=tT-m(4PV75pfY}F`ZJrB`#A~uk-5Rfk*yW3YEn0aLfd>4* z8i=vR_s2K8aXxcU3{JR-WKSH4H$y$6dpovD2Zfys^1+1782>2*r3}NT^1Azv~>P~jD-AV(%y|ovDr;>2CDKyiw7cLHY71=p(=9=7ZOZIiX(^Ap$LEWetWGB z_FA2^*RW~N_8K4CDbF_IcCjF`BCzXF$F$VT%G8zZwK`O$PF5yVs3o2Te5I1`TqNr9LP?OG?Ptk!n*>GN^)*!S3K9C4(y|8R8BpQ8J`52o@6ZENOzx?obXla|iO} z2dtk^m^4U2CT1Z}@itv$$&J!+Tjer>Ftflpwjx(s+?HfA<%YSrRh?QmQ(t4ovB#{M zYM46=6wrvFT7HGJF2;P!jO3W_x)~btX>5NC{|fj=`_#p;o$y-$)0j^NKoJj+|FY1% z^shMP)7lM|A?RMOyswpi4$8yvEge+oHXj#C9gfay*{c5P(3V_lK-pT{EUE&U0K2B* zsXWJ3lnE>|l>|_p+zQgzO6!}0@SQ|s1;)0R$sM6QTlyd0HL=CN4I!}jA^En z)_J|Yq&KCmE}lLkU0v3yst!A9ZF$11sI`4Xmd>}adZr1R;+Is<#bQV;8&V-l(US_f zI55;BP6}nj%8gVf0qe6>ZoNFu7s}By0U>}qYJUTRugtP6I22pQQK zfuv}8UZ@D%LC?m`AkP+kiQ|?cp)oriiuP=hS+sU8gQGl20Q{N^))FnB_Qk@792At& zZIq-w3Q`+EYNI3->nd_AC0(4t%{$qXFxf)Clseh$d>0}jhcTRE<>-8-Fdp&)P)RB) zQrJBZdE_8fbt!~&wTr`pQc=r8hH=06`_vL?Z#TTzm72Sxtw?QyPRCED)YxvZACQ_> zDq*uLRdlJEq@z6cN(Z=tVyDBsF$XMo3{7Wkl-gn3+ zWi*0R+Qs^@&X%gMfU_x%bYyRkdc0UuQsc#%N*ykTfp`tnCDxCWx~$2FqP~0&Txh~S zOv6ihk@V$K^aV@_#4Y&t)t7&U{-A$leQAhUh&P4p*CQc$Ihe{v;R_p_P4nILt`z`z z_K3~GMpVLUf`c8`lHnN%c>REi>cJ3C+`B1*j!!^?-bHIdt%!UCzMVHRs+2Bnrs7tT z&O(yumg}ISA#AsjR;SZ-i-A_CPO^g;!M3iPKyYbvkJS%C{kGy@C5}GCgD{VYfb^9Z zxm#(*Kx8mP!B}Vv=5R_bhl49wEy-kO$0}+tOw%O9S53byE-eMGKdQ~iutPxHQ@rDO z6ejX=9@Tf^OS365>y^2|nP7mRc@;_&Q@JgO>9mQsEB4!crtvs|i-E=Fl^~xP6O~Pa zaSJCriLIHuG=7x{jN=l{`It0o>7WY4LI1@POX(D7fQuXbQYxA~3qxZ6a2Scql;58YlU~#W%jTnAy z@r{D6r4&H(O&Don4{8fOsKHwhIQG+y^Yis6gC7|wkKH^ngadpk1IJn@lOUhYkmrBo zg5WmZdL;f8_sI6$w^Z)2vRp&JR-*ok`6QvF43wIqZUo5$9gwCA9Tb80z>+wMB#We9_98kG z-BeD}G2}woiD)+s_e}dPoAD?l*Z)Dj)4C>wt?%sNxGYWl;%$vwO)(W2Q`H*yIinbk z*^rjYDTo)&aL(dyrIgrJC4d*~IAKg~DI1ctSJ|D1;rLd@P%Fk@n1)S~=}1x-%rb=C zEE=NILkP0)a7Y0M;vB9Ttf)nYy+~e4pljJ$&bPI~odxpX7!Mj%uosPC!GH2`Qo8i&beIiEcJ?lZG zsbbI>kdY_@^>F#pdADZIyPZ&S(6MLKeuBmn)aM zpU%f*Wr&J>l-|>d7)CxvRwLd6C=!XtBPhT_b&EJe9<|fR2*7cHo#Zmj$VK$@eWI*x)cBRsHS4 zAE}(tz80XRKyC`Oi_D~NmAS}_!{-y|4)S>@lm4LISE1E)zMI~CB73KSQQaq%u_v?l zAi|zf#;z@U*O7M+2HUCKr;}IiCL7%=2`~%Tn{>KYv){w;02#pIms98^{x{TyLB*zL&~>+q1EXolnl_jh#(IOada}C z@RTHvZ=WQDk^z-T2v0Pvp)XNM9-T16IL0ss*~bi|Fk&BLn2+pZHc}Wdkul6o!VoVR z!%Su0sX%cl{AInJ5II6cKM@^C6Nz08q3GWPmn7sGml7C z=7!2Ia~>^?!&z~Kp|)fU-d;=L>{d|FQx!8=o}r`d+z<|BhufT+g%&Q$tr37G;-d8i=Yh;blu{e2x z(g6x+vRyp+sd-QXFyv zLMpP}MY6zQGN>4h1i~MU#6m@*AQ>&hTcD9F7)I5@7)&kOiGdGBC^ac_wtYXpz!q>3 zi7q5@)V?jp@%uI&BR#yk=CttXXPGrwu%#l#z<{JCU>T5EdpJJpEb`^0(31&G78~CO zB6>NdYcHe?b%rSl!B{CTW$FeN5FJw%!GM)m5-vAb#5%jIqZ{A~u;aq;#U?M;MM`!n zKNUkaDN!0pcs!hlM-p*Nny3e-+EUU}Hw9x4xJuos6BaPMOdC<*BXe9gQ|SwoRQsX; zv3|;e9by!w5lf;R-Jb_(#a$$kXqhn5flUBjYMHW=)Zp9B@&FyG*h>0T^vUygnU&~U zgHl$Jj*$--%c{!fgz>raINTnnW(+Jeey!YEhR;FpeCf< z+SG!*(6#BNg}4s?&IPMFR_H`#soVRyJJ$4C(d^}*a=)h_)$gH?q^==dC3_Xt0&z+e z?x*_MtMN`h8o zn`9#Q6Iw+ihKTkMu1S$_#IdnNJoc#D(5$%}Ou`9_jOWW_5e_>9VsvbB$j>%(RhMnkqy&xv5fxnt<|GwXPJ>Z`W0o6_8b}bDOZlrL+ynVtw1Z9a9GXZ-l0QQD`Dk zX!=W`k&+~hByT@d!^3-uP8=_c^2kUAQoLF3Du9Lwf7iiuw^^D%gpqjvpzBiyK1Idc zV*y?mALxtX4pfY3@kK5p-GSi7)6*7F8d;pL#|#*-uzfFkFiAB1DbE*^i=JPVf_!-& zUqX7ApI}(J0!iHg&G+I{&h3kG+EV;dwm$NY_vb7#%_efm)t_hMsGe4?$G>Z7#n=~*joSunxGGlaW1CC9f~GcKbfVn==eczAtlY$;%*CD zi_+pMSz2s&E%p^FO|t*FXsj;EsQ;VdEAl5zlE3+Xsv1@yWq1+bBIXu32$jBw_~s5LJDDs!wztV6?PO|Ro$oBnLi6uUb}!OCJ&m2R z!TVq5A(B*8e*S*4EDxnZcQMHur_kaA*3 zA-0@>+64%#O_RblY}+KP^hcZ>=A@?Bj{Nqay6{|%4vjnB5pZzWJ{Q2V3-GpBtwVL* zAnQig#A$K8i@t>;sf5B&aJw%BoE?sH#YYo#rHV!0^fAEnjf#^Y8!1j2B|F{Ml2`Zs z3Zl9%WPcasS8f<)ZmRGgMYx6{bV-*I(v1we1)>g9fM+;_%*?}_jkD#LhKxk*N(t$U zU3__JhCM(~!4zNH@tjgZ$+{cf+r+N`t0yc^9gAoYieg*RE=e^3rY+78g89v|aHl7-Ep88?D$J2%Fb*So8;akrW`w<0kcw|ECQ^XFIF zKn+8Nt?y&wsu~+t)pFws((J_S=Ec=QAX3coLpoh6z>OiCa;MvAc1tXf!X5vfWyIQV zf#_KdACPT<;Sk(=A=ux(JhXS4<;4TF`OTgU>=-$Wp z7LKDLZif#Co4ZN$3daLOlU82&irIAw>*g0$Qmj>Qu;%A|kJyHXj)SYG2`nNf>E$In zCZp3KMw^ak;6f;7Wb$#ScqkEfz{9%H39z6#W}=d2YLj}=23PrM3T5NDRfsuG1{kn* zA(?m<93hc};X&MO4{J!cnBazDmW+$Gw&T&5?M87|Hk|L@h*o@^S}|tJ=Q(l282eC3 z3Qv2WTRt{`Jt(df3~S$l9;Tfb=U^{upc$)F+>dE(6+_mtTXgskEtFn#UXZjp%lSB?qGvfa>v>UckBx8&^|m?H0g{WbkPodi67Ml zGvq4~B*41qL8(YN<^%pLyLdN**K=2sc-@2W7Co>-UHeL4WnJmPO;oj6ptV%xvUnJW zYG?C7IQ)Wue0dBvTt~sq?1XjU3f8H8i~B|7ap8h^+)*^eso*83!9Gb6)M0bW!~P}? z{k0CrPa)YR4JB{I&?)+)u^Cf|Kasp6W;0?U-tB$jVQNY7VjK_et73kz#0YbcO- zRWe?kP2m(w^~Go(dWYjNklW%`oM{pq_jWv8yl65F|L8!i5I`*p!-mf|U?Z6kN_?2KRA$K9aXtrO-Lo3nq zED#>~epUom)9he6jA^!dmG+H_S83H-RQRQY42DgqPin;QiSiB4edO51H@?}vqKECf zHqoHPW7V5BpWQ|OA{gDCk*y3PyqbPcq7#-@wN&`AB+a)~UuepV!l}3ghzTSod zC@JiDo(W_QZ#1vLh@*K}mgSdan%AR^7Sov&PKCo@!znQrBS#MmY&?fj^t?&*oXv4q zFs+^+0D!C&xdGD(b4hHcV5UY!jR(Hkg>$GpD4b@@f`oT2Ak49-n&}@>2u`I0y&nzPR03I* zK>=P0oRq>MhKCTtyNn|D%g)4N$>rpY)x^>p$x{W-73Aq((?5MBJQexUi927W404Xh zhb_80VA1u1Mb}SSbkc@Tj*4Ithy?|=rQ`H1xx6}ki?;-=9e!PXgDr$fO$n;N+ znkhL8rPgy5XsYN~Btkyc^lU-|Sd_o&gD<|p#AJ}a_V<^8qUYpIRtZFYL z>W7{{ZV{DQuyMf#Lb$bce8HudzHY(jr}ZwH2Tu4oIvm(QGe zM!$+=hsmHM(~(43RH*ANUxB5}pCWDgW;huT%M;k20qDMni6xg~#Oo^wOf7KxV0p)E#HvBKnbzpi#zomlV&xzm~{Pp-MRTSht!$V#;{}$!-B?8yk=~mDX2zj?6vlg4# z0&T#eKMJ=@`xhfhmb4C$mncetU~*qtzCxyCVaVnbvt^i{|0=PJEV&PAsc%h~E`JEX zcZJ^YZUap|WdOtP;J);r<#~04#S-iAvP;OHGdNX-<<>}gk}LeANYS*KMRU;_x3haG z!g1CY7spq?9RE2e@cmu_jKT+qg4=<{ssoiw2ZQC$C*V=Pz={QAbtG4eyssCA1$BuW zEG6y=$Dx*rARLgRAeJwM!d3?5Ru;iGAqT~?f30ZM$MEmZ_@@L8J|RAaj6mgyf8cB2 zOEx-|4fyXt;OO6E1=AUyH2h`xlXT&Sd=!Cqpi&tC#6s^*0?6KPM;-^0%+i=DC#RST zNt1^L1!a365SBYxTlrUsL(s{vNF;U`uswIdQ7ddccN2mJkRpO(+~Y$~?0^haW6Hgh z=01vYKb+)B^U5-NcFsjxGVLKM zoa<=I`RFc~_Wu*+7;G>zQ+PrbEj>xOQ-+N;yoafRkHCTWgMYd>0^fO%KO9JtnUUyu zH-dAR-{73?rI5jZ_bVWsYzI~rq00#sWZMvD!X8CPYcyndkHM3}lOWj|`3$AB;vNTf z)N`2i7$|F|>MNzQI-8M`Zt?>VeBQGACPJv^7=BFtE^_l@AwJ#{R6tng%Xr;{rb!Yd zpfAHT26Ui|39Q#CNZt+P(Dc`+Q|S(!-bH2eH9Xv>8%H{tFS{WxUVKL1v;ZqMV;~*R+t3&TYMw+PSmR;ArRIooj}Ee&`d*R^fU3KZYp7>C5^w+wLZlX%LQHUb zo}v)2!zt8&7Vl|*AyzToGw|}Uln%_aT>AM9z9mZN{w_?K-$?TGxAq-2AvDX^xJs+7*l3dDdPtaUV7W`LCQ~_v7L$boKNj37geyPi)T-KZ}WzI@A%^V zn*d+LlqYN_+wyVP%fLk*cGQ7Vg*Zs1}@QADmIhS^*>^bCZCut3K1V|92w zn3>uH@xULAk!|2D*3*1v;pvy$1id|mGlmVQCo~5p?szWi1r$P@*Sn}~?(Q}wQzZ5Dx8S0$rs=yDw`Lz%F9!cvJDc=Iz|YKo!*70 zPcW1WVd)Np+e2|EdgKSF9cnFW*PG-Ygd?K@XmkpRelsI~J86KJzC`}kpTH|;;Zub( zmu-Uq;=mK==YdGxZ1Gp{V-eM#D~=#lR9gKOVH}&PP=>-2R9q zLERgOh4sc%-TA8si?w#Fl3wRl(Hi8Nl!JVgcS2U$q`rrF?E=Pgey1sGd%cL7oCM8| z4!{7YcpJFXrZxM5!O6>3_yyq|gtW)-lno{8ei#hQ7NOL9hIy)i$LzEF*jP8AocMRO zHt{uFta}tQ=9@gYk~9B0*>LzV(TMjhqRaNfy2wy)ha%AGjVy5VZT`ju$r}0w{leUv z{pEY|ur{@s=V}JY39T=li#UgpY{ZNDVw^C z_K0KrJOK^_dZ;_4rm}(gZCw8Bi&(T{!|u4x}#iF@-i^4 zk4BKHAi2U`QTPB^^Ex%(L)Z)X9=@x5E32>*0mD!7mVd6$_dDb%F=V0$QrqGzbR(E! zcpsuzJi*Q^lMqg-lFen(Ta09Pq3x-g@d^xWImYp@Xh8O>kC6EK;v;XPKSJveFB_0< zG13{O9^hdAOmznAG%dimDXkaUTQ?SXn81*sNB1wN9gd{5-X}ovuNr;|554{L8NNIH zRYSQ>9S{uzilurOh-_;&~a)Uo6*KqJV zjC3ii5v9q9^c$@P-RGg-X$4Jxro6I^0q-yH@Z+Ulz=JnTp+X(+OE`+axBC3-qFPH2 zh+%C-8s+Pm;r)}o{3JmVweY?INbeIc zuma7L!N&dy?`uL+8$!m#QqBVpCsJfcFCG}JMKmzuUla{(X7HUdDyv3(S9=IC<2+(S z4lnW*%SM@1!bpFEB+2je%yajTa|7RX-6jNHD!==440dC&IcSnBKf_vIFxIY3lYl7*KkPO=s(* z_#j(}W$6i!dsXsxLsisx!(7{@xB~O0hA-L8$XYm?nF9W{t(dU0w&mwp4Q^V5jd){@6>vili;m&0WEc~FYl=Ahht3otpE7TBGIAQE#q zum;8PD97-ZgP+J<;e`;z&TpYVivwDiN7(FMQdhN_!g?U;2syBWV(hhKLYd8m%x!%kmJC4 z2{<<1c_V+4{9Xz!erR3&o@^aKP!3*w(bGU4NcdosigRO2r=%MR(_i;KpvD1?bk%9q0azB`MdS~96QL6!akyRXAK*w? zOMO-^26V6r5Ri!)3{@al-o8o?L^g;=y0#WHzfm$9O$BaEgB|0SVm#n^c}?U z5JyU-l%w9}mi%6b=<71PWTn%Omi!4cW23e#0T)tRO9?17yP2C|^0bCuoN1ka*9Z~I z;`wVhJp|H{6}yC@@ZN;ZJ`7x5-cmRpn9mKBtX%^LFj$N zDx9-HlI}+_(i1S8fL}3C`d#r@Of=IP7`4%K7<{X#?p_8lxmh9|aUg@TWiZb|r|QX4 z;5bw}or7<}m*5yCGnL4<7}-{dq|S$#Q6=&oMvg^xzC3Z}05L(5A4%qw-7~5J*?Kaj zKb4pzc29dHX0F{crV=yN?ipK&8DaN~tH9(EI=_{g{|6dCK9j2VhS=W3k484DCD=dG z4M8PI#!dkkGP@{%PyL`Pv5sj6%_o5Dgj1LR!f?afn}~0E zH*$7|6Tt-yPBj^=#@EfUxIPI1SYVor4?ng@rxS4?qA-OJn5c!ksqmp!O(O)oDS-K| zw+8^+KBi*^xe@vLfvP8}2?|bUU=f?@wHe0(Y69s!5glIHGm#foKIqUvuysWKh(2kt z^;t?ANg?iZP8(5agYro*da!l|W#aTafnxH%LN1tXP!q`)gpVZYg#`thl^93s8T3UQ z7zQ|d<>2&K^L*yA%VVG^t_2_S)Yq7D9C_px>7oWn@5fB(dKCUJZ(LxyYpNx8-19ZF z4(7R-L##lFIN#x)@s9W)DBlpV>@Mo*xlZ|&KRPiJ6z4w!^<_{#ffdJnrGF8AZ)BO? zo9c?zzyq)&Fyl$DA|#Zev!1G-xlYfA2jM5ua#6!u3@n=tShm>%Ncl4m>6d}_tqT1b z$slyk(#Pt4jxr&Ad^C7a01x<*6?jL{a)Ys)PM&hs^7@jUpNssJU+<#4ynR4uF%+|{ zawrv{0+_mtm^OExX_|o0;xB@KCyi+v+15U?eH0n2xa)B|+%dc1aCVOnH{iQw0{nB= zxds3YvEoC*B0B2TjBrQcoA-+X_<~Vz?=y89Jsdl&b$?I23Ztmltq{xD{ndC zX3K(Kvnh0-O*v=acf>+_Pumff*{9htF(i-*)QI)~mH2Gno`8)dm;VGD;RUJm?ZML! z^KbAcMLN_Ja>Okm!p{j)4lBclgdK4n`(Fv)gH$I($=$^6Yf+*w6C?MrDoXVQyGyF) zRy$&2b$2y>#GDQjiX+`Qt}W7u|Dg1h?4AwR5mzRt+(#2s?myvo#MmTpV`XxI5feYb zF(^m;JNbUn7Qd;ddd+DV(cp+T8;G`B8ZT_L#fwc;>pA_%bq0RXY>Tl22M@HxY<8Ek zdk(udvil&rzi0Ogc83oN4YI{CgZ6@Z3cF_yq8zSg_jiLH8Z=CM4^m`{FF9t-;LxB3 zu^U`RtQ~y(U|XC$cmZg;XfW~NR(2nUYm1i#Q+?kaOsx|f@{hre$PS@6?Qm^z)DWWi z1a_|+a>fuxd^P04Mn?=DO73p#9>eaf>~3ZE{h<@hnD~C^UxzwkVhiQA58RkIrsc0g zW8yTpws^9IO8l&asM>c}%P?Eyhf!JQuzNn-n7C_L3behznDJ?fxi)=x8q%2NTCn>I zcArnLMf`8me?jQT;V&YeDZ{xfhEsdJ!0w0)#aWo4_If2lt@A0nZkGIMxG^y!OSGMm zB|dD&Yq5sLf3%9M6{4?0y z)XFro`xLuhw!YTt2)m8knQc_B!`q13KeRp8=7{&&x~pyRRU6e>jF{v07rTuZhArT^ zBc{VWo&9&R{}*r_@y-a6(x#Ciyo)zx_=6!CWDsEx^( z*v#%TaFgQiqe57Rym~a1`0Qwk|HkNzkm_JNNl1UVjySfR@W+q&>(HdQZp;N^V&b_m z6sK-1g&s0?%veV(V|OjP*N?T#8u94Z`|#rcFS7d|cEjTc(+JlQr;ppyw#9Yhi0Ai@ zBbr|yN4@4_#*7(Ht+3Dde+-63g4-ZY8($c2i)+T`EnD0^o@C~JxHaOf@swv%$G77h zaa_lq9gdjaNt&vwlQhf?og|Ih;djKV>>3k3>2yT@2@^2{N}Wh9{Z}pOp))P^Lw(X7 z5kod6qSf>=1)v00vMJ`l95V$UK}!@=$56L|28tVz=3+nsIS=8mYdEYPzgI-*e$G$> ztT}=n2GoTAMl$p}KsmgP*$zmEH#p`1tcMWvIYYz5R4zf&sDw1$u%KM}0kQy1=dj&5 zY&h;r5H0&FXkUg7RnUG69jl-NIG0laSt#W|p`$}*hzLV>Gjz9z!p5!^Pa&Np?iV#2_5z1JAd(u@{cR3=L=4u}V;cDdhdly| zoS^R&w7X^@X2_siekBg#Txt!f(PQFZhO&U_#jnNT3{B;*UyCEOapD@Y9q*xE$L!6`p0+1~x=>(=HcI3IYk?Ub=!UyT;CgK=V+@4erOjJ^TwtA;w|+IdC$rnb z?pbh0i3{y7X=u)`5hpvD-BQ#O7 z?T_ag5Up{7PpXxY6Ydfqw(XxyT`!iB8Frf#B6*+~cDWk^ehp*JV9fb$SqAoxo7TMbhq8MDyXUfd zTbv~1L3Uqd_s?)GQJWx_^07pF;xY>|oTxP|v0LIQ_+^VvOI!>8+{BG=k4pRm?z+S+ za4$^U0r#fFy>RbOkc7OR_!a!`C$_w~ zmb!Y=5O>wR9~>wiulqV!Y)9FrepB}?Ft60vAx(T<*NstUZ9}tRh(S%1XSV4l;O*{B z#L4}DDfarK;kU#Jc28pW^rjCTOZ*TqEpZKFwl`e`uD;q72wUOme9O>Aai?_-pvW+axl#PWx=@70#|paBx}4@+v}}X0#}F13&nf6RKrzul zgE*{(SFEc=l{iyDA6qwJE$4QIWZi4T{S0jsRrb#?hucwm#Nrb4 zCU%B?oGpfZg>#U1mmo)L7LO`unLR)oEXL5Z5p>>!N8N^sn;DX&4-VcUmuKGx(=%C~RTib!~6)5U=A&*_|a`u7Ij|{a~YbDKJ)_C7KlU8KBu>h=QWQ ziTb``N(tISpCe9DP$D>6KR{fep#Fdk6t^=Z+i$Me%8)F5F6~L;Z=;xmm~+J*49S=W zQ2_q7TeE@(=w)dC;C%gHF{>1FzCKSZEI~)!0NQM!dPOXPWS@FaeF zPnK{_1vD$TLSIyh`JH`&j$Ls6+iqPS?9z`YL7xRq^Fj9_T{-Lz!87&6;^q=%H|j@< z|0r5!hkmLbC4!SEqYdKF(Ea){v6!Ll+U(Gy`Uo{6mNvS(N7UqF(lhzt=LwAZq(O`)et9=rOTX2+B&gTK{q&Qqf4Bwp!=LX zwNu4;3_Yp+)@d`k#rG=gE4v-gkeM=&|i$3#4!g`OoF~LwurjJ2->b`k(0zvMYDpUk$HfIE2th}H;YjU8jP@;#RLVl zBJ5{knu0nJ_A@bCL3>5MH*OIpD(HZSY2G4w6jX?G>9>lr71YDf1qyl{dEP2EDu{aA zt>PvHMWY1Ws-RYewkha{Xg~8-G5c_;(FSo|G;7{2R+peL<{jczhPG>WMRzyv6z?l& zYjiL394Y_CyyTdG!SPm9qCx(m=VV!DDJsG5h_^+)E5 zqW=j*nXJQ$B3pvy>o1Bi3~kq_gqP?v9R6f3FN^sMorSu`t(V0YD(1Crm;S2wT0ytE z{j67oo~Lx%wFlfG)@#^@kkFHE2GB?az2LT6e-KA2=naN8D(C~Z6JggW=%4OnKsPIh zdg|-qP6Z8)%(Pw?4=N~7Gt+uQY*P^R+c(8?3hG46H^nOoszc1T#5)Sg)|@1I#m5SI zp=u_6Uhqo=O-9(;;u{5hfUvi52vF8&pPD(=J0hl_1vNLB?~0^?mem|$y({`F=+v6S z0HqalQO#28k7BffZbHoW!~_N1hM4b(Jr#6c%}VQiF_!TLy?si4OAnc`z{FGEji!{Y7M$Ks+MO82Bj z+U;X;rGgHK`~YE_6*M+J)A~f*#?S^aEj~hgDvZ-8os_IkMRf_9uYW4)8IqFqnMgCV zL0lZa(E3~)%aD|_&&7&Tx@Qn}N(p+&`it0SJ(aMXDf^o^P(h@3|0b3xNYwUPe;2D2 zl&bC0|1M5ZP;2e`2-~2bJ!((Fj_tV$+Q0S_>mTA`1sz-aXFyjeXl?D^0sUA(KSH{H ziq91^Ha-u~)(rxG+qLUz|80FG9%blB?Uvdb^{>P;D(wDRVSg=NRM2m0ZTny1bp^d$ z8v*pbg1)GY+uw*GXUaUkt$M{pB3eEMM2{IzE?euKHJh}v6?Cxmw_uZYfrNPW*`z&KN_V5) zq`jtM{t>&~{j`}^$z1lYjfeVc=P|TF{JX9`G(elVNrr{%2Zjb}$6OXh@3VX1AN@$q&2t%@`4%fD;n2*=b3=P*_R?r*ub3!?->3Yg@qxfU}k)bwinS$P^ zKOr6ttYVTKkOxe;dVJ(TH=9Hl+k5oO#-8 z1x17XorM}5d8e4s;816g*2B=H;*y3o=P>O&1#M}#94p6L6?9+2Smy}sH3iWu@<{C+ z1wGfWyK|H_X*1<{sra;EPv>ZDrh-J{9A~Naxq|vP&Ucn+d)y>rE(|PomTL!;pk>Z+ z+Cl|QY+UWE)Q(oroW`||r=6&v1&!;S)!KRmi;Z_XCu&2s$gsaOKJ2X3Zu^OZPO5ssS*JaqpsJ={J6+mi3VI^; zf^(|2jUj2>cWcixB;~VPd#wb`6W!XMO3p8OE49Hg*484=SU=m$Ex`@gIV}s@ z=-NxsMvr5t^?CV!D&m;mko3zQw%I6iMKQ0T+NGsl#TCdK>PkuT%QaLwjYzF-uhaaN zdMs`p- zJGL~`U6LLuVg4TJ`%x*$uYFN=S+9=pIF(-S1Jd#{lD;7$jL8lX|M*S{q%L&|sarjY zv`BposlD&qSp#PtTK%89ZLXB^tMc;S78>~JB944s(*G%`rmI^k2+!?E zo$7k&byHsY0r0Owy%!};H6!JJkxd=y9x3OJ53F@rwHGS? ztn}3~N2mETdHG_F`FUxhWt67pN2NrsNU!d>Dz==Ja<0Xpo|61`CDpx1Ar71~ab3?! zxlUPPs7EB#qkT>Cdi?K5UXOoX@~=tyHZOa9P|_Aj1Cst!MvhDVWyx!t-zFu$%Y)|+ z$UBtd62eo7)KK{)OHtyo%Jm3K1RDdA>d`dK4@=39B*ipsjiKD7*wW@=j%lc*q_;?Vm!u!crVgBAvCRo7(diT@ zji*+GO-m{*VeUs8hk8(2ehqISbl`-E^PWP=vn8f_?{s=I_{*VAN&C-Bs>}29y5zJT z6RAUeUGm?M{I`)h6|TW!7lRddB-0rTjwq%cU=IXegcUkoG#SrFSl0IB$=%?3WRS z=H%^`n$y|FQ28x&`)i~;U%%^p9G3E&Y}9hePzO=&P`WlDZIV)Q%e?&irCiHE&DWHK z(07s^m-Zi*RCB?9mG-BRI&o@9+#VHerR9T`LQhEh4zVb5`r0AqhE8=>YFVCN;!@9} zoF|T)eMVBP?>LuD|C(+!9)5r}PMk-Us(PH+@qY8=Y6aeXzE*YOy;o&w3qJAkjKK)p zS)}{aIZ0oV^t`06NoqLcd{9!aq&1SRlC%M7r{R};K+>S3A*4$e?MM28JB)M-zF(JV zK)vhIF4qy{|7lSeNcZ8c5c8|mFE0I`i#AC< ztQM~>FMr5!$!fFwl!NVCrTlVfbJ`JHeH1Iaa`ok??OT1b;~~fX)wiozM||~Q`I7>} z8OH;w_m|H)POmfp%W zE;pP{q2!q32Wy^QRO)P6^H}*qm}MW{6jQu*d)ZlsxwZxGOCz+ij9sD`l;iZ;qxdA3@2=fh zajm*!_0<(e)Va0SRRo>CU%N#`WksWcleDzCMcM?^8*A^W2;datM8&Age!H~%fTZ_I z`y=WKMXx_dm2$~dh3if3B= zVBNP%kEkDG-hiZ~YVFz|c#f!7*1h38qTX2d4DwvdN#$9;5Ajyc^=+YwRg#}o)$40; z?*4k^qRLrSEBV#yuXcP-YR{^T>+5lYmizLItn>w0X@hgm`fWJ0Bp&XSwfsWHtU=2$ zqXsMbo62eDU#@RK)a1(5e^Yr3HI^(&5m<8uYUoN*pdyPYNKMq168w5 zM#;+6(T0brexcTG_^(xG1V3k-fekOX{YKA*uVI8MSAP>NU#~njlT{57;0GhS1t0pV-v@eOAACaLwKe4je0Uf8e?rJd&x;UB+T{Z zah2G3pDUo=yU}l!y5j48;F)%QXk!cBcm93%R+ButMb)^70ZPJsNT1kvtJx`#cMF_@ zlAl%QHrBW(wE(T-)s0UtYH*cp`d9CHwRqFbj#Caw!t;%L%~f-HK}B)vUh z9(PdY_+3qFzHRzliH+Yi1Fow#b-98l`H>k!$vHFRIc?y>t@`wZL_2L2zr%Np8)nMkq&HLTK%MA-&qygO!}EbT58<6xyv<$5g^Z9 zU)lW6)z=EfqJptwu7BQqzM35IyXsX~-QQO>pydm0rvD)&uEl$h|Ndg~j%)Fg>h;aH zEIIC4(m1o^glm0c7sh|S>@HWgLB1faLY`5Ba{IIqXgsQ@N5+hkfb;n!#{uU{OUjX6 zUOtOk>V)uJjWw=^WX4%_2>FvjpC_Hk#^R;3Dr1#+kiP@@w4pifeaO$MyCwawq<@Pv zfZ10q4H%zo9CHVZFG@P&TE8?X<+p&^+m}*b1ecz|jJuHjvax?@NZN;_{jBScjR%%K z>8jik!#Fdp!?<}*dN_mj`_%2m_?BCi-fkS-a{JN`7{`&GaeZvdJxgCg?MIj13kvxM z^!oZ1&i=HdKSX*){dCLMmcHhCWeaG-^~WtwE;r^GUhP$EZnWY~WT$Q?aH2o55 zd{+}?&I22MBQXB~X}rmS_wyWWDp_{Q_1-2^(p5;0HQl;wR{b^F*SHB`jr-xI_b;n) zKZbM_N;E&-y{y4~uIWCcuQWZd%y7H>-*Or5pR8Y3^?>ZxF{8>4DWMwt#OGF|4Oq3y z^%+&-y;-6VQZQS9%b9umh+&@ti6i9;ZNB#e0 zc?kIv%MI5L{r4}YRC&RD*7@_*%e;>Z?R{Hl@2q;>|A*zXs;n7z3*E0*8Z~>|<;_J! zvuZ6$X4OVXHzV~MTbgfI54pBA-|V=>qJbdFPXfYKOH19I&E++QyQjGtXv7iRin+@YW&6|#`&JC?g-Ks+|S7BUJ$5$AyEC+$TVMF@mr&8(_J-> zAf<%AQy}?Ef#k26JB>435@u5mdii^^dILDw5!0<;azCz_wA z3CoTwc2v~9;9l(bS@W|sr`5Msf2xL9{c%l$`?t*xEIZ;})&kAzuBbhLH?>oSlNt}z zPNDBU6?C_>yuX&|J+-syiWcIhU()N5o>9q`kJV1QGc6~P-r4fWTJjOJzxt5Wez=9y z^*7S~Q<9!STI&9C%Wq1~ssG-x8dQ3p_e-_q>botstJhr5w)|u58TCpFEgz$`wX_ag zyal(LXBwWx?B%VTy&CCi2W9&zRonXW+H>kh>zyl3I@h*#fr?sN%U1Y}x3}J|PC7eV zkK(<7gUHV+YS>K<>NwhmAy2D}e7U4&)itfxt>_dSb_)&%9o&Uk6~|oXFw5O51|8g; zYh~VJ?kAf6pB2}NJsE|~`RC=+MyB;sHJcoy*t6=Bt@U-s-KQ|`arZyAUZYL`lWXfb z#TI(V^&(o*&fHy>mKkr68E==7Z?t9vA@VzGaY3GCOxZPj$c>8;O zrA0HY+kD=lUtD^Z?<{uVxbM9Cx9uN6TDtWUzT>dm9`>D3=GI3r*NUyz!M<(S`ek1Y zdO_wC?c4g8uc7F>Ys&iSU*-CN&qPD2k~qXI>LxAmvKK+)$m zJYfclp5FRPzDDbPr;BI}9z&Zk<5-co_3bN>UbbW9i6ZLsBTnAUJXW;vvM12y9hV(d zC&U7ob@X0#pgdT#@3KQHpEM4l+@o$$&l?_AI6}RH zBdm6Cg!NLsNy?j~yj99GQhqziJ?dV^r|{mCk2t=A^y7}#_&ycK+loEv0Y?|o2OWoy ze#UVe>E|3@M*4{3$4I~AC@=A-M;!sAk2&6r^l`^MB_)9JT$x9eIA6xS%G=Zv^^NK- z_0LG}Q9nd_vpS7*qxv5!8&yQok6}fPs#@KKk`?M(NH3N0!|E^5vPGRk$=lVdNIR6e zq!I7`XWwhorb}+a-PBTNXVD_0QAwZkTvJ@?yni*zACvSsN!1#b)JuAWq}NNDk@S8^ zACvSsN!40uFX1Dt=pGc$RybJn!bB<*zF* zsT!>M%c^IpR(iL1TfM#BcYAO3e$@L}?-Sl1dVl71n`_N?m=W`!`BC#T=8EbsRe!bm zYt`SV{ug|C+vgWQz4+f2-?`*tOa5WW*OxrKOamfgPW;bos+_O)fs<(Dmg z_wr9K|MBuyYWCNT*FIeP_1fRmR;_4WaoLK0T=BaVTk6{Dw%2vk#p-UU`)_qWsC%*Q z_jQYXt9+OFcKG)B-s5|~_YL3w^3|@~y>fizdskNDPVc`jdS#I+UsJxZyuEy?{2dkj z6{(7wD{iZ}tK#X3pH#eD@p?sxr^<7==Y;2{o>x3eE7w;xRqm>ER&`c&S4~wN@*eem zzgyvO`;_0OswT(Wp+d9_b>hA(w{8-^0MzOdw$tV%bs5T{pCAq zdTOq!c~8x)HFwuMQuEcC@74Ts?T>3Wtms*>Z^fY%H>~)J6(3&l$cnG5=&jpV_wKr> zx;yIbt^3=$hwHvm=k&QU`rW5Ez(Pa@?_OWjcPS%4xgW%?oP0v)M_MpnrbZVi^RUUB zzWWzwS5-Q{T@z-VJ4fvnmN^kqa^Zg|KG|Uz{+Hvw2LG!NA6g9_S%d!uT;blRHmF9m znO#(m<8NgNeb(z9$IoiGx;>6x)v$iRDj%}Shph6j^&GaIKa6(QJC>GT@2D&9c6`6$ zdWWO3(&(tX-tm>Hzjl1e?8X0H{Qs5X(WTvve_ejP;}bQ5_}}IHPEEJt2en^u{=6G6sw0+6$4BCka5@$mA4#bAAgc#Z z9ZpXsV<~lYTT@eScr3;n;ZmrdTq+?@$`I>GH(vgNRD=l z#$)4YmCf~yjEu&|V|fMR^1L^ZNDrhF$(SCMGwCQ|c!B!)eO0J0lA5sK4-F0k^Rh(a zj>P16WU3>aN{_}=clu}nI=W*=V+F|S6JYbv6dI3=PNs;vec{n~G|a_xhSTBPSO7Z* zLcw%>9RvMYNJ@TgP=j4#tMK zSe***jin|qSWIR2tpI=dd)0MgB&IhfzqId4HI|AblB4lqH87P*$Hr9O@OxsBG)Ql9 z%;K=apf+r?=BF#=p%KU-f^_4nlHmzp?Rr0p^0q*a*w~R69mSAbcH^!X$TS`S)5r>? zQ(y96V?NxCHu99T2BBNX%&lQw7qYbQbsp!UdpeX#O~#Uf?39|EuNvq@pnYIa4Git=4fb@YU7>wl zy^;&{h6Y2y?g6!{tG8=!a4^)@tA=_*dxp9WggU!=2SYnUU3*p6SU5f!j7A~3AP7`v zhvH`_gmUP;z8Nr#geOPS15;ze2~b31L?8=w$5Ux+#?kQTBf z4f9$oKnN5-YDYK~k1$8JvIZ8(lv6AVbR9%nP6zQYHldfUXJlcDg?YfG)KO6vtCCbGm6cQ?tJT2npr1)|TRUG`wzcx5&EKr{hWfjD zI{ocUDk?gQ83Q z3y-PX0J)rnN~<7jYf}vwhRmu`5v>Y(AU-0J8{3fME|rS&i4sky4jn=1hjN`!uz-3d z%t8bMuq(P!>G&9^Q3?x5q>y67UI~q-CLksksG(F%L?*|mY@&81rS*VC0V9bd>;)~N zcS;_ef{|o`4=QYDAQGs9K|PZxB%lYOD;iA@&xEr-oa8jZ;bhaSia}kLJctRXJM#Xa z9aHI8YT!`VuX#1DqX z(=o~tErPL)(i7?gjGGmfoX!%QVvoe52M$jhU{lfz)k!ENG9^n+dQsWOP)B^?P%H`R zgCv0gloHen)VVg59x&|HO;Kv)PzD=I6E{k;d6cuC?6Eb5AP;i_&61LoMJX#EOsA6&n4?%L42|^Egh&Z3 zw6a2m1L?6B5H*&B)Lq`HK0Bm>f)?*V_sc%+9SDMN!z5oSV)_?W3o&;=0Kka_jItYt z#^X7m07GMH44Vh@4zeM;la@drMFL1_E}HTFInP_gOpylR;Ze9pV#$aU8X6BzrVr7^ zqH7l=;^M=T;6V-%T(C-H5-l{W=EAL5iABjySg+`!V&f4yRMzT21GMd;T5(T-U}11s z#=(H@Y(WEJfFj9i{m|G1>?oOGoB}~pG;@R~PejN%FqE{?u4YU#dyd%hnv2%Td(Ji` z`@^jmk5Wbs)0QE3aWb+d7!b0;;sveV*;cCJDH<{HR7W@ptU_ny8#5uP#3)(CB%FeR zodwhjY!dBz!{bl_cpwh3X(AkD)hH95r8c8%#k2j&)r*)Ftw5(o_YcDnL37JG@1IO# z724T|>aw26RC)*8{b)Q%rv*Tvd3Ln99^GJH|AB$74l%fUf_ryCGfad5ZGmUAE0!38 zml%P!cMvYGjLTLmDr||V8m~@hcL{lF+N&*4$D7o6vq~o>y2Dc-Dd@aBr&9~Gj_9IpklD`h8DJXw~H`JmKJQvLK!KO2t|*4e6j}IX>lMy$ac07i5?4; z?TyXDg5DFf*BJ~Z8I69sIQt~)*ZYB%3q8p;pr5Xr|0MeoN`=O0l|XoU~lJv{K8#(gZ;Zh9TzI+O!-WQS*;+-6l|_R+a~Mk zRF-wt)p?+|kA_+AuC9Rt!S3$9s|qT2_U-LCurt)%4T~ljVY=GM59-9Yu>JTQc>(hJ0NCbf8Lt9oMH$aq}wE}6G6UGCH$t2<| zAvB35#)1(-V3ncc5cR<56wF7fj36W;J)QjoMi$O|flvnQjluEpgh=MA;?aZPY(8dP zHeE(ps#O-|(M5T}x1$H2fe{2-tBJ$vP=O;0duMKt8J_t83AP^S3=Rf!PxQ0EzIGwZ zK-P8Ym#z*g)6v&Eh%nZH!TtSRtncX>7zpm_I?x#c!w<5=_SXhOJ<=lBxufgA-mZOp z@Z1;;LleY>_#)mPUW37Gr#vU`z&<}4U8u6TP%hBi9t|Osu0t*1ED}?8(TE+CO~KG8 zU>rPy%kz4NQn^jY_6O#(#G8WGEl#xzni7Gm3l0F|;A(nBsrW&PWVoYA5JoJTWiVZu z#x9TAE?&yAq;pmlrkm3NC554XJr0To3x`69JuV>|gk((7vDGvvY65(c#1gX2vQsVO zvvww8x#*GLpzB6NAz{^bClK9|Fn|^EReuJDC#8doA%>YDGc}8W1+WtcLF)E{3^5GC zRz)=M&-OwRAB_dmb`B<5II6Bfj9n^Iu;tMPMFX+eVJ=ApM<9*o`?U<7#~I*n6zh`- z<%eas?T8_g0`|2W8_8(H9wFM!uAL#$Kgo7rFxb;iPUuo_by6`%8T#IqP{=Q0#0~Id z#f~g!$CTy1?M|m9-Y9N!j^tv9_1YeHeyIe-^83K*)SkEY$n8FPWmn-WsW-vkuLK)I zBRVFYf`cD9M6u)F)`SQyQj9SFS(VRc=wJBYw$_pZLZp+S6t3;Ho8%@;s2 z4hChuKb%V406t}2t9~9I7R33Yg>e8XGsrP%%ta0av+<5do~1cR=il-(lO3a&#*Q9{ z{Nj343LzReIf70B2=QFS4B}a^=y@=u4##kUL+VY?--G9cvnCvVaNiWPXXLQXB}cn* z5o8i?cue>$>sX^PLroY?jw%^w*4t;f{}Pq39Sz1vAy>(2smWo;Nao^}?;GbiESGA9 z!A7Zrr9h4aaR65kj1nFgR$<71bV3IW)zM^pObs)}2ro)}Z$__WCD@yIc+~PWxMuNp z(F$i`kfzcGKKN0J5dxt-Whg&8d_m8OE~?!?GI|ezqIpUY2vLcV5e0N(KhQa9#RxzU z)~P8_rDG4N_(6&3gc;_s=TTAmy?laP6{(X&=SGCJ-PN zAS_t~Of8D0sW={mAB$)mdPv|V%Mjc;o`MKPSRf*92Tu^ZB;(HO(n09Ns=}%irb@}M za{8DvY}q%ZCMDEs+0*IEwUBNT?=i?LzsK-_IInFqYR zN((WxF5rhDE(_GyzQ@wL`omp>ozQn{~&=4?=WvrB;@u zP*perp?eS6K1dZviId$v9-kpR4~fUvGtkwaK&V+SJ`}YprI5J6$s$}s7=ZJ-uy$S{ zh;r`GVs{X*3)jeOV#=_cbjUlkr~iW!6{u@kQ<#;n8eot@Z-PdD?j8m!LGJwjV=Utcsve6E)^b?XR)_o?#H75PAIJh(SR_6wK?KN zXaTe2o~=OrVFWO6J`P(ZCETO4g1Y{!`Z@?qgPeF_r?Z#Gr#K-2-NNXaXO`P{Xc}o2 zSuswKQSN~*3s@va<(4G?GPD?g!H`@v(V;BS!)@Pfb5x#DQ$6LRb1oXp?FH4E6XFf)Ox%soE&E`j#eiyVdZdAKt~cY z1nrY=L`Jz0ARDd7atjgKNLd6k4{s!rM!G|C*r}yV?#2%exev#0WVINwlr;8EV*e<2 zXhsO@#gYXwkJy#t5r|cb9tmzLmj~g%@ztRqMEND=OBrl=H`EK(ak-|k3mh{cEWl>>?@lKp4!;_JV54jUhCmJ&Kj+{W(ZxMbvIma7AB*{*HxX zl2DE$yOK#Bf`J`Jaqa}#V_&<6bSLJl!SZ<69zKzX0`o`VpGK`BnLOVp)zFC)%hTSF z!uEYI4l6+j9TmWxBHG(njc6ep;{v@yBFt1S*WT+<@ODyaKQm+%o$+WD?j3Z zrsOIKb_mi?=(r;o#o<6_*I=+?H%NZVr5Vy{lX3VarXqm zCX%uS%w9T#B@_(?vo0UW5PNQ4MazyY!0<_sB*KI8Cu^=``$zc(q5K zZ-fbQ0Lo1l*^#E~%wlfo%RCn8Q{U$MsuHcs?IZOGv{g#mCJQfHZr7#qz_jv~BDD|M zNdR+-Kr-BoSk%_JY-8;YBN#gJU_}ii@_?047+La#Oe=AI%05T}0uSO*B2;^neYC?k zB(JB4ShKu(o_`&X!)FpMV(~EW2u_Sn&-~ziM;N+EBvmeFA0)#>)hCE@9j6Cj89+!? zVCz$Tm?z3XoARUKDR}MHEI2O2L!%w6Bd;Cgp>RR9va0Q4J4-^#-CV3)#+F$!NLJ5lJOg@B~ap~8YSW^DCDy|aR7Y`BIUVgJu7ktXL)+8m$-$L zl@lCWNLvrs2aZga!QL97X8Xtw1Hx|r+hNHT9_0>sG6whlXe<{_QV1z(?P6a*mvNGE zqA226@93a?t_W!9OBOH^%K(j-PZv;ukWtYDn7F_HO2Vi)SaWpLK25W=+QgK?AyifZ z5JB|fAY$`Ru>^`_RzD1cM`A3pZu05#tbOs=4F#QaaUrn^S3yAq2l6xz7-(WjD@u@2 zR;cV84Ih;K%avp|cpm{pxoo$6fT(>(vgcvdkFN>}r7V8V>W5%I2~`iQah)BT+j}Ks z1L}qLxa&L;!YH;iB61X}j~L@oT|e&h!_UBb91cO2rQ2HxR6kC4=$1)iOW>kdme(Q9 zCa?LE+zPfe{{f7p2@J<4vLSiriGp?}+7T6rZG}s_aHWU#BrK1dIbp3*pG?Y8&j^rc zDchIhMi>n>`#kVNn-Zz8!VK%}@fC)qcR)<)$u6ly7 zfkC1QO>&2ngNa-oXKTbCOiSB1EaXXER* zKOeYOI~z(NhtuN}LlV4mCE&`0SK%pfLq3yfegXN8AJo%}{ zJooC8BX-bX95;9=r7bUMo^I6_up5O0^mWUITum>!+%hP5@Rip;`)GCM zGpGX=b_cinAQXPyV+xO?FKk0z(8VedQOXvKA57q~6|xpX2uKnPwxFP`jwM(I5DayQ z342L2Fw$bi<*Z`5e_*FP9bnr|xIJOtkU-d`AjCuG-;RV)9$6FQg@Eiq>^D}$6ci&Y z@RW5}1E}Ei&ze`Va!MxVJIYFBK3)sY%q#3#TDwFq#g7WvFjp>wvm+4=f>- zhJa<5PfXYR0xJd-WEccDnGk~#@xmm(**wpU90D>D*Of{zmdql8>()ges~fc}%325( zFaG30d2XBYymij=wmHwOb7p8&n4ti!R=neVZpr*!n76@? zRdGezu)Zd|`tjR>^|s>EkoeSSy{b0WvaA{PoRPHwyf)#tMN*dVN%w2UD5~X8?FH;P zJMiC!|3Q5GT_3)cQn@=+FP^)R8^BxanDV&`IovJ88=r^p$!wj#(g0fTMO{$!;Ei*t zdKb#~A=jHL^M;TQVU!SB;ca?KE!ib4y3jKy<$Y+O)^P1D_;0t?LmJ@!R{XaDUtA^L z(!KdY7~CekS(}H&B_Yh%hxb`;!*3h#w;8oY*KT|kTng{Vo>VxgL^^@r1it$^tz!5r zw|acW0Z!KO6h|(M?_sV-`HfP`_HncwS881cO4%o_My!_XUym|eGC-Tdc#SHRneMUWalIOrMn{oy5_Z*-hSfo|iwj6>msU7u#_N?xr?3p1Ez8jL@|vrU$Vl_Sh@ih7 zPsKtQ^{6Z*E(^rnb375;M$FKU5piiAa}Q&&oD(;hF;@hr8AZFKlq|g1yjeDP2*eRA zoU1r^!Csf4Mnwjs9Z|2>-vNT?M$IJVBg)rf{tL~S5XiVyg;<>sXmOB^RW>N~_P3q` zZ`s0ZBZAX0tDnGk5q>jF}Z%?5mJDb{vL5c5YDT$j`byD=XL#Kx1JzaGq` zabQ1R2m|Vpo#;>UB@XOf8gnwkrEk7+Y2%x}UwR~|ZCGdxgv*|lE0&fk7M@G$7S&t0 z1y=T4h+*EE7jVk}zvK#1uI7VA2#tNRdzz*->}2a4lmbIFX^AY$of^lkfHFD;E$+Gy zhRHh|i`*JRueofE*)QFNnlygxU9|UM1QeXWSJYz7nUC3pHlfCa)h5xETQ>~W&apCX zGNKz9Fbp(P0C1_4L~Nx2vc6NVk?Vr#Z`1NsZx~EN(8b%rGO=5ip^LX?QgvI4y~x1S)&c!F|4s(86jKwFH{@BczVUuo;QdN)<*H% znSiOjfdwFAJI*++)`=d@jrdmM8ACQ?_KLNYFn8v9(eZ@OwkmRF${1$H=ZRz;3qbkj z_Nep6xdAxkIHU-=1H>)uk}&$~=bUvySA}~(yxQ>5(y(_VHx`Y08e1F-^Ec!uaZ1LZ zYcPKVw8Tef*oNMP>>I*B6E6pg_P1#5Sf1IQw;qL#0$m=x+8fZ5c9Z4#=t=#}(utdxV@ZPRp$xGR~%b{fz6bz!#~}#`Cz$##&>05LhF27hb#GCr$nN z&Kyl!n40e#u%ICH8gA}g0(BFzuSW$xoZU8Nb2tO;TY9DK{C#12jrDn$-jZ9N?NaLT zwe6|f+nzAyc#HOQEe_*9ft8Gta@Nc>v2*j~8QJ)3bW){u1MC)rdX6J-!FD@kcj-)t z#0uDB@F^s8LVF$e?(saAyXipULNjpQsEn}2;W|LCBL;M5ar0T)#2^oH?vOo4JzPFY z3q~LqU5Kf-qm`ykN;P{YwInol<@ym%^;kFi9TfQL^PGIbuKN^nvn4mhqM=S+DBi+y z=#{*=xRd!Xl19lbO{1ueS6$BkR!{$p%ANLi8V(6lC9eiaNV)PDu*%lQi6zPj=!-I@syQ&L?;OL82)4!Tfsu= zx*%wRJEsk^T$D^37>t|{=flz}M+FplIvS~ChX^>ChqPC;a1unHDN6DctK`Yb^;6ezV5}4khcBFUl`1)gr^K^Wh*qxP$ibmY%hvaLzrNIO`?;tVgEJ zZhKVP#w77_r}N30J3x}vqvlp(teHEa#w}OF)eQ?4Rm5(UZ;NY%GJmx;Q*z&^GHhs> zBfU~gY5$2f8;{9|73HJvLK>qm;;1a;xgqI76>BB0mT94@m6-NgX!j|17~?8me(hYL zt!naXd(ex}Q1mM|RhVia1UW0a(ZX^gMxJXc+x)27LgL)*O&7svn$obC#;ufhY2K1d!2m|nar zF?@AJ$}r1KA757a+)hLmokixF64W+0%rzb-?kg;k2hS37v*B^q8wS283aJN)$5T>o zNV%uHUa^aAg<(97BK(zLur@q7IDWwNxs~CO)C}OW>mBAQ)8~=0Rg07ZHA~FxXjh?} z*}ONnTsb*~nR(2|23>~TtX8=euJ8s;<(ONwc>XZnAZB%#Aw8(%dvqRP*ExzDvf4VX z)=^n+LGic=FwnCeGi*0}Zmggf2-=J+o9omJV1zxo+#Hf}PZ4X)A^qZ$p@-Ol3o1pO z&RKl|7~e4KWD&@_9K;^yEvdJ%#q|Ikg=M+p0*{^5vHt9%K77bN>azzk|0ux^0&Y^A z8(YX7G52$Su)6C!CHmFe@51JB6)Y9-y;!6da2RIT3`>R2U0UxXPJk6`dx@)Fnfs+} z3~l4CA~W+KGGnDO6S_sEGVZA_!uIbr>%G@{ul8PFibY}w{4K@OK~qF@ehPB(xdqi` zn1CAQ5%Wkzy&H6mU*ydrBr6bfk@spB_(Hi1CJrW`X@&A@@qa!3g8(Q^Fud0i+;o*D zX*2V&&LSel442e9 zrL~9qatBx$DMqq0$BL!CguCc-SJz`)Y;Cu_6W+|-#q9DypW9pSVqIR1S?@A4#|w%8 z1_+AF1dsbLKX^a^Y9ifd6>G#&2B1E~8e+JI8A-xii?4({WM=+~WO5^jD02cMnVCBs zMdhxKo;dLiZuk{k(|t@%x{AD++nM`2CJ$j(3^R^O4r69ACDQpmS#<`*@^}&{dg(%B zbW@KkI3ti_IJ?W==~@RUzFcR^WsryL1`p|e-XIrp3!!)zdr*d@-N=V~k-3hUQ%t^4 zW-~|TG@rl9BqLB~X3gsyWwPJMc_5##> zeX(J70`amD_29bh66|FW`S^0+1TWp5GBAu8_n6n2msjN^o$jH>`FbU$5iJp3;>|p% zA<#2DC9~cL3N^4lnXd{O?lUuI(6apDus%atW%(Q3yTL+MIK;j}BHmi$h$T|H= zSd(#)+~tkJWAQg>k_)L^#B@61aghyMFJ=K8vF*YQ=Z5JvT?omA4LPb`t$s6!W#46N zz0inruY%^s{r==m48+kOp$+hW5S7==+@F=o4}g1)l#!`3_d^&QA>AJ#b=|M67!Txy z^rrD*rX}@)lR=zmsxJ}lv}Q`A>N;i}9%`5}^9Y)i`08jTIbrOSc&iKth<>}V61_72 zf|p8^`1r&!WENLB*rLI)QmxFsmZK5dY(|@sO0>vTt;E;-I7-axAQ=@P!YgLRQcjgB zmlT(jprQ;CqYSSOZxAMcyqcM_rR*UhJDWR(N|yQD!okdxlwd>;nT=KZp`yv#c$ErB z^!@0=7nvF+#dT;%9ZnoT01TBWr=4)iGLqRtU@~`jDzgLXv0V7d zYG`w_zX%lIp+V}F7j3Dc6l?a0f_qU!sL#y2D5jWqx*>BvzRyUxGD_VGjZsA70MGcm zCqKVa$-wVjt6sk5owKtwe;p{D{*SkP<=<-q51sQ}zUf!be*c#r`cUPAM_wCw&vUQe zTl>oTilTvkbG`rhv+w-CyEo5NeEISZzV`CY58U-aS>lbKH~;JU)@Fy3I{Jd`FBQN~wb9=w}yQiPN z^89q%@of>c)7y)S9p?0Qvz(;83T5VWS4nxX%bX6Hesj9VTvf8foZdrwy;vkNUa{_K zRc$Pi3fp#9=JXJhRGqtwZA;25a~-Btm*FWB*P zf$2%Asp%<6yL8bs2-}>#!<-H|8j3w|7p9M}q5Msm)3@SbTK?|vu)KuNC6&b`Xz2&M z<{ERAHfQ~H<;Ad1IKl_9jMJx+%}0I<0gp%}PL~ z;2)}GdDf%(%8Q+*53UAh^;ttd?k+=*$4fO@k%M^af+dF+8p^VO?NSDJquA;3c}mce zKnz(x3@rjh4e3$go&cb8Zmhnp&Rq;)1UC&{fJcTC@5NEA%0$~^3@jUb>Z@B<4B7&u zLs&dBlnNpnGN($Uh!79?>PpDBFM6j>6(h3;S()xd07C3MUQ=v2aqz43%3*fPVkvU# z>gpC3gBppE$4ktBg$SVEaMZ~_X92Ot3~*h005o8x2jjvX?ne!ToC{R$aTbHVVJ&7( zh`9)ylU?9`KyI01X569i10gJvLwZ7DQ?BFBS!TGwUvP)fzM;6nOv84P-)M<#@%r4D z%Ia#p6m#Q%%S(!j&8Tf1V`1~!0kI%k5In{H2WgXI3#PkP6_=7$Gaodkzb@&MY?c3C z3{ILp1J8@%T0ApA7_I^vdQr|PN#Uz1i0LKX>1SmDMB4N@b|o$-SfEU`{d@u-;EA3b z94L4PqSeqy(+*#`G2t3OmUXk(!Sq@+W$eN>nG=*ql935tN#osf%T<=S{#DYm)<|3WoOuy_d zCF{NnL_tMDzt`1~ZqhZySgBAPwi!E!#HK*(=_a$uoY26`mKH-HvyOBJrVq%tFVXG* zaU;Z`X**yZ!FpfPZ9=*-RBbzax)P4!^!Y`A=T@jwh?9^OfUg2M)2~qmhp_dp;;#<1 zn#VKGQXfn~hGNmzm7(t>GGIb$!$Q`Eh4NV_WU)|4xaV2n^g8tA=q28uFx8A()^Xgk z8aoZH)cV_J&d{+Bm`xh9sNfF!z*~+b5V@myAPTT0LZAT{Pl6nuUN~Q7yoLwz7?waC zvFodIL7@3csO2D%x65XDJR+I*Q!+!)g2J&-bEZZsiy4{)=FBSVX$@C1(?EQzk-eGT z&&g-nOsSdPFRcS;4I-MM^fqIej}^aD7)100^yrYCIgZ`hj|V@^lBqh#UjSigK-RDl zO+wN{TF%q}JN&~2z>Xpja)6mtC|e_c4Y?W)WzK9Sbh=f*oC%mQC{hSgK`9VY4D$(z z4-y$iS!T?8Ot23v}ne9Rfz$Z3{6o|gGbY{A!{Q+Q?G@sH4>B@YvxDkS+ zj>3derj%R8VtJ*m;FG81!%$<>gk* zOHlS~Q>_CCW*e+(dKD)T5vVUVXRbr+qg;LqNN9EcGz z4}l7+l@uRgF=3gBmoK8nHnU$xV$fv@Pm|?M2HN832;2i&Iac#qOBU6Gu=iKUkBl(w z$$Efi05|3J8S*teIxOxbQDe-=E*vX`+6Z$6VOatA?>bpPIUGkq!N-2k60(_-9<12Z z)c_wMA^dp=bHNV^uq*eK!+w~V=8PDfBgtRpq!l!p*)Im=45FZrP#-K8KXuleIgSSk zrOX%LRN>`ec# zy%_45b|I`4M2pND^ILl01H+-q{aU)*=UxH31;Lw4erw%S$9yTSE-fxEDI5>}B0Wov zhk7Jlt~)a;tG`c7u&2z-Q@%1ycTZ_A^{KKl^SZYc!xDKA_QVl5-kGO37by8hw>fIAJ`JY>chl|oJC zjAZUGXKt0YcSu{%i`1gf!nTP=H%VVwU=97Q_Hrouvt58J1WYz(qNB z?+%!0ctDF=vGHB#{eUHtk{ zt2h@JrQ}lWGkFTT6}CeR0DWZ>o}|-(Jt-@LnE^E4hZii@K?ka=#Dy}nb0GW$9AT9R zS;g3W+3!g7w$vISq#v5oGP|X>*?D{rNQ(V71ku}-kH}-DFl4S04+=bq~2f! ziTXBpHq|f|ZiC=u`;lSIaLOwY~B_$*FczRv+=f|^a)*3T!FIN0_GIz znd>rd3ze8XW=sobbccmri3u}b!hD%iK&{VB3!`n9p=uj@y9To#J-x~LsG#jfTN|4U zHRxHKt>WjF;v>oUF$z`eQ=hG=Z>=;`nf?GBe$oVPjPOJcB2{^$w0>7IF*#A+yAO*o zlv~R12025m&3=M({rX5ly+07xwz;{<-(Ekr+jQ7a)7Rua;<%CD+?d*w{Q%YNO-;?M zP5f!yCNBZ~)YR18)GP&7eOq%|ORK-d&z4fo9R9ZrH~VAJw&9kMh(F>7Anh%IrocA; zNUXVecsRDzA8rYR12Mlp+7fAw#73ggXnQOcXbv;lN1C)^K=hq$LKhn!=H2bEK&$u(h>)csLp!i41RT8;(SWBQfM6 z;SoQ&v<|n&wnm%$0Ka`W+}89zROP??kE__2UF%TvrKq94 zw9`OwzH{fFed5$PcmHgtiajEiT|0%3eFnulEif#HasU%TG(c zHtGi&TB6N?SR^pw-`aGokO@BRFgc;K_*(3k&goRXY5v;8@O!Ssr*Pv#|Ex+WZk&h? z6R`h;KYxsA+!3^uBgoe;3}hAS=KbIklaigIqdoZG8U0P+u~=giAKS>EH`ZV*#OnX= z&;K_uV94e!$HxT}u8)kWc-?wlIos{V|J4~o)$>w@^;7S}n-A1J+@d;wly6!Xz+EbS z!pZ@>_Ntx8^XIFsU;PJc_W|GC$9vJyWo{A;lxkCJpEH83jgh4Q*I@7M8G8t<&~ zXSMVilzU#h1kL-e*&A=t;&ZMH1~mYazWUGGx${T5R@(3&S^u`;9w%_1u249@!tF4A zv;r?YkK?9YFJ5`yj6ZSQ?X%x?wF^-2f;4Y3_5x=9T%mBHmYuH?ZSlnyXvep#D_nZ#4E&fB-iXbuJb$kg-Y!GD7Pg&> u-MP5kCVhgy8*dQeyGVc^-Y9;neuCKl49WF|DhC`*_$Y?|`}6-l4E%3@$pf|k literal 0 HcmV?d00001 diff --git a/src/packages/itext7.7.0.1/lib/net40/itext.sign.xml b/src/packages/itext7.7.0.1/lib/net40/itext.sign.xml new file mode 100644 index 00000000000..71ee652616e --- /dev/null +++ b/src/packages/itext7.7.0.1/lib/net40/itext.sign.xml @@ -0,0 +1,2444 @@ + + + + itext.sign + + + + + Class containing static methods that allow you to get information from + an X509 Certificate: the issuer and the subject. + + + + Get the issuer fields from an X509 Certificate. + an X509Certificate + an X500Name + + + Get the "issuer" from the TBSCertificate bytes that are passed in. + a TBSCertificate in a byte array + an ASN1Primitive + + + Get the subject fields from an X509 Certificate. + an X509Certificate + an X500Name + + + Get the "subject" from the TBSCertificate bytes that are passed in. + A TBSCertificate in a byte array + a ASN1Primitive + + + Class that holds an X509 name. + + + Country code - StringType(SIZE(2)). + + + Organization - StringType(SIZE(1..64)). + + + Organizational unit name - StringType(SIZE(1..64)). + + + Title. + + + Common name - StringType(SIZE(1..64)). + + + Device serial number name - StringType(SIZE(1..64)). + + + Locality name - StringType(SIZE(1..64)). + + + State, or province name - StringType(SIZE(1..64)). + + + Naming attribute of type X520name. + + + Naming attribute of type X520name. + + + Naming attribute of type X520name. + + + Naming attribute of type X520name. + + + Naming attribute of type X520name. + + + Email address (RSA PKCS#9 extension) - IA5String. + + Email address (RSA PKCS#9 extension) - IA5String. +

    Note: if you're trying to be ultra orthodox, don't use this! It shouldn't be in here.

    +
    +
    + + Email address in Verisign certificates. + + + Object identifier. + + + LDAP User id. + + + A Map with default symbols. + + + A Map with values. + + + Constructs an X509 name. + an ASN1 Sequence + + + Constructs an X509 name. + a directory name + + + Gets the first entry from the field array retrieved from the values Map. + the field name + the (first) field value + + + Gets a field array from the values Map. + + List + + + Getter for values. + Map with the fields of the X509 name + + + Class for breaking up an X500 Name into it's component tokens, similar to java.util.StringTokenizer. + + + Class for breaking up an X500 Name into it's component tokens, similar to java.util.StringTokenizer. + We need this class as some of the lightweight Java environments don't support classes such as StringTokenizer. + + + + Creates an X509NameTokenizer. + the oid that needs to be parsed + + + Checks if the tokenizer has any tokens left. + true if there are any tokens left, false if there aren't + + + Returns the next token. + the next token + + + + This class contains a series of static methods that + allow you to retrieve information from a Certificate. + + + + Gets a CRL from a certificate + + the CRL or null if there's no CRL available + + + + + + Gets the URL of the Certificate Revocation List for a Certificate + the Certificate + the String where you can check if the certificate was revoked + + + + + Gets the CRL object using a CRL URL. + the URL where to get the CRL + a CRL object + + + + + + Retrieves the OCSP URL from the given certificate. + the certificate + the URL or null + + + + Gets the URL of the TSA if it's available on the certificate + a certificate + a TSA URL + + + + the certificate from which we need the ExtensionValue + the Object Identifier value for the extension. + the extension value as an ASN1Primitive object + + + + Gets a String from an ASN1Primitive + the ASN1Primitive + a human-readable String + + + + This class consists of some methods that allow you to verify certificates. + + + Verifies a single certificate for the current date. + the certificate to verify + the certificate revocation list or null + + a String with the error description or null + if no error + + + + Verifies a single certificate. + the certificate to verify + the certificate revocation list or null + the date, shall not be null + + a String with the error description or null + if no error + + + + Verifies a certificate chain against a KeyStore for the current date. + the certificate chain + the KeyStore + the certificate revocation list or null + + null if the certificate chain could be validated or a + Object[]{cert,error} where cert is the + failed certificate and error is the error message + + + + Verifies a certificate chain against a KeyStore. + the certificate chain + the KeyStore + the certificate revocation list or null + the date, shall not be null + + null if the certificate chain could be validated or a + Object[]{cert,error} where cert is the + failed certificate and error is the error message + + + + Verifies a certificate chain against a KeyStore for the current date. + the certificate chain + the KeyStore + + null if the certificate chain could be validated or a + Object[]{cert,error} where cert is the + failed certificate and error is the error message + + + + Verifies a certificate chain against a KeyStore. + the certificate chain + the KeyStore + the date, shall not be null + + null if the certificate chain could be validated or a + Object[]{cert,error} where cert is the + failed certificate and error is the error message + + + + Verifies an OCSP response against a KeyStore. + the OCSP response + the KeyStore + the provider or null to use the BouncyCastle provider + true is a certificate was found + + + Verifies a time stamp against a KeyStore. + the time stamp + the KeyStore + the provider or null to use the BouncyCastle provider + true is a certificate was found + + + + Superclass for a series of certificate verifiers that will typically + be used in a chain. + + + Superclass for a series of certificate verifiers that will typically + be used in a chain. It wraps another CertificateVerifier + that is the next element in the chain of which the verify() + method will be called. + + + + The previous CertificateVerifier in the chain of verifiers. + + + Indicates if going online to verify a certificate is allowed. + + + Creates the final CertificateVerifier in a chain of verifiers. + the previous verifier in the chain + + + Decide whether or not online checking is allowed. + + + + + Checks the validity of the certificate, and calls the next + verifier in the chain, if any. + + the certificate that needs to be checked + its issuer + the date the certificate needs to be valid + + a list of VerificationOK objects. + The list will be empty if the certificate couldn't be verified. + + + + + + + An implementation of the CrlClient that handles offline + Certificate Revocation Lists. + + Paulo Soares + + + + Interface that needs to be implemented if you want to embed + Certificate Revocation Lists (CRL) into your PDF. + + Paulo Soares + + + Gets an encoded byte array. + The certificate which a CRL URL can be obtained from. + A CRL url if you don't want to obtain it from the certificate. + A collection of byte array each representing a crl. It may return null or an empty collection. + + + The CRL as a byte array. + + + + Creates an instance of a CrlClient in case you + have a local cache of the Certificate Revocation List. + + the CRL bytes + + + + Creates an instance of a CrlClient in case you + have a local cache of the Certificate Revocation List. + + a CRL object + + + Returns the CRL bytes (the parameters are ignored). + + + + + An implementation of the CrlClient that fetches the CRL bytes + from an URL. + + Paulo Soares + + + The Logger instance. + + + The URLs of the CRLs. + + + + Creates a CrlClientOnline instance that will try to find + a single CRL by walking through the certificate chain. + + + + Creates a CrlClientOnline instance using one or more URLs. + + + Creates a CrlClientOnline instance using one or more URLs. + + + Creates a CrlClientOnline instance using a certificate chain. + + + Adds an URL to the list of CRL URLs + an URL in the form of a String + + + Adds an URL to the list of CRL URLs + an URL object + + + Fetches the CRL bytes from an URL. + + Fetches the CRL bytes from an URL. + If no url is passed as parameter, the url will be obtained from the certificate. + If you want to load a CRL from a local file, subclass this method and pass an + URL with the path to the local file to this method. An other option is to use + the CrlClientOffline class. + + + + + + Class that allows you to verify a certificate against + one or more Certificate Revocation Lists. + + + + + Verifies a certificate against a KeyStore + containing trusted anchors. + + + + A key store against which certificates can be verified. + + + Creates a RootStoreVerifier in a chain of verifiers. + the next verifier in the chain + + + Sets the Key Store against which a certificate can be checked. + a root store + + + Verifies a single certificate against a key store (if present). + the certificate to verify + the issuer certificate + the date the certificate needs to be valid + + a list of VerificationOK objects. + The list will be empty if the certificate couldn't be verified. + + + + + + The Logger instance + + + The list of CRLs to check for revocation date. + + + Creates a CRLVerifier instance. + the next verifier in the chain + a list of CRLs + + + Verifies if a a valid CRL is found for the certificate. + + Verifies if a a valid CRL is found for the certificate. + If this method returns false, it doesn't mean the certificate isn't valid. + It means we couldn't verify it against any CRL that was available. + + the certificate that needs to be checked + its issuer + + a list of VerificationOK objects. + The list will be empty if the certificate couldn't be verified. + + + + + + + Verifies a certificate against a single CRL. + the Certificate Revocation List + a certificate that needs to be verified + its issuer + the sign date + true if the verification succeeded + + + + Fetches a CRL for a specific certificate online (without further checking). + the certificate + its issuer + an X509CRL object + + + Checks if a CRL verifies against the issuer certificate or a trusted anchor. + the CRL + the trusted anchor + true if the CRL can be trusted + + + Class that contains a map with the different message digest algorithms. + + + Algorithm available for signatures since PDF 1.3. + + + Algorithm available for signatures since PDF 1.6. + + + Algorithm available for signatures since PDF 1.7. + + + Algorithm available for signatures since PDF 1.7. + + + Algorithm available for signatures since PDF 1.7. + + + Maps the digest IDs with the human-readable name of the digest algorithm. + + + Maps digest algorithm that are unknown by the JDKs MessageDigest object to a known one. + + + Maps the name of a digest algorithm with its ID. + + + Get a digest algorithm. + oid of the digest algorithm + the provider you want to use to create the hash + MessageDigest object + + + + + Creates a MessageDigest object that can be used to create a hash. + the algorithm you want to use to create a hash + the provider you want to use to create the hash + a MessageDigest object + + + + + Creates a hash using a specific digest algorithm and a provider. + the message of which you want to create a hash + the algorithm used to create the hash + the provider used to create the hash + the hash + + + + + Create a digest based on the inputstream. + data to be digested + algorithm to be used + digest of the data + + + + + Gets the digest name for a certain id + an id (for instance "1.2.840.113549.2.5") + a digest name (for instance "MD5") + + + + Returns the id of a digest algorithms that is allowed in PDF, + or null if it isn't allowed. + + The name of the digest algorithm. + An oid. + + + Class that contains a map with the different encryption algorithms. + + + Maps IDs of encryption algorithms with its human-readable name. + + + Gets the algorithm name for a certain id. + an id (for instance "1.2.840.113549.1.1.1") + an algorithm name (for instance "RSA") + + + Produces a blank (or empty) signature. + + Produces a blank (or empty) signature. Useful for deferred signing with + MakeSignature.signExternalContainer(). + + Paulo Soares + + + Interface to sign a document. + Interface to sign a document. The signing is fully done externally, including the container composition. + + Paulo Soares + + + Produces the container with the signature. + the data to sign + a container with the signature and other objects, like CRL and OCSP. The container will generally be a PKCS7 one. + + + + + Modifies the signature dictionary to suit the container. + + Modifies the signature dictionary to suit the container. At least the keys + + and + + will have to be set. + + the signature dictionary + + + Creates an ExternalBlankSignatureContainer. + PdfDictionary containing signature iformation. /SubFilter and /Filter aren't set in this constructor. + + + + Creates an ExternalBlankSignatureContainer. + + Creates an ExternalBlankSignatureContainer. This constructor will create the PdfDictionary for the + signature information and will insert the /Filter and /SubFilter values into this dictionary. + + PdfName of the signature handler to use when validating this signature + PdfName that describes the encoding of the signature + + + + + + Interface that needs to be implemented to do the actual signing. + + Interface that needs to be implemented to do the actual signing. + For instance: you'll have to implement this interface if you want + to sign a PDF using a smart card. + + Paulo Soares + + + Returns the hash algorithm. + The hash algorithm (e.g. "SHA-1", "SHA-256,..."). + + + Returns the encryption algorithm used for signing. + The encryption algorithm ("RSA" or "DSA"). + + + + Signs the given message using the encryption algorithm in combination + with the hash algorithm. + + The message you want to be hashed and signed. + A signed message digest. + + + + Interface for the Online Certificate Status Protocol (OCSP) Client. + + + Gets an encoded byte array with OCSP validation. + Gets an encoded byte array with OCSP validation. The method should not throw an exception. + Certificate to check. + The parent certificate. + + The url to get the verification. It it's null it will be taken. + from the check cert or from other implementation specific source + + A byte array with the validation or null if the validation could not be obtained + + + + Get the time stamp estimated token size. + + Get the time stamp estimated token size. + Implementation must return value large enough to accommodate the + entire token returned by + + prior + to actual + + call. + + an estimate of the token size + + + + Returns the + + to digest the data imprint + + + The + + object. + + + + + Returns RFC 3161 timeStampToken. + + Returns RFC 3161 timeStampToken. + Method may return null indicating that timestamp should be skipped. + + byte[] - data imprint to be time-stamped + byte[] - encoded, TSA signed data of the timeStampToken + - TSA request failed + + + + Interface you can implement and pass to TSAClientBouncyCastle in case + you want to do something with the information returned + + + + + When a timestamp is created using TSAClientBouncyCastle, + this method is triggered passing an object that contains + info about the timestamp and the time stamping authority. + + a TimeStampTokenInfo object + + + Add verification according to PAdES-LTV (part 4). + Paulo Soares + + + The verification constructor. + + The verification constructor. This class should only be created with + PdfStamper.getLtvVerification() otherwise the information will not be + added to the Pdf. + + + The + + to apply the validation to. + + + + Add verification for a particular signature. + the signature to validate (it may be a timestamp) + the interface to get the OCSP + the interface to get the CRL + options as to how many certificates to include + the validation options to include + certificate inclusion options + true if a validation was generated, false otherwise + + + + + Get the issuing certificate for a child certificate. + the certificate for which we search the parent + an array with certificates that contains the parent + the parent certificate + + + Adds verification to the signature. + name of the signature + collection of ocsp responses + collection of crls + collection of certificates + boolean + + + + + + + + + + + + + + + Merges the validation with any validation already in the document or creates a new one. + + + + + + + + + + + + + Converts an array of bytes to a String of hexadecimal values + a byte array + the same bytes expressed as hexadecimal values + + + What type of verification to include. + + + Options for how many certificates to include. + + + + Certificate inclusion in the DSS and VRI dictionaries in the CERT and CERTS + keys. + + + + Verifies the signatures in an LTV document. + + + The Logger instance + + + Option to specify level of verification; signing certificate only or the entire chain. + + + Verify root. + + + A document object for the revision that is being verified. + + + The fields in the revision that is being verified. + + + The date the revision was signed, or null for the highest revision. + + + The signature that covers the revision. + + + The PdfPKCS7 object for the signature. + + + Indicates if we're working with the latest revision. + + + The document security store for the revision that is being verified + + + Creates a VerificationData object for a PdfReader + The document we want to verify. + + + + Sets an extra verifier. + the verifier to set + + + Sets the certificate option. + Either CertificateOption.SIGNING_CERTIFICATE (default) or CertificateOption.WHOLE_CHAIN + + + + Set the verifyRootCertificate to false if you can't verify the root certificate. + + + + Checks if the signature covers the whole document + and throws an exception if the document was altered + + a PdfPKCS7 object + + + + Verifies all the document-level timestamps and all the signatures in the document. + + + + + Verifies a document level timestamp. + + + + + + Checks the certificates in a certificate chain: + are they valid on a specific date, and + do they chain up correctly? + + the certificate chain + + + + Verifies certificates against a list of CRLs and OCSP responses. + the signing certificate + the issuer's certificate + + a list of VerificationOK objects. + The list will be empty if the certificate couldn't be verified. + + + + + + + Switches to the previous revision. + + + + + Gets a list of X509CRL objects from a Document Security Store. + a list of CRLs + + + + + Gets OCSP responses from the Document Security Store. + a list of BasicOCSPResp objects + + + + + OcspClient implementation using BouncyCastle. + Paulo Soarees + + + The Logger instance. + + + + Create + OcspClient + + + will be used for response verification. + OCSPVerifier + . + + + + Gets OCSP response. + + Gets OCSP response. If + OCSPVerifier + was setted, the response will be checked. + + + + Gets an encoded byte array with OCSP validation. + Gets an encoded byte array with OCSP validation. The method should not throw an exception. + to certificate to check + the parent certificate + + to get the verification. It it's null it will be taken + from the check cert or from other implementation specific source + + a byte array with the validation or null if the validation could not be obtained + + + Generates an OCSP request using BouncyCastle. + certificate of the issues + serial number + an OCSP request + + + + + + + + + + + + + + Class that allows you to verify a certificate against + one or more OCSP responses. + + + + The Logger instance + + + The list of OCSP responses. + + + Creates an OCSPVerifier instance. + the next verifier in the chain + a list of OCSP responses + + + Verifies if a a valid OCSP response is found for the certificate. + + Verifies if a a valid OCSP response is found for the certificate. + If this method returns false, it doesn't mean the certificate isn't valid. + It means we couldn't verify it against any OCSP response that was available. + + the certificate that needs to be checked + its issuer + + a list of VerificationOK objects. + The list will be empty if the certificate couldn't be verified. + + + + + + + Verifies a certificate against a single OCSP response + the OCSP response + the certificate that needs to be checked + the certificate of CA + sign date + + + + , in case successful check, otherwise false. + + + + + + + Verifies if an OCSP response is genuine + If it doesn't verify against the issuer certificate and response's certificates, it may verify + using a trusted anchor or cert. + + the OCSP response + the issuer certificate + + + + + Verifies if the response is valid. + + Verifies if the response is valid. + If it doesn't verify against the issuer certificate and response's certificates, it may verify + using a trusted anchor or cert. + NOTE. Use + isValidResponse() + instead. + + the response object + the issuer certificate + true if the response can be trusted + + + Checks if an OCSP response is genuine + the OCSP response + the responder certificate + true if the OCSP response verifies against the responder certificate + + + + Gets an OCSP response online and returns it if the status is GOOD + (without further checking!). + + the signing certificate + the issuer certificate + an OCSP response + + + + This class does all the processing related to signing + and verifying a PKCS#7 signature. + + + + Assembles all the elements needed to create a signature, except for the data. + the private key + the certificate chain + the interface digest + the hash algorithm + the provider or null for the default provider + true if the sub-filter is adbe.pkcs7.sha1 + on error + on error + on error + + + Use this constructor if you want to verify a signature using the sub-filter adbe.x509.rsa_sha1. + the /Contents key + the /Cert key + the provider or null for the default provider + + + Use this constructor if you want to verify a signature. + the /Contents key + the filtersubtype + the provider or null for the default provider + + + Holds value of property signName. + + + Holds value of property reason. + + + Holds value of property location. + + + Holds value of property signDate. + + + Getter for property sigName. + Value of property sigName. + + + Setter for property sigName. + New value of property sigName. + + + Getter for property reason. + Value of property reason. + + + Setter for property reason. + New value of property reason. + + + Getter for property location. + Value of property location. + + + Setter for property location. + New value of property location. + + + Getter for property signDate. + Value of property signDate. + + + Setter for property signDate. + New value of property signDate. + + + Version of the PKCS#7 object + + + Version of the PKCS#7 "SignerInfo" object. + + + Get the version of the PKCS#7 object. + the version of the PKCS#7 object. + + + Get the version of the PKCS#7 "SignerInfo" object. + the version of the PKCS#7 "SignerInfo" object. + + + The ID of the digest algorithm, e.g. + The ID of the digest algorithm, e.g. "2.16.840.1.101.3.4.2.1". + + + The object that will create the digest + + + The digest algorithms + + + The digest attributes + + + Getter for the ID of the digest algorithm, e.g. + Getter for the ID of the digest algorithm, e.g. "2.16.840.1.101.3.4.2.1" + + + Returns the name of the digest algorithm, e.g. + Returns the name of the digest algorithm, e.g. "SHA256". + the digest algorithm name, e.g. "SHA256" + + + The encryption algorithm. + + + Getter for the digest encryption algorithm + + + Get the algorithm used to calculate the message digest, e.g. + Get the algorithm used to calculate the message digest, e.g. "SHA1withRSA". + the algorithm used to calculate the message digest + + + The signed digest if created outside this class + + + External RSA data + + + Sets the digest/signature to an external calculated value. + the digest. This is the actual signature + the extra data that goes into the data tag in PKCS#7 + + the encryption algorithm. It may must be null if the digest + is also null. If the digest is not null + then it may be "RSA" or "DSA" + + + + Class from the Java SDK that provides the functionality of a digital signature algorithm. + + + The signed digest as calculated by this class (or extracted from an existing PDF) + + + The RSA data + + + + + + + + + + + + + Update the digest with the specified bytes. + + Update the digest with the specified bytes. + This method is used both for signing and verifying + + the data buffer + the offset in the data buffer + the data length + on error + + + Gets the bytes for the PKCS#1 object. + a byte array + + + Gets the bytes for the PKCS7SignedData object. + the bytes for the PKCS7SignedData object + + + Gets the bytes for the PKCS7SignedData object. + + Gets the bytes for the PKCS7SignedData object. Optionally the authenticatedAttributes + in the signerInfo can also be set. If either of the parameters is null, none will be used. + + the digest in the authenticatedAttributes + the bytes for the PKCS7SignedData object + + + Gets the bytes for the PKCS7SignedData object. + + Gets the bytes for the PKCS7SignedData object. Optionally the authenticatedAttributes + in the signerInfo can also be set, OR a time-stamp-authority client + may be provided. + + the digest in the authenticatedAttributes + TSAClient - null or an optional time stamp authority client + byte[] the bytes for the PKCS7SignedData object + + + + Added by Aiken Sam, 2006-11-15, modifed by Martin Brunecky 07/12/2007 + to start with the timeStampToken (signedData 1.2.840.113549.1.7.2). + + + Added by Aiken Sam, 2006-11-15, modifed by Martin Brunecky 07/12/2007 + to start with the timeStampToken (signedData 1.2.840.113549.1.7.2). + Token is the TSA response without response status, which is usually + handled by the (vendor supplied) TSA request/response interface). + + byte[] - time stamp token, DER encoded signedData + ASN1EncodableVector + + + + + + This method provides that encoding and the parameters must be + exactly the same as in + + . + + the content digest + the byte array representation of the authenticatedAttributes ready to be signed + + + Signature attributes + + + Signature attributes (maybe not necessary, but we use it as fallback) + + + encrypted digest + + + Indicates if a signature has already been verified + + + The result of the verification + + + Verify the digest. + true if the signature checks out, false otherwise + on error + + + + + + + Checks if the timestamp refers to this document. + true if it checks false otherwise + on error + + + All the X.509 certificates in no particular order. + + + All the X.509 certificates used for the main signature. + + + The X.509 certificate that is used to sign the digest. + + + Get all the X.509 certificates associated with this PKCS#7 object in no particular order. + + Get all the X.509 certificates associated with this PKCS#7 object in no particular order. + Other certificates, from OCSP for example, will also be included. + + the X.509 certificates associated with this PKCS#7 object + + + Get the X.509 sign certificate chain associated with this PKCS#7 object. + + Get the X.509 sign certificate chain associated with this PKCS#7 object. + Only the certificates used for the main signature will be returned, with + the signing certificate first. + + the X.509 certificates associated with this PKCS#7 object + + + Get the X.509 certificate actually used to sign the digest. + the X.509 certificate actually used to sign the digest + + + + Helper method that creates the collection of certificates + used for the main signature based on the complete list + of certificates and the sign certificate. + + + + Get the X.509 certificate revocation lists associated with this PKCS#7 object + the X.509 certificate revocation lists associated with this PKCS#7 object + + + Helper method that tries to construct the CRLs. + + + BouncyCastle BasicOCSPResp + + + Gets the OCSP basic response if there is one. + the OCSP basic response or null + + + Checks if OCSP revocation refers to the document signing certificate. + true if it checks, false otherwise + + + Helper method that creates the BasicOCSPResp object. + + + + + True if there's a PAdES LTV time stamp. + + + True if it's a CAdES signature type. + + + BouncyCastle TimeStampToken. + + + Check if it's a PAdES-LTV time stamp. + true if it's a PAdES-LTV time stamp, false otherwise + + + Gets the timestamp token if there is one. + the timestamp token or null + + + Gets the timestamp date + a date + + + Returns the filter subtype. + + + Returns the encryption algorithm + the name of an encryption algorithm + + + Represents the signature dictionary. + Paulo Soares + + + Creates new PdfSignature. + + + Creates new PdfSignature. + PdfName of the signature handler to use when validating this signature + PdfName that describes the encoding of the signature + + + Sets the /ByteRange. + an array of pairs of integers that specifies the byte range used in the digest calculation. A pair consists of the starting byte offset and the length + + + + Sets the /Contents value to the specified byte[]. + a byte[] representing the digest + + + Sets the /Cert value of this signature. + the byte[] representing the certificate chain + + + Sets the /Name of the person signing the document. + name of the person signing the document + + + Sets the /M value. + Sets the /M value. Should only be used if the time of signing is not available in the signature. + time of signing + + + Sets the /Location value. + physical location of signing + + + Sets the /Reason value. + reason for signing + + + + Sets the signature creator name in the + + dictionary. + + name of the signature creator + + + Sets the /ContactInfo value. + information to contact the person who signed this document + + + + Gets the + + instance if it exists, if + not it adds a new one and returns this. + + + + + + + + A dictionary that stores the name of the application that signs the PDF. + + + Creates a new PdfSignatureAppDictionary + + + Creates a new PdfSignatureAppDictionary. + PdfDictionary containing initial values + + + + Sets the signature created property in the Prop_Build dictionary's App + dictionary. + + String name of the application creating the signature + + + Provides convenient methods to make a signature appearance. + + Provides convenient methods to make a signature appearance. Use it in conjunction with + + . + + + + Extra space at the top. + + + Margin for the content inside the signature rectangle. + + + The document to be signed. + + + The page where the signature will appear. + + + + The coordinates of the rectangle for a visible signature, + or a zero-width, zero-height rectangle for an invisible signature. + + + + Rectangle that represent the position and dimension of the signature in the page. + + + Zero level of the signature appearance. + + + Second level of the signature appearance. + + + Form containing all layers drawn on top of each other. + + + The rendering mode chosen for visible signatures. + + + The reason for signing. + + + The caption for the reason for signing. + + + Holds value of property location. + + + The caption for the location of signing. + + + Holds value of the application that creates the signature. + + + The contact name of the signer. + + + Holds value of property signDate. + + + The signing certificate. + + + The image that needs to be used for a visible signature. + + + A background image for the text in layer 2. + + + The scaling to be applied to the background image. + + + The text that goes in Layer 2 of the signature appearance. + + + Font for the text in Layer 2. + + + Font size for the font of Layer 2. + + + + Indicates the field to be signed if it is already presented in the document + (signing existing field). + + + Indicates the field to be signed if it is already presented in the document + (signing existing field). Required for + + option. + + + + Indicates if we need to reuse the existing appearance as layer 0. + + + Creates a PdfSignatureAppearance. + PdfDocument + Rectangle of the appearance + Number of the page the appearance should be on + + + + Provides the page number of the signature field which this signature + appearance is associated with. + + + The page number of the signature field which this signature + appearance is associated with. + + + + + Sets the page number of the signature field which this signature + appearance is associated with. + + + The page number of the signature field which + this signature appearance is associated with. + + + + + Provides the rectangle that represent the position and dimension + of the signature field in the page. + + + the rectangle that represent the position and dimension + of the signature field in the page + + + + + Sets the rectangle that represent the position and dimension of + the signature field in the page. + + + The rectangle that represents the position and + dimension of the signature field in the page. + + + + Get Layer 0 of the appearance. + layer 0 + + + Get Layer 2 of the appearance. + layer 2 + + + Gets the rendering mode for this signature. + the rendering mode for this signature + + + Sets the rendering mode for this signature. + the rendering mode + + + Returns the signing reason. + reason for signing + + + Sets the signing reason. + signing reason. + + + Sets the caption for the signing reason. + A new signing reason caption + + + Returns the signing location. + signing location + + + Sets the signing location. + A new signing location + + + Sets the caption for the signing location. + A new signing location caption + + + Returns the signature creator. + The signature creator + + + Sets the name of the application used to create the signature. + A new name of the application signing a document + + + Returns the signing contact. + The signing contact + + + Sets the signing contact. + A new signing contact + + + Sets the certificate used to provide the text in the appearance. + + Sets the certificate used to provide the text in the appearance. + This certificate doesn't take part in the actual signing process. + + the certificate + + + Get the signing certificate. + the signing certificate + + + Gets the Image object to render. + the image + + + Sets the Image object to render when Render is set to RenderingMode.GRAPHIC or RenderingMode.GRAPHIC_AND_DESCRIPTION. + + image rendered. If null the mode is defaulted to RenderingMode.DESCRIPTION + + + Indicates that the existing appearances needs to be reused as layer 0. + + + Gets the background image for the layer 2. + the background image for the layer 2 + + + Sets the background image for the layer 2. + the background image for the layer 2 + + + Gets the scaling to be applied to the background image. + the scaling to be applied to the background image + + + Sets the scaling to be applied to the background image. + + Sets the scaling to be applied to the background image. If it's zero the image + will fully fill the rectangle. If it's less than zero the image will fill the rectangle but + will keep the proportions. If it's greater than zero that scaling will be applied. + In any of the cases the image will always be centered. It's zero by default. + + the scaling to be applied to the background image + + + Sets the signature text identifying the signer. + + the signature text identifying the signer. If null or not set + a standard description will be used + + + + Gets the signature text identifying the signer if set by setLayer2Text(). + the signature text identifying the signer + + + Gets the n2 and n4 layer font. + the n2 and n4 layer font + + + Sets the n2 and n4 layer font. + Sets the n2 and n4 layer font. If the font size is zero, auto-fit will be used. + the n2 and n4 font + + + Sets the n2 and n4 layer font size. + font size + + + Gets the visibility status of the signature. + the visibility status of the signature + + + + Returns the signature date. + the signature date + + + Sets the signature date. + A new signature date + + + Set the field name of the appearance. + name of the field + + + Signature rendering modes. + + + Dictionary that stores signature build properties. + Kwinten Pisman + + + Creates new PdfSignatureBuildProperties. + + + Creates new PdfSignatureBuildProperties with preset values. + PdfDictionary containing preset values + + + + Sets the signatureCreator property in the underlying + + dictionary. + + + + + + Gets the + + from this dictionary. If it + does not exist, it adds a new + + and + returns this instance. + + + + + + + + Takes care of the cryptographic options and appearances that form a signature. + + + Approval signature. + + + Author signature, no changes allowed. + + + Author signature, form filling allowed. + + + Author signature, form filling and annotations allowed. + + + The certification level. + + + The name of the field. + + + The file right before the signature is added (can be null). + + + The bytes of the file right before the signature is added (if raf is null). + + + Array containing the byte positions of the bytes that need to be hashed. + + + The PdfDocument. + + + The crypto dictionary. + + + Holds value of property signatureEvent. + + + OutputStream for the bytes of the document. + + + Outputstream that temporarily holds the output in memory. + + + Tempfile to hold the output temporarily. + + + Name and content of keys that can only be added in the close() method. + + + Indicates if the pdf document has already been pre-closed. + + + Signature field lock dictionary. + + + The signature appearance. + + + Holds value of property signDate. + + + Boolean to check if this PdfSigner instance has been closed already or not. + + + Creates a PdfSigner instance. + + Creates a PdfSigner instance. Uses a + + instead of a temporary file. + + PdfReader that reads the PDF file + OutputStream to write the signed PDF file + boolean to indicate whether the signing should happen in append mode or not + + + + Creates a PdfSigner instance. + + Creates a PdfSigner instance. Uses a + + instead of a temporary file. + + PdfReader that reads the PDF file + OutputStream to write the signed PDF file + File to which the output is temporarily written + boolean to indicate whether the signing should happen in append mode or not + + + + Gets the signature date. + Calendar set to the signature date + + + Sets the signature date. + the signature date + + + Provides access to a signature appearance object. + + Provides access to a signature appearance object. Use it to + customize the appearance of the signature. +

    + Be aware: +

      +
    • If you create new signature field (either use + + with + the name that doesn't exist in the document or don't specify it at all) then + the signature is invisible by default.
    • +
    • If you sign already existing field, then the signature appearance object + is modified to have all the properties (page num., rect etc.) consistent with + the state of the field (if you customized the appearance object + before the + + call you'll have to do it again)
    • +
    +

    +
    + + + + object. + +
    + + Returns the document's certification level. + + Returns the document's certification level. + For possible values see + + . + + The certified status. + + + Sets the document's certification level. + + a new certification level for a document. + Possible values are:
      +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    + +
    + + Gets the field name. + the field name + + + Returns the user made signature dictionary. + + Returns the user made signature dictionary. This is the dictionary at the /V key + of the signature field. + + The user made signature dictionary. + + + Getter for property signatureEvent. + Value of property signatureEvent. + + + Sets the signature event to allow modification of the signature dictionary. + the signature event + + + Gets a new signature field name that doesn't clash with any existing name. + A new signature field name. + + + Sets the name indicating the field to be signed. + + Sets the name indicating the field to be signed. The field can already be presented in the + document but shall not be signed. If the field is not presented in the document, it will be created. + + The name indicating the field to be signed. + + + Gets the PdfDocument associated with this instance. + the PdfDocument associated with this instance + + + Sets the PdfDocument. + + + Setter for the OutputStream. + + + Getter for the field lock dictionary. + Field lock dictionary. + + + Setter for the field lock dictionary. + + Setter for the field lock dictionary. +

    Be aware: if a signature is created on an existing signature field, + then its /Lock dictionary takes the precedence (if it exists).

    +
    + Field lock dictionary +
    + + Signs the document using the detached mode, CMS or CAdES equivalent. + + Signs the document using the detached mode, CMS or CAdES equivalent. +

    + NOTE: This method closes the underlying pdf document. This means, that current instance + of PdfSigner cannot be used after this method call. +
    + the interface providing the actual signing + the certificate chain + the CRL list + the OCSP client + the Timestamp client + an implementation that provides the digest + the reserved size for the signature. It will be estimated if 0 + Either Signature.CMS or Signature.CADES + + +
    + + Sign the document using an external container, usually a PKCS7. + + Sign the document using an external container, usually a PKCS7. The signature is fully composed + externally, iText will just put the container inside the document. +

    + NOTE: This method closes the underlying pdf document. This means, that current instance + of PdfSigner cannot be used after this method call. +
    + the interface providing the actual signing + the reserved size for the signature + + +
    + + Signs a document with a PAdES-LTV Timestamp. + + Signs a document with a PAdES-LTV Timestamp. The document is closed at the end. +

    + NOTE: This method closes the underlying pdf document. This means, that current instance + of PdfSigner cannot be used after this method call. +
    + the timestamp generator + + the signature name or null to have a name generated + automatically + + + +
    + + Signs a PDF where space was already reserved. + the original PDF + the field to sign. It must be the last field + the output PDF + + the signature container doing the actual signing. Only the + method ExternalSignatureContainer.sign is used + + + + + + Processes a CRL list. + a Certificate if one of the CrlList implementations needs to retrieve the CRL URL from it. + + a list of CrlClient implementations + a collection of CRL bytes that can be embedded in a PDF + + + Checks if the document is in the process of closing. + true if the document is in the process of closing, false otherwise + + + + Gets the document bytes that are hashable when using external signatures. + + Gets the document bytes that are hashable when using external signatures. + The general sequence is: + + , + + and + + . + + + The + + of bytes to be signed. + + + + + + Returns the underlying source. + The underlying source + + + + Adds keys to the signature dictionary that define the certification level and the permissions. + + Adds keys to the signature dictionary that define the certification level and the permissions. + This method is only used for Certifying signatures. + + the signature dictionary + + + Adds keys to the signature dictionary that define the field permissions. + + Adds keys to the signature dictionary that define the field permissions. + This method is only used for signatures that lock fields. + + the signature dictionary + + + Get the rectangle associated to the provided widget. + PdfWidgetAnnotation to extract the rectangle from + Rectangle + + + Get the page number associated to the provided widget. + PdfWidgetAnnotation from which to extract the page number + page number + + + Enum containing the Cryptographic Standards. + Enum containing the Cryptographic Standards. Possible values are "CMS" and "CADES". + + + An interface to retrieve the signature dictionary for modification. + + + Allows modification of the signature dictionary. + The signature dictionary + + + + Implementation of the + + interface that + can be used when you have a + + object. + + Paulo Soares + + + The private key object. + + + The hash algorithm. + + + The encryption algorithm (obtained from the private key) + + + + Creates a + + instance. + + + A + + object. + + A hash algorithm (e.g. "SHA-1", "SHA-256",...). + A security provider (e.g. "BC"). + + + + + + + + + + + + + A list of IDs that are used by the security classes + + + + A helper class that tells you more about the type of signature + (certification or approval) and the signature's DMP settings. + + + + Is the signature a cerification signature (true) or an approval signature (false)? + + + Is form filling allowed by this signature? + + + Is adding annotations allowed by this signature? + + + Does this signature lock specific fields? + + + + Creates an object that can inform you about the type of signature + in a signature dictionary as well as some of the permissions + defined by the signature. + + + + Getter to find out if the signature is a certification signature. + true if the signature is a certification signature, false for an approval signature. + + + Getter to find out if filling out fields is allowed after signing. + true if filling out fields is allowed + + + Getter to find out if adding annotations is allowed after signing. + true if adding annotations is allowed + + + Getter for the field lock actions, and fields that are impacted by the action + an Array with field names + + + + Class that contains a field lock action and + an array of the fields that are involved. + + + + Can be /All, /Exclude or /Include + + + An array of PdfString values with fieldnames + + + Creates a FieldLock instance + + + Getter for the field lock action. + + + Getter for the fields involved in the lock action. + + + toString method + + + Utility class that provides several convenience methods concerning digital signatures. + + + Creates a SignatureUtil instance. + + Creates a SignatureUtil instance. Sets the acroForm field to the acroForm in the PdfDocument. + iText will create a new AcroForm if the PdfDocument doesn't contain one. + + PdfDocument to be inspected + + + Verifies a signature. + + Verifies a signature. Further verification can be done on the returned + + object. + + the signature field name + the provider or null for the default provider + PdfPKCS7 object to continue the verification + + + Gets the signature dictionary, the one keyed by /V. + the field name + + the signature dictionary keyed by /V or null if the field is not + a signature + + + + Gets the field names that have signatures and are signed. + List containing the field names that have signatures and are signed + + + Gets the field names that have blank signatures. + List containing the field names that have blank signatures + + + Extracts a revision from the document. + the signature field name + an InputStream covering the revision. Returns null if it's not a signature field + + + + Checks if the signature covers the entire document or just part of it. + the signature field name + true if the signature covers the entire document, false if it doesn't + + + Checks whether a name exists as a signature field or not. + Checks whether a name exists as a signature field or not. It checks both signed fields and blank signatures. + + name of the field + boolean does the signature field exist + + + + Converts a + + to an array of longs + + PdfArray to be converted + long[] containing the PdfArray values + + + + + + + + Time Stamp Authority Client interface implementation using Bouncy Castle + org.bouncycastle.tsp package. + + + Time Stamp Authority Client interface implementation using Bouncy Castle + org.bouncycastle.tsp package. +

    + Created by Aiken Sam, 2006-11-15, refactored by Martin Brunecky, 07/15/2007 + for ease of subclassing. +

    +
    +
    + + The default value for the hash algorithm + + + The default value for the hash algorithm + + + The Logger instance. + + + URL of the Time Stamp Authority + + + TSA Username + + + TSA password + + + An interface that allows you to inspect the timestamp info. + + + Estimate of the received time stamp token + + + Hash algorithm + + + Creates an instance of a TSAClient that will use BouncyCastle. + String - Time Stamp Authority URL (i.e. "http://tsatest1.digistamp.com/TSA") + + + Creates an instance of a TSAClient that will use BouncyCastle. + String - Time Stamp Authority URL (i.e. "http://tsatest1.digistamp.com/TSA") + String - user(account) name + String - password + + + Constructor. + + Constructor. + Note the token size estimate is updated by each call, as the token + size is not likely to change (as long as we call the same TSA using + the same imprint length). + + String - Time Stamp Authority URL (i.e. "http://tsatest1.digistamp.com/TSA") + String - user(account) name + String - password + int - estimated size of received time stamp token (DER encoded) + + + the tsaInfo to set + + + Get the token size estimate. + + Get the token size estimate. + Returned value reflects the result of the last succesfull call, padded + + an estimate of the token size + + + Gets the MessageDigest to digest the data imprint + the digest algorithm name + + + + Get RFC 3161 timeStampToken. + + Get RFC 3161 timeStampToken. + Method may return null indicating that timestamp should be skipped. + + data imprint to be time-stamped + encoded, TSA signed data of the timeStampToken + + + + + Get timestamp token - communications layer + - byte[] - TSA response, raw bytes (RFC 3161 encoded) + + + + An exception that is thrown when something is wrong with a certificate. + + + Creates a VerificationException + + + + Class that informs you that the verification of a Certificate + succeeded using a specific CertificateVerifier and for a specific + reason. + + + + The certificate that was verified successfully. + + + The CertificateVerifier that was used for verifying. + + + The reason why the certificate verified successfully. + + + Creates a VerificationOK object + the certificate that was successfully verified + the class that was used for verification + the reason why the certificate could be verified + + + Return a single String explaining which certificate was verified, how and why. + + +
    +
    diff --git a/upgradescripts/3.80-the next version/upgrade.sql b/upgradescripts/3.80-the next version/upgrade.sql index e38dbf357ec..3e28c96dada 100644 --- a/upgradescripts/3.80-the next version/upgrade.sql +++ b/upgradescripts/3.80-the next version/upgrade.sql @@ -4,7 +4,175 @@ declare @resources xml --a resource will be deleted if its value is empty set @resources=' - + + + Included Tax: + + + Amount: + + + Amount incl. Tax: + + + Included Tax + + + Amount + + + Amount incl. Tax + + + Total to pay + + + Invoice Discount + + + Tax % + + + Tax % + + + Invoice + + + Invoice Date + + + Amount + + + Amount incl. tax + + + Discount + + + Discount incl. tax + + + VAT Amount + + + Amount to pay + + + Base Ammount + + + Total Base + + + Continued on next page ... + + + Additional items + + + Invoice discount + + + Currency + + + Page + + + Order No. + + + Orderdate + + + Invoice No. + + + Invoice Date + + + Amount + + + Total order base amount. + + + Amount incl. Tax + + + Total order amount incl. tax + + + Invoice No. + + + Invoice No. + + + Invoice Date + + + Invoice Date + + + Amount + + + Amount + + + Amount incl. Tax + + + Amount incl. Tax + + + Edit Invoice data. + + + Save Invoice Data. + + + A new InvoiceId get''s assigned when saving an Id = null + + + Included Tax + + + Amount + + + Amount incl. Tax + + + Invoice No. + + + Invoice Date + + + Tax % + + + Amount + + + Amount incl. Tax + + + Discount + + + Discount incl. tax + + + Base Amount + + + VAT + You haven''t written any reviews yet @@ -2217,6 +2385,20 @@ DEALLOCATE cur_existinglanguage DROP TABLE #LocaleStringResourceTmp GO +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'ordersettings.invoiceident') + BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'ordersettings.invoiceident', 0, 0) + END +GO + +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'ordersettings.invoiceyear') + BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'ordersettings.invoiceyear', 2016, 0) + END +GO --new setting IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'shippingsettings.hideshippingtotal') BEGIN @@ -3131,6 +3313,120 @@ DELETE FROM [Setting] WHERE [name] = N'producteditordettings.specialpriceenddate' GO +--add a new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[TierPrice]') and NAME='StartDateTimeUtc') +BEGIN + ALTER TABLE [TierPrice] + ADD [StartDateTimeUtc] datetime NULL +END +GO + +--add a new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[TierPrice]') and NAME='EndDateTimeUtc') +BEGIN + ALTER TABLE [TierPrice] + ADD [EndDateTimeUtc] datetime NULL +END +GO + +--add a new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[OrderItem]') and NAME='VatRate') +BEGIN + ALTER TABLE [OrderItem] + ADD [VatRate] decimal(18,4) NULL +END +GO + +IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[OrderItem]') and NAME='VatRate') +BEGIN + + UPDATE OrderItem + SET [VatRate] = 0 + WHERE ([VatRate] IS NULL) AND (PriceInclTax = PriceExclTax) + + UPDATE OrderItem + SET [VatRate] = round((PriceInclTax / PriceExclTax - 1) * 100, 2) + WHERE ([VatRate] IS NULL) AND PriceExclTax <> 0 + + ALTER TABLE [OrderItem] + ALTER COLUMN [VatRate] DECIMAL(18,4) NOT NULL +END +GO + +--add a new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='InvoiceId') +BEGIN + ALTER TABLE [dbo].[Order] + ADD InvoiceId NVARCHAR(20) NULL; +END + +GO + +IF NOT EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'[dbo].[Order]') AND name = N'UI_Order_InvoiceId') +BEGIN + CREATE UNIQUE NONCLUSTERED INDEX UI_Order_InvoiceId + ON dbo.[Order](InvoiceId) + WHERE InvoiceId IS NOT NULL; +END +GO + +--add a new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='InvoiceDateUtc') +BEGIN + ALTER TABLE [dbo].[Order] + ADD InvoiceDateUtc DATETIME NULL; +END + +GO + +--add a new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='OrderAmount') +BEGIN + ALTER TABLE [dbo].[Order] + ADD OrderAmount DECIMAL(18,4) NULL; +END + +GO + +IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='OrderAmount') +BEGIN + + UPDATE [dbo].[Order] + SET [OrderAmount] = OrderSubtotalExclTax - ISNULL(OrderSubTotalDiscountExclTax, 0) + ISNULL(OrderShippingExclTax, 0) + ISNULL(PaymentMethodAdditionalFeeExclTax, 0) - ISNULL(OrderDiscount, 0) + WHERE CustomerTaxDisplayTypeId = 10 AND OrderSubtotalExclTax IS NOT NULL + + UPDATE [dbo].[Order] + SET [OrderAmount] = OrderSubtotalInclTax - ISNULL(OrderSubTotalDiscountInclTax, 0) + ISNULL(OrderShippingInclTax, 0) + ISNULL(PaymentMethodAdditionalFeeInclTax, 0) - ISNULL(OrderDiscount, 0) - ISNULL(OrderTax, 0) + WHERE CustomerTaxDisplayTypeId = 0 AND OrderSubtotalInclTax IS NOT NULL +END + +GO + +--add a new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='OrderAmountIncl') +BEGIN + ALTER TABLE [dbo].[Order] + ADD OrderAmountIncl DECIMAL(18,4) NULL; + +END + +GO + +IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='OrderAmountIncl') +BEGIN + + UPDATE [dbo].[Order] + SET [OrderAmountIncl] = OrderSubtotalExclTax - ISNULL(OrderSubTotalDiscountExclTax, 0) + ISNULL(OrderShippingExclTax, 0) + ISNULL(PaymentMethodAdditionalFeeExclTax, 0) - ISNULL(OrderDiscount, 0) + ISNULL(OrderTax, 0) + WHERE CustomerTaxDisplayTypeId = 10 AND OrderSubtotalExclTax IS NOT NULL + + UPDATE [dbo].[Order] + SET [OrderAmountIncl] = OrderSubtotalInclTax - ISNULL(OrderSubTotalDiscountInclTax, 0) + ISNULL(OrderShippingInclTax, 0) + ISNULL(PaymentMethodAdditionalFeeInclTax, 0) - ISNULL(OrderDiscount, 0) + WHERE CustomerTaxDisplayTypeId = 0 AND OrderSubtotalExclTax IS NOT NULL +END + +GO + + --a stored procedure update IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[ProductLoadAllPaged]') AND OBJECTPROPERTY(object_id,N'IsProcedure') = 1) DROP PROCEDURE [ProductLoadAllPaged] From afb7fa80d4835e6e81bc85e29f3fa00d75c5526d Mon Sep 17 00:00:00 2001 From: Markus Falk Date: Mon, 23 Jan 2017 00:07:25 +0100 Subject: [PATCH 02/13] VAT Rounding: Vat for Product Attributes and fixes --- src/Libraries/Nop.Core/Domain/Orders/Order.cs | 1074 +++++------ src/Libraries/Nop.Core/Html/HtmlHelper.cs | 459 ++--- .../Catalog/IPriceCalculationService.cs | 316 ++-- .../Catalog/IProductAttributeFormatter.cs | 76 +- .../Catalog/IProductAttributeParser.cs | 261 +-- .../Catalog/PriceCalculationService.cs | 91 +- .../Catalog/ProductAttributeFormatter.cs | 519 ++--- .../Catalog/ProductAttributeParser.cs | 1662 +++++++++-------- .../Nop.Services/Common/PdfService7.cs | 3 +- .../Orders/OrderProcessingService.cs | 49 +- .../Orders/OrderTotalCalculationService.cs | 35 +- src/Libraries/Nop.Services/Tax/TaxService.cs | 3 - src/Libraries/Nop.Services/Tax/TaxSummary.cs | 90 +- .../Controllers/OrderController.cs | 68 +- .../Views/Order/_ProductAddAttributes.cshtml | 18 + .../Controllers/ShoppingCartController.cs | 22 +- .../Factories/ShoppingCartModelFactory.cs | 15 +- .../Models/ShoppingCart/ShoppingCartModel.cs | 2 +- .../Nop.Web/Views/Order/Details.cshtml | 2 +- .../Views/ShoppingCart/OrderSummary.cshtml | 659 +++---- .../Catalog/PriceCalculationServiceTests.cs | 6 +- .../Orders/OrderProcessingServiceTests.cs | 4 +- .../OrderTotalCalculationServiceTests.cs | 4 +- 23 files changed, 2907 insertions(+), 2531 deletions(-) diff --git a/src/Libraries/Nop.Core/Domain/Orders/Order.cs b/src/Libraries/Nop.Core/Domain/Orders/Order.cs index 9b576f6b608..f11141295f1 100644 --- a/src/Libraries/Nop.Core/Domain/Orders/Order.cs +++ b/src/Libraries/Nop.Core/Domain/Orders/Order.cs @@ -1,535 +1,539 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.Linq; -using Nop.Core.Domain.Common; -using Nop.Core.Domain.Customers; -using Nop.Core.Domain.Discounts; -using Nop.Core.Domain.Payments; -using Nop.Core.Domain.Shipping; -using Nop.Core.Domain.Tax; - -namespace Nop.Core.Domain.Orders -{ - /// - /// Represents an order - /// - public partial class Order : BaseEntity - { - - private ICollection _discountUsageHistory; - private ICollection _giftCardUsageHistory; - private ICollection _orderNotes; - private ICollection _orderItems; - private ICollection _shipments; - - #region Utilities - - protected virtual SortedDictionary ParseTaxRates(string taxRatesStr) - { - //var taxRatesDictionary = new SortedDictionary(); - var taxRatesDictionary = new SortedDictionary(); - if (String.IsNullOrEmpty(taxRatesStr)) - return taxRatesDictionary; - - string[] lines = taxRatesStr.Split(new [] { ';' }, StringSplitOptions.RemoveEmptyEntries); - foreach (string line in lines) - { - if (String.IsNullOrEmpty(line.Trim())) - continue; - - string[] taxes = line.Split(new [] { ':' }); - if (taxes.Length == 6) - { - try - { - decimal rate = decimal.Parse(taxes[0].Trim(), CultureInfo.InvariantCulture); - taxRatesDictionary.Add(rate, new TaxRateRec() - { - VatRate = rate, - Amount = decimal.Parse(taxes[1].Trim(), CultureInfo.InvariantCulture), - DiscountAmount = decimal.Parse(taxes[2].Trim(), CultureInfo.InvariantCulture), - BaseAmount = decimal.Parse(taxes[3].Trim(), CultureInfo.InvariantCulture), - VatAmount = decimal.Parse(taxes[4].Trim(), CultureInfo.InvariantCulture), - AmountIncludingVAT = decimal.Parse(taxes[5].Trim(), CultureInfo.InvariantCulture) - }); - } - catch (Exception exc) - { - Debug.WriteLine(exc.ToString()); - } - } - } - - //add at least one tax rate (0%) - if (!taxRatesDictionary.Any()) - taxRatesDictionary.Add(decimal.Zero, new TaxRateRec() - { - VatRate = decimal.Zero, - Amount = decimal.Zero, - DiscountAmount = decimal.Zero, - BaseAmount = decimal.Zero, - VatAmount = decimal.Zero, - AmountIncludingVAT = decimal.Zero - }); - - return taxRatesDictionary; - } - - #endregion - - #region Properties - - /// - /// Gets or sets the order identifier - /// - public Guid OrderGuid { get; set; } - - /// - /// Gets or sets the store identifier - /// - public int StoreId { get; set; } - - /// - /// Gets or sets the customer identifier - /// - public int CustomerId { get; set; } - - /// - /// Gets or sets the billing address identifier - /// - public int BillingAddressId { get; set; } - - /// - /// Gets or sets the shipping address identifier - /// - public int? ShippingAddressId { get; set; } - - /// - /// Gets or sets the pickup address identifier - /// - public int? PickupAddressId { get; set; } - - /// - /// Gets or sets a value indicating whether a customer chose "pick up in store" shipping option - /// - public bool PickUpInStore { get; set; } - - /// - /// Gets or sets an order status identifier - /// - public int OrderStatusId { get; set; } - - /// - /// Gets or sets the shipping status identifier - /// - public int ShippingStatusId { get; set; } - - /// - /// Gets or sets the payment status identifier - /// - public int PaymentStatusId { get; set; } - - /// - /// Gets or sets the payment method system name - /// - public string PaymentMethodSystemName { get; set; } - - /// - /// Gets or sets the customer currency code (at the moment of order placing) - /// - public string CustomerCurrencyCode { get; set; } - - /// - /// Gets or sets the currency rate - /// - public decimal CurrencyRate { get; set; } - - /// - /// Gets or sets the customer tax display type identifier - /// - public int CustomerTaxDisplayTypeId { get; set; } - - /// - /// Gets or sets the VAT number (the European Union Value Added Tax) - /// - public string VatNumber { get; set; } - - /// - /// Gets or sets the order subtotal (incl tax) - /// - public decimal OrderSubtotalInclTax { get; set; } - - /// - /// Gets or sets the order subtotal (excl tax) - /// - public decimal OrderSubtotalExclTax { get; set; } - - /// - /// Gets or sets the order subtotal discount (incl tax) - /// - public decimal OrderSubTotalDiscountInclTax { get; set; } - - /// - /// Gets or sets the order subtotal discount (excl tax) - /// - public decimal OrderSubTotalDiscountExclTax { get; set; } - - /// - /// Gets or sets the order shipping (incl tax) - /// - public decimal OrderShippingInclTax { get; set; } - - /// - /// Gets or sets the order shipping (excl tax) - /// - public decimal OrderShippingExclTax { get; set; } - - /// - /// Gets or sets the payment method additional fee (incl tax) - /// - public decimal PaymentMethodAdditionalFeeInclTax { get; set; } - - /// - /// Gets or sets the payment method additional fee (excl tax) - /// - public decimal PaymentMethodAdditionalFeeExclTax { get; set; } - - /// - /// Gets or sets the tax rates - /// - public string TaxRates { get; set; } - - /// - /// Gets or sets the order tax - /// - public decimal OrderTax { get; set; } - - /// - /// Gets or sets the order discount (applied to order total) - /// - public decimal OrderDiscount { get; set; } - - /// - /// Gets or sets the order total to pay - /// - public decimal OrderTotal { get; set; } - - /// - /// Gets or sets the refunded amount - /// - public decimal RefundedAmount { get; set; } - - /// - /// Gets or sets the reward points history entry identifier when reward points were earned (gained) for placing this order - /// - public int? RewardPointsHistoryEntryId { get; set; } - - /// - /// Gets or sets the checkout attribute description - /// - public string CheckoutAttributeDescription { get; set; } - - /// - /// Gets or sets the checkout attributes in XML format - /// - public string CheckoutAttributesXml { get; set; } - - /// - /// Gets or sets the customer language identifier - /// - public int CustomerLanguageId { get; set; } - - /// - /// Gets or sets the affiliate identifier - /// - public int AffiliateId { get; set; } - - /// - /// Gets or sets the customer IP address - /// - public string CustomerIp { get; set; } - - /// - /// Gets or sets a value indicating whether storing of credit card number is allowed - /// - public bool AllowStoringCreditCardNumber { get; set; } - - /// - /// Gets or sets the card type - /// - public string CardType { get; set; } - - /// - /// Gets or sets the card name - /// - public string CardName { get; set; } - - /// - /// Gets or sets the card number - /// - public string CardNumber { get; set; } - - /// - /// Gets or sets the masked credit card number - /// - public string MaskedCreditCardNumber { get; set; } - - /// - /// Gets or sets the card CVV2 - /// - public string CardCvv2 { get; set; } - - /// - /// Gets or sets the card expiration month - /// - public string CardExpirationMonth { get; set; } - - /// - /// Gets or sets the card expiration year - /// - public string CardExpirationYear { get; set; } - - /// - /// Gets or sets the authorization transaction identifier - /// - public string AuthorizationTransactionId { get; set; } - - /// - /// Gets or sets the authorization transaction code - /// - public string AuthorizationTransactionCode { get; set; } - - /// - /// Gets or sets the authorization transaction result - /// - public string AuthorizationTransactionResult { get; set; } - - /// - /// Gets or sets the capture transaction identifier - /// - public string CaptureTransactionId { get; set; } - - /// - /// Gets or sets the capture transaction result - /// - public string CaptureTransactionResult { get; set; } - - /// - /// Gets or sets the subscription transaction identifier - /// - public string SubscriptionTransactionId { get; set; } - - /// - /// Gets or sets the paid date and time - /// - public DateTime? PaidDateUtc { get; set; } - - /// - /// Gets or sets the shipping method - /// - public string ShippingMethod { get; set; } - - /// - /// Gets or sets the shipping rate computation method identifier or the pickup point provider identifier (if PickUpInStore is true) - /// - public string ShippingRateComputationMethodSystemName { get; set; } - - /// - /// Gets or sets the serialized CustomValues (values from ProcessPaymentRequest) - /// - public string CustomValuesXml { get; set; } - - /// - /// Gets or sets a value indicating whether the entity has been deleted - /// - public bool Deleted { get; set; } - - /// - /// Gets or sets the date and time of order creation - /// - public DateTime CreatedOnUtc { get; set; } - - /// - /// Gets or sets the invoice ID - /// - public string InvoiceId { get; set; } - /// - /// Gets or sets the invoice date UTC - /// - public DateTime? InvoiceDateUtc { get; set; } - - /// - /// Gets or sets the order total base amount excl. tax - /// - public decimal OrderAmount { get; set; } //MF 09.12.16 - - /// - /// Gets or sets the order total amount incl. tax - /// - public decimal OrderAmountIncl { get; set; } //MF 09.12.16 - #endregion - - #region Navigation properties - - /// - /// Gets or sets the customer - /// - public virtual Customer Customer { get; set; } - - /// - /// Gets or sets the billing address - /// - public virtual Address BillingAddress { get; set; } - - /// - /// Gets or sets the shipping address - /// - public virtual Address ShippingAddress { get; set; } - - /// - /// Gets or sets the pickup address - /// - public virtual Address PickupAddress { get; set; } - - /// - /// Gets or sets the reward points history record (spent by a customer when placing this order) - /// - public virtual RewardPointsHistory RedeemedRewardPointsEntry { get; set; } - - /// - /// Gets or sets discount usage history - /// - public virtual ICollection DiscountUsageHistory - { - get { return _discountUsageHistory ?? (_discountUsageHistory = new List()); } - protected set { _discountUsageHistory = value; } - } - - /// - /// Gets or sets gift card usage history (gift card that were used with this order) - /// - public virtual ICollection GiftCardUsageHistory - { - get { return _giftCardUsageHistory ?? (_giftCardUsageHistory = new List()); } - protected set { _giftCardUsageHistory = value; } - } - - /// - /// Gets or sets order notes - /// - public virtual ICollection OrderNotes - { - get { return _orderNotes ?? (_orderNotes = new List()); } - protected set { _orderNotes = value; } - } - - /// - /// Gets or sets order items - /// - public virtual ICollection OrderItems - { - get { return _orderItems ?? (_orderItems = new List()); } - protected set { _orderItems = value; } - } - - /// - /// Gets or sets shipments - /// - public virtual ICollection Shipments - { - get { return _shipments ?? (_shipments = new List()); } - protected set { _shipments = value; } - } - - #endregion - - #region Custom properties - - /// - /// Gets or sets the order status - /// - public OrderStatus OrderStatus - { - get - { - return (OrderStatus)this.OrderStatusId; - } - set - { - this.OrderStatusId = (int)value; - } - } - - /// - /// Gets or sets the payment status - /// - public PaymentStatus PaymentStatus - { - get - { - return (PaymentStatus)this.PaymentStatusId; - } - set - { - this.PaymentStatusId = (int)value; - } - } - - /// - /// Gets or sets the shipping status - /// - public ShippingStatus ShippingStatus - { - get - { - return (ShippingStatus)this.ShippingStatusId; - } - set - { - this.ShippingStatusId = (int)value; - } - } - - /// - /// Gets or sets the customer tax display type - /// - public TaxDisplayType CustomerTaxDisplayType - { - get - { - return (TaxDisplayType)this.CustomerTaxDisplayTypeId; - } - set - { - this.CustomerTaxDisplayTypeId = (int)value; - } - } - - /// - /// Gets the applied tax rates - /// - public SortedDictionary TaxRatesDictionary - { - get - { - return ParseTaxRates(this.TaxRates); - } - } - - #endregion - } - - #region Nested classes - public partial class TaxRateRec - { - public decimal VatRate { get; set; } - public decimal Amount { get; set; } - public decimal DiscountAmount { get; set; } - public decimal BaseAmount { get; set; } - public decimal VatAmount { get; set; } - public decimal AmountIncludingVAT { get; set; } - } - #endregion -} +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using Nop.Core.Domain.Common; +using Nop.Core.Domain.Customers; +using Nop.Core.Domain.Discounts; +using Nop.Core.Domain.Payments; +using Nop.Core.Domain.Shipping; +using Nop.Core.Domain.Tax; + +namespace Nop.Core.Domain.Orders +{ + /// + /// Represents an order + /// + public partial class Order : BaseEntity + { + + private ICollection _discountUsageHistory; + private ICollection _giftCardUsageHistory; + private ICollection _orderNotes; + private ICollection _orderItems; + private ICollection _shipments; + + #region Utilities + + /// + /// Parses order.TaxRates string + /// + /// + /// Returns the sorted dictionary of taxrateEntries + protected virtual SortedDictionary ParseTaxRates(string taxRatesStr) + { + var taxRatesDictionary = new SortedDictionary(); + if (String.IsNullOrEmpty(taxRatesStr)) + return taxRatesDictionary; + + string[] lines = taxRatesStr.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + foreach (string line in lines) + { + if (String.IsNullOrEmpty(line.Trim())) + continue; + + string[] taxes = line.Split(new[] { ':' }); + if (taxes.Length == 6) + { + try + { + decimal rate = decimal.Parse(taxes[0].Trim(), CultureInfo.InvariantCulture); + taxRatesDictionary.Add(rate, new TaxRateRec() + { + VatRate = rate, + Amount = decimal.Parse(taxes[1].Trim(), CultureInfo.InvariantCulture), + DiscountAmount = decimal.Parse(taxes[2].Trim(), CultureInfo.InvariantCulture), + BaseAmount = decimal.Parse(taxes[3].Trim(), CultureInfo.InvariantCulture), + VatAmount = decimal.Parse(taxes[4].Trim(), CultureInfo.InvariantCulture), + AmountIncludingVAT = decimal.Parse(taxes[5].Trim(), CultureInfo.InvariantCulture) + }); + } + catch (Exception exc) + { + Debug.WriteLine(exc.ToString()); + } + } + } + + //add at least one tax rate (0%) + if (!taxRatesDictionary.Any()) + taxRatesDictionary.Add(decimal.Zero, new TaxRateRec() + { + VatRate = decimal.Zero, + Amount = decimal.Zero, + DiscountAmount = decimal.Zero, + BaseAmount = decimal.Zero, + VatAmount = decimal.Zero, + AmountIncludingVAT = decimal.Zero + }); + + return taxRatesDictionary; + } + + #endregion + + #region Properties + + /// + /// Gets or sets the order identifier + /// + public Guid OrderGuid { get; set; } + + /// + /// Gets or sets the store identifier + /// + public int StoreId { get; set; } + + /// + /// Gets or sets the customer identifier + /// + public int CustomerId { get; set; } + + /// + /// Gets or sets the billing address identifier + /// + public int BillingAddressId { get; set; } + + /// + /// Gets or sets the shipping address identifier + /// + public int? ShippingAddressId { get; set; } + + /// + /// Gets or sets the pickup address identifier + /// + public int? PickupAddressId { get; set; } + + /// + /// Gets or sets a value indicating whether a customer chose "pick up in store" shipping option + /// + public bool PickUpInStore { get; set; } + + /// + /// Gets or sets an order status identifier + /// + public int OrderStatusId { get; set; } + + /// + /// Gets or sets the shipping status identifier + /// + public int ShippingStatusId { get; set; } + + /// + /// Gets or sets the payment status identifier + /// + public int PaymentStatusId { get; set; } + + /// + /// Gets or sets the payment method system name + /// + public string PaymentMethodSystemName { get; set; } + + /// + /// Gets or sets the customer currency code (at the moment of order placing) + /// + public string CustomerCurrencyCode { get; set; } + + /// + /// Gets or sets the currency rate + /// + public decimal CurrencyRate { get; set; } + + /// + /// Gets or sets the customer tax display type identifier + /// + public int CustomerTaxDisplayTypeId { get; set; } + + /// + /// Gets or sets the VAT number (the European Union Value Added Tax) + /// + public string VatNumber { get; set; } + + /// + /// Gets or sets the order subtotal (incl tax) + /// + public decimal OrderSubtotalInclTax { get; set; } + + /// + /// Gets or sets the order subtotal (excl tax) + /// + public decimal OrderSubtotalExclTax { get; set; } + + /// + /// Gets or sets the order subtotal discount (incl tax) + /// + public decimal OrderSubTotalDiscountInclTax { get; set; } + + /// + /// Gets or sets the order subtotal discount (excl tax) + /// + public decimal OrderSubTotalDiscountExclTax { get; set; } + + /// + /// Gets or sets the order shipping (incl tax) + /// + public decimal OrderShippingInclTax { get; set; } + + /// + /// Gets or sets the order shipping (excl tax) + /// + public decimal OrderShippingExclTax { get; set; } + + /// + /// Gets or sets the payment method additional fee (incl tax) + /// + public decimal PaymentMethodAdditionalFeeInclTax { get; set; } + + /// + /// Gets or sets the payment method additional fee (excl tax) + /// + public decimal PaymentMethodAdditionalFeeExclTax { get; set; } + + /// + /// Gets or sets the tax rates + /// + public string TaxRates { get; set; } + + /// + /// Gets or sets the order tax + /// + public decimal OrderTax { get; set; } + + /// + /// Gets or sets the order discount (applied to order total) + /// + public decimal OrderDiscount { get; set; } + + /// + /// Gets or sets the order total to pay + /// + public decimal OrderTotal { get; set; } + + /// + /// Gets or sets the refunded amount + /// + public decimal RefundedAmount { get; set; } + + /// + /// Gets or sets the reward points history entry identifier when reward points were earned (gained) for placing this order + /// + public int? RewardPointsHistoryEntryId { get; set; } + + /// + /// Gets or sets the checkout attribute description + /// + public string CheckoutAttributeDescription { get; set; } + + /// + /// Gets or sets the checkout attributes in XML format + /// + public string CheckoutAttributesXml { get; set; } + + /// + /// Gets or sets the customer language identifier + /// + public int CustomerLanguageId { get; set; } + + /// + /// Gets or sets the affiliate identifier + /// + public int AffiliateId { get; set; } + + /// + /// Gets or sets the customer IP address + /// + public string CustomerIp { get; set; } + + /// + /// Gets or sets a value indicating whether storing of credit card number is allowed + /// + public bool AllowStoringCreditCardNumber { get; set; } + + /// + /// Gets or sets the card type + /// + public string CardType { get; set; } + + /// + /// Gets or sets the card name + /// + public string CardName { get; set; } + + /// + /// Gets or sets the card number + /// + public string CardNumber { get; set; } + + /// + /// Gets or sets the masked credit card number + /// + public string MaskedCreditCardNumber { get; set; } + + /// + /// Gets or sets the card CVV2 + /// + public string CardCvv2 { get; set; } + + /// + /// Gets or sets the card expiration month + /// + public string CardExpirationMonth { get; set; } + + /// + /// Gets or sets the card expiration year + /// + public string CardExpirationYear { get; set; } + + /// + /// Gets or sets the authorization transaction identifier + /// + public string AuthorizationTransactionId { get; set; } + + /// + /// Gets or sets the authorization transaction code + /// + public string AuthorizationTransactionCode { get; set; } + + /// + /// Gets or sets the authorization transaction result + /// + public string AuthorizationTransactionResult { get; set; } + + /// + /// Gets or sets the capture transaction identifier + /// + public string CaptureTransactionId { get; set; } + + /// + /// Gets or sets the capture transaction result + /// + public string CaptureTransactionResult { get; set; } + + /// + /// Gets or sets the subscription transaction identifier + /// + public string SubscriptionTransactionId { get; set; } + + /// + /// Gets or sets the paid date and time + /// + public DateTime? PaidDateUtc { get; set; } + + /// + /// Gets or sets the shipping method + /// + public string ShippingMethod { get; set; } + + /// + /// Gets or sets the shipping rate computation method identifier or the pickup point provider identifier (if PickUpInStore is true) + /// + public string ShippingRateComputationMethodSystemName { get; set; } + + /// + /// Gets or sets the serialized CustomValues (values from ProcessPaymentRequest) + /// + public string CustomValuesXml { get; set; } + + /// + /// Gets or sets a value indicating whether the entity has been deleted + /// + public bool Deleted { get; set; } + + /// + /// Gets or sets the date and time of order creation + /// + public DateTime CreatedOnUtc { get; set; } + + /// + /// Gets or sets the invoice ID + /// + public string InvoiceId { get; set; } + /// + /// Gets or sets the invoice date UTC + /// + public DateTime? InvoiceDateUtc { get; set; } + + /// + /// Gets or sets the order total base amount excl. tax + /// + public decimal OrderAmount { get; set; } //MF 09.12.16 + + /// + /// Gets or sets the order total amount incl. tax + /// + public decimal OrderAmountIncl { get; set; } //MF 09.12.16 + #endregion + + #region Navigation properties + + /// + /// Gets or sets the customer + /// + public virtual Customer Customer { get; set; } + + /// + /// Gets or sets the billing address + /// + public virtual Address BillingAddress { get; set; } + + /// + /// Gets or sets the shipping address + /// + public virtual Address ShippingAddress { get; set; } + + /// + /// Gets or sets the pickup address + /// + public virtual Address PickupAddress { get; set; } + + /// + /// Gets or sets the reward points history record (spent by a customer when placing this order) + /// + public virtual RewardPointsHistory RedeemedRewardPointsEntry { get; set; } + + /// + /// Gets or sets discount usage history + /// + public virtual ICollection DiscountUsageHistory + { + get { return _discountUsageHistory ?? (_discountUsageHistory = new List()); } + protected set { _discountUsageHistory = value; } + } + + /// + /// Gets or sets gift card usage history (gift card that were used with this order) + /// + public virtual ICollection GiftCardUsageHistory + { + get { return _giftCardUsageHistory ?? (_giftCardUsageHistory = new List()); } + protected set { _giftCardUsageHistory = value; } + } + + /// + /// Gets or sets order notes + /// + public virtual ICollection OrderNotes + { + get { return _orderNotes ?? (_orderNotes = new List()); } + protected set { _orderNotes = value; } + } + + /// + /// Gets or sets order items + /// + public virtual ICollection OrderItems + { + get { return _orderItems ?? (_orderItems = new List()); } + protected set { _orderItems = value; } + } + + /// + /// Gets or sets shipments + /// + public virtual ICollection Shipments + { + get { return _shipments ?? (_shipments = new List()); } + protected set { _shipments = value; } + } + + #endregion + + #region Custom properties + + /// + /// Gets or sets the order status + /// + public OrderStatus OrderStatus + { + get + { + return (OrderStatus)this.OrderStatusId; + } + set + { + this.OrderStatusId = (int)value; + } + } + + /// + /// Gets or sets the payment status + /// + public PaymentStatus PaymentStatus + { + get + { + return (PaymentStatus)this.PaymentStatusId; + } + set + { + this.PaymentStatusId = (int)value; + } + } + + /// + /// Gets or sets the shipping status + /// + public ShippingStatus ShippingStatus + { + get + { + return (ShippingStatus)this.ShippingStatusId; + } + set + { + this.ShippingStatusId = (int)value; + } + } + + /// + /// Gets or sets the customer tax display type + /// + public TaxDisplayType CustomerTaxDisplayType + { + get + { + return (TaxDisplayType)this.CustomerTaxDisplayTypeId; + } + set + { + this.CustomerTaxDisplayTypeId = (int)value; + } + } + + /// + /// Gets the applied tax rates + /// + public SortedDictionary TaxRatesDictionary + { + get + { + return ParseTaxRates(this.TaxRates); + } + } + + #endregion + } + + #region Nested classes + public partial class TaxRateRec + { + public decimal VatRate { get; set; } + public decimal Amount { get; set; } + public decimal DiscountAmount { get; set; } + public decimal BaseAmount { get; set; } + public decimal VatAmount { get; set; } + public decimal AmountIncludingVAT { get; set; } + } + #endregion +} diff --git a/src/Libraries/Nop.Core/Html/HtmlHelper.cs b/src/Libraries/Nop.Core/Html/HtmlHelper.cs index af886767b2a..5defb54fe6c 100644 --- a/src/Libraries/Nop.Core/Html/HtmlHelper.cs +++ b/src/Libraries/Nop.Core/Html/HtmlHelper.cs @@ -1,227 +1,232 @@ -using System; -using System.Text; -using System.Text.RegularExpressions; -using System.Web; - -namespace Nop.Core.Html -{ - /// - /// Represents a HTML helper - /// - public partial class HtmlHelper - { - #region Fields - private readonly static Regex paragraphStartRegex = new Regex("

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

    ", RegexOptions.IgnoreCase); - //private static Regex ampRegex = new Regex("&(?!(?:#[0-9]{2,4};|[a-z0-9]+;))", RegexOptions.Compiled | RegexOptions.IgnoreCase); - - #endregion - - #region Utilities - - private static string EnsureOnlyAllowedHtml(string text) - { - if (String.IsNullOrEmpty(text)) - return string.Empty; - - const string allowedTags = "br,hr,b,i,u,a,div,ol,ul,li,blockquote,img,span,p,em,strong,font,pre,h1,h2,h3,h4,h5,h6,address,cite"; - - var m = Regex.Matches(text, "<.*?>", RegexOptions.IgnoreCase); - for (int i = m.Count - 1; i >= 0; i--) - { - string tag = text.Substring(m[i].Index + 1, m[i].Length - 1).Trim().ToLower(); - - if (!IsValidTag(tag, allowedTags)) - { - text = text.Remove(m[i].Index, m[i].Length); - } - } - - return 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; - - var endchars = new [] { ' ', '>', '/', '\t' }; - - int pos = tag.IndexOfAny(endchars, 1); - 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; - } - #endregion - - #region Methods - /// - /// Formats the text - /// - /// Text - /// A value indicating whether to strip tags - /// A value indicating whether HTML is allowed - /// A value indicating whether HTML is allowed - /// A value indicating whether BBCode is allowed - /// A value indicating whether to resolve links - /// A value indicating whether to add "noFollow" tag - /// Formatted text - public static string FormatText(string text, bool stripTags, - bool convertPlainTextToHtml, bool allowHtml, - bool allowBBCode, bool resolveLinks, bool addNoFollowTag) - { - - if (String.IsNullOrEmpty(text)) - return string.Empty; - - try - { - if (stripTags) - { - text = StripTags(text); - } - - text = allowHtml ? EnsureOnlyAllowedHtml(text) : HttpUtility.HtmlEncode(text); - - if (convertPlainTextToHtml) - { - text = ConvertPlainTextToHtml(text); - } - - if (allowBBCode) - { - text = BBCodeHelper.FormatText(text, true, true, true, true, true, true, true); - } - - if (resolveLinks) - { - text = ResolveLinksHelper.FormatText(text); - } - - if (addNoFollowTag) - { - //add noFollow tag. not implemented - } - } - catch (Exception exc) - { - text = string.Format("Text cannot be formatted. Error: {0}", exc.Message); - } - return text; - } - - /// - /// Strips tags - /// - /// Text - /// Formatted text - public static string StripTags(string text) - { - if (String.IsNullOrEmpty(text)) - return string.Empty; - - text = Regex.Replace(text, @"(>)(\r|\n)*(<)", "><"); - text = Regex.Replace(text, "(<[^>]*>)([^<]*)", "$2"); - text = Regex.Replace(text, "(&#x?[0-9]{2,4};|"|&| |<|>|€|©|®|‰|‡|†|‹|›|„|”|“|‚|’|‘|—|–|‏|‎|‍|‌| | | |˜|ˆ|Ÿ|š|Š)", "@"); - - return text; - } - - ///
    - /// Text - /// Text - public static string ReplaceAnchorTags(string text) - { - if (String.IsNullOrEmpty(text)) - return string.Empty; - - text = Regex.Replace(text, @"]+>([^<]*(?:(?!", "$1", RegexOptions.IgnoreCase); - return text; - } - - /// - /// Converts plain text to HTML - /// - /// Text - /// Formatted text - public static string ConvertPlainTextToHtml(string text) - { - if (String.IsNullOrEmpty(text)) - return string.Empty; - - text = text.Replace("\r\n", "
    "); - text = text.Replace("\r", "
    "); - text = text.Replace("\n", "
    "); - text = text.Replace("\t", "  "); - text = text.Replace(" ", "  "); - - return text; - } - - /// - /// Converts HTML to plain text - /// - /// Text - /// A value indicating whether to decode text - /// A value indicating whether to replace anchor text (remove a tag from the following url Name and output only the string "Name") - /// Formatted text - public static string ConvertHtmlToPlainText(string text, - bool decode = false, bool replaceAnchorTags = false) - { - if (String.IsNullOrEmpty(text)) - return string.Empty; - - if (decode) - text = HttpUtility.HtmlDecode(text); - - text = text.Replace("
    ", "\n"); - text = text.Replace("
    ", "\n"); - text = text.Replace("
    ", "\n"); - text = text.Replace("  ", "\t"); - text = text.Replace("  ", " "); - - if (replaceAnchorTags) - text = ReplaceAnchorTags(text); - - return text; - } - - /// - /// Converts text to paragraph - /// - /// Text - /// Formatted text - public static string ConvertPlainTextToParagraph(string text) - { - if (String.IsNullOrEmpty(text)) - return string.Empty; - - text = paragraphStartRegex.Replace(text, string.Empty); - text = paragraphEndRegex.Replace(text, "\n"); - text = text.Replace("\r\n", "\n").Replace("\r", "\n"); - text = text + "\n\n"; - text = text.Replace("\n\n", "\n"); - var strArray = text.Split(new [] { '\n' }); - var builder = new StringBuilder(); - foreach (string str in strArray) - { - if ((str != null) && (str.Trim().Length > 0)) - { - builder.AppendFormat("

    {0}

    \n", str); - } - } - return builder.ToString(); - } - #endregion - } -} +using System; +using System.Text; +using System.Text.RegularExpressions; +using System.Web; + +namespace Nop.Core.Html +{ + /// + /// Represents a HTML helper + /// + public partial class HtmlHelper + { + #region Fields + private readonly static Regex paragraphStartRegex = new Regex("

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

    ", RegexOptions.IgnoreCase); + //private static Regex ampRegex = new Regex("&(?!(?:#[0-9]{2,4};|[a-z0-9]+;))", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + #endregion + + #region Utilities + + private static string EnsureOnlyAllowedHtml(string text) + { + if (String.IsNullOrEmpty(text)) + return string.Empty; + + const string allowedTags = "br,hr,b,i,u,a,div,ol,ul,li,blockquote,img,span,p,em,strong,font,pre,h1,h2,h3,h4,h5,h6,address,cite"; + + var m = Regex.Matches(text, "<.*?>", RegexOptions.IgnoreCase); + for (int i = m.Count - 1; i >= 0; i--) + { + string tag = text.Substring(m[i].Index + 1, m[i].Length - 1).Trim().ToLower(); + + if (!IsValidTag(tag, allowedTags)) + { + text = text.Remove(m[i].Index, m[i].Length); + } + } + + return 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; + + var endchars = new [] { ' ', '>', '/', '\t' }; + + int pos = tag.IndexOfAny(endchars, 1); + 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; + } + #endregion + + #region Methods + /// + /// Formats the text + /// + /// Text + /// A value indicating whether to strip tags + /// A value indicating whether HTML is allowed + /// A value indicating whether HTML is allowed + /// A value indicating whether BBCode is allowed + /// A value indicating whether to resolve links + /// A value indicating whether to add "noFollow" tag + /// Formatted text + public static string FormatText(string text, bool stripTags, + bool convertPlainTextToHtml, bool allowHtml, + bool allowBBCode, bool resolveLinks, bool addNoFollowTag) + { + + if (String.IsNullOrEmpty(text)) + return string.Empty; + + try + { + if (stripTags) + { + text = StripTags(text); + } + + text = allowHtml ? EnsureOnlyAllowedHtml(text) : HttpUtility.HtmlEncode(text); + + if (convertPlainTextToHtml) + { + text = ConvertPlainTextToHtml(text); + } + + if (allowBBCode) + { + text = BBCodeHelper.FormatText(text, true, true, true, true, true, true, true); + } + + if (resolveLinks) + { + text = ResolveLinksHelper.FormatText(text); + } + + if (addNoFollowTag) + { + //add noFollow tag. not implemented + } + } + catch (Exception exc) + { + text = string.Format("Text cannot be formatted. Error: {0}", exc.Message); + } + return text; + } + + /// + /// Strips tags + /// + /// Text + /// Formatted text + public static string StripTags(string text) + { + if (String.IsNullOrEmpty(text)) + return string.Empty; + + text = Regex.Replace(text, @"(>)(\r|\n)*(<)", "><"); + text = Regex.Replace(text, "(<[^>]*>)([^<]*)", "$2"); + text = Regex.Replace(text, "(&#x?[0-9]{2,4};|"|&| |<|>|€|©|®|‰|‡|†|‹|›|„|”|“|‚|’|‘|—|–|‏|‎|‍|‌| | | |˜|ˆ|Ÿ|š|Š)", "@"); + + return text; + } + + /// + /// replace anchor text (remove a tag from the following url Name and output only the string "Name") + /// + /// Text + /// Text + public static string ReplaceAnchorTags(string text) + { + if (String.IsNullOrEmpty(text)) + return string.Empty; + + text = Regex.Replace(text, @"]+>([^<]*(?:(?!", "$1", RegexOptions.IgnoreCase); + return text; + } + + /// + /// Converts plain text to HTML + /// + /// Text + /// Formatted text + public static string ConvertPlainTextToHtml(string text) + { + if (String.IsNullOrEmpty(text)) + return string.Empty; + + text = text.Replace("\r\n", "
    "); + text = text.Replace("\r", "
    "); + text = text.Replace("\n", "
    "); + text = text.Replace("\t", "  "); + text = text.Replace(" ", "  "); + + return text; + } + + /// + /// Converts HTML to plain text + /// + /// Text + /// A value indicating whether to decode text + /// A value indicating whether to replace anchor text (remove a tag from the following url Name and output only the string "Name") + /// Formatted text + public static string ConvertHtmlToPlainText(string text, + bool decode = false, bool replaceAnchorTags = false) + { + if (String.IsNullOrEmpty(text)) + return string.Empty; + + if (decode) + text = HttpUtility.HtmlDecode(text); + + text = text.Replace("
    ", "\n"); + text = text.Replace("
    ", "\n"); + text = text.Replace("
    ", "\n"); + text = text.Replace("  ", "\t"); + text = text.Replace("  ", " "); + //attribute Vat tags to remove + text = text.Replace("", ""); + text = text.Replace("", "\t"); + text = text.Replace("", ""); + + + if (replaceAnchorTags) + text = ReplaceAnchorTags(text); + + return text; + } + + /// + /// Converts text to paragraph + /// + /// Text + /// Formatted text + public static string ConvertPlainTextToParagraph(string text) + { + if (String.IsNullOrEmpty(text)) + return string.Empty; + + text = paragraphStartRegex.Replace(text, string.Empty); + text = paragraphEndRegex.Replace(text, "\n"); + text = text.Replace("\r\n", "\n").Replace("\r", "\n"); + text = text + "\n\n"; + text = text.Replace("\n\n", "\n"); + var strArray = text.Split(new [] { '\n' }); + var builder = new StringBuilder(); + foreach (string str in strArray) + { + if ((str != null) && (str.Trim().Length > 0)) + { + builder.AppendFormat("

    {0}

    \n", str); + } + } + return builder.ToString(); + } + #endregion + } +} diff --git a/src/Libraries/Nop.Services/Catalog/IPriceCalculationService.cs b/src/Libraries/Nop.Services/Catalog/IPriceCalculationService.cs index 9012932b96e..6e03c43f27d 100644 --- a/src/Libraries/Nop.Services/Catalog/IPriceCalculationService.cs +++ b/src/Libraries/Nop.Services/Catalog/IPriceCalculationService.cs @@ -1,158 +1,158 @@ -using System; -using System.Collections.Generic; -using Nop.Core.Domain.Catalog; -using Nop.Core.Domain.Customers; -using Nop.Core.Domain.Orders; -using Nop.Services.Discounts; - -namespace Nop.Services.Catalog -{ - /// - /// Price calculation service - /// - public partial interface IPriceCalculationService - { - /// - /// 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 - /// Final price - decimal GetFinalPrice(Product product, - Customer customer, - decimal additionalCharge = decimal.Zero, - bool includeDiscounts = true, - int quantity = 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 - /// Applied discount amount - /// Applied discounts - /// Final price - decimal GetFinalPrice(Product product, - Customer customer, - decimal additionalCharge, - bool includeDiscounts, - int quantity, - out decimal discountAmount, - out List appliedDiscounts); - /// - /// 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 - /// Rental period start date (for rental products) - /// Rental period end date (for rental products) - /// Applied discount amount - /// Applied discounts - /// Final price - decimal GetFinalPrice(Product product, - Customer customer, - decimal additionalCharge, - bool includeDiscounts, - int quantity, - DateTime? rentalStartDate, - DateTime? rentalEndDate, - out decimal discountAmount, - out List appliedDiscounts); - - - - /// - /// Gets the shopping cart unit price (one item) - /// - /// The shopping cart item - /// A value indicating whether include discounts or not for price computation - /// Shopping cart unit price (one item) - decimal GetUnitPrice(ShoppingCartItem shoppingCartItem, - bool includeDiscounts = true); - /// - /// Gets the shopping cart unit price (one item) - /// - /// The shopping cart item - /// A value indicating whether include discounts or not for price computation - /// Applied discount amount - /// Applied discounts - /// Shopping cart unit price (one item) - decimal GetUnitPrice(ShoppingCartItem shoppingCartItem, - bool includeDiscounts, - out decimal discountAmount, - out List appliedDiscounts); - /// - /// Gets the shopping cart unit price (one item) - /// - /// Product - /// Customer - /// Shopping cart type - /// Quantity - /// Product atrributes (XML format) - /// Customer entered price (if specified) - /// Rental start date (null for not rental products) - /// Rental end date (null for not rental products) - /// A value indicating whether include discounts or not for price computation - /// Applied discount amount - /// Applied discounts - /// Shopping cart unit price (one item) - decimal GetUnitPrice(Product product, - Customer customer, - ShoppingCartType shoppingCartType, - int quantity, - string attributesXml, - decimal customerEnteredPrice, - DateTime? rentalStartDate, DateTime? rentalEndDate, - bool includeDiscounts, - out decimal discountAmount, - out List appliedDiscounts); - /// - /// Gets the shopping cart item sub total - /// - /// The shopping cart item - /// A value indicating whether include discounts or not for price computation - /// Shopping cart item sub total - decimal GetSubTotal(ShoppingCartItem shoppingCartItem, - bool includeDiscounts = true); - /// - /// Gets the shopping cart item sub total - /// - /// The shopping cart item - /// A value indicating whether include discounts or not for price computation - /// Applied discount amount - /// Applied discounts - /// Maximum discounted qty. Return not nullable value if discount cannot be applied to ALL items - /// Shopping cart item sub total - decimal GetSubTotal(ShoppingCartItem shoppingCartItem, - bool includeDiscounts, - out decimal discountAmount, - out List appliedDiscounts, - out int? maximumDiscountQty); - - /// - /// Gets the product cost (one item) - /// - /// Product - /// Shopping cart item attributes in XML - /// Product cost (one item) - decimal GetProductCost(Product product, string attributesXml); - - - - - /// - /// Get a price adjustment of a product attribute value - /// - /// Product attribute value - /// Price adjustment - decimal GetProductAttributeValuePriceAdjustment(ProductAttributeValue value); - } -} +using System; +using System.Collections.Generic; +using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Customers; +using Nop.Core.Domain.Orders; +using Nop.Services.Discounts; + +namespace Nop.Services.Catalog +{ + /// + /// Price calculation service + /// + public partial interface IPriceCalculationService + { + /// + /// 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 + /// Final price + decimal GetFinalPrice(Product product, + Customer customer, + decimal additionalCharge = decimal.Zero, + bool includeDiscounts = true, + int quantity = 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 + /// Applied discount amount + /// Applied discounts + /// Final price + decimal GetFinalPrice(Product product, + Customer customer, + decimal additionalCharge, + bool includeDiscounts, + int quantity, + out decimal discountAmount, + out List appliedDiscounts); + /// + /// 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 + /// Rental period start date (for rental products) + /// Rental period end date (for rental products) + /// Applied discount amount + /// Applied discounts + /// Final price + decimal GetFinalPrice(Product product, + Customer customer, + decimal additionalCharge, + bool includeDiscounts, + int quantity, + DateTime? rentalStartDate, + DateTime? rentalEndDate, + out decimal discountAmount, + out List appliedDiscounts); + + + + /// + /// Gets the shopping cart unit price (one item) + /// + /// The shopping cart item + /// A value indicating whether include discounts or not for price computation + /// Shopping cart unit price (one item) + decimal GetUnitPrice(ShoppingCartItem shoppingCartItem, + bool includeDiscounts = true); + /// + /// Gets the shopping cart unit price (one item) + /// + /// The shopping cart item + /// A value indicating whether include discounts or not for price computation + /// Applied discount amount + /// Applied discounts + /// Shopping cart unit price (one item) + decimal GetUnitPrice(ShoppingCartItem shoppingCartItem, + bool includeDiscounts, + out decimal discountAmount, + out List appliedDiscounts); + /// + /// Gets the shopping cart unit price (one item) + /// + /// Product + /// Customer + /// Shopping cart type + /// Quantity + /// Product atrributes (XML format) + /// Customer entered price (if specified) + /// Rental start date (null for not rental products) + /// Rental end date (null for not rental products) + /// A value indicating whether include discounts or not for price computation + /// Applied discount amount + /// Applied discounts + /// Shopping cart unit price (one item) + decimal GetUnitPrice(Product product, + Customer customer, + ShoppingCartType shoppingCartType, + int quantity, + ref string attributesXml, + decimal customerEnteredPrice, + DateTime? rentalStartDate, DateTime? rentalEndDate, + bool includeDiscounts, + out decimal discountAmount, + out List appliedDiscounts); + /// + /// Gets the shopping cart item sub total + /// + /// The shopping cart item + /// A value indicating whether include discounts or not for price computation + /// Shopping cart item sub total + decimal GetSubTotal(ShoppingCartItem shoppingCartItem, + bool includeDiscounts = true); + /// + /// Gets the shopping cart item sub total + /// + /// The shopping cart item + /// A value indicating whether include discounts or not for price computation + /// Applied discount amount + /// Applied discounts + /// Maximum discounted qty. Return not nullable value if discount cannot be applied to ALL items + /// Shopping cart item sub total + decimal GetSubTotal(ShoppingCartItem shoppingCartItem, + bool includeDiscounts, + out decimal discountAmount, + out List appliedDiscounts, + out int? maximumDiscountQty); + + /// + /// Gets the product cost (one item) + /// + /// Product + /// Shopping cart item attributes in XML + /// Product cost (one item) + decimal GetProductCost(Product product, string attributesXml); + + + + + /// + /// Get a price adjustment of a product attribute value + /// + /// Product attribute value + /// Price adjustment + decimal GetProductAttributeValuePriceAdjustment(ProductAttributeValue value); + } +} diff --git a/src/Libraries/Nop.Services/Catalog/IProductAttributeFormatter.cs b/src/Libraries/Nop.Services/Catalog/IProductAttributeFormatter.cs index 25996258a40..0848706934d 100644 --- a/src/Libraries/Nop.Services/Catalog/IProductAttributeFormatter.cs +++ b/src/Libraries/Nop.Services/Catalog/IProductAttributeFormatter.cs @@ -1,37 +1,39 @@ -using Nop.Core.Domain.Catalog; -using Nop.Core.Domain.Customers; - -namespace Nop.Services.Catalog -{ - /// - /// Product attribute formatter interface - /// - public partial interface IProductAttributeFormatter - { - /// - /// Formats attributes - /// - /// Product - /// Attributes in XML format - /// Attributes - string FormatAttributes(Product product, string attributesXml); - - /// - /// Formats attributes - /// - /// Product - /// Attributes in XML format - /// Customer - /// Serapator - /// A value indicating whether to encode (HTML) values - /// A value indicating whether to render prices - /// A value indicating whether to render product attributes - /// A value indicating whether to render gift card attributes - /// A value indicating whether to HTML hyperink tags could be rendered (if required) - /// Attributes - string FormatAttributes(Product product, string attributesXml, - Customer customer, string serapator = "
    ", bool htmlEncode = true, bool renderPrices = true, - bool renderProductAttributes = true, bool renderGiftCardAttributes = true, - bool allowHyperlinks = true); - } -} +using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Customers; + +namespace Nop.Services.Catalog +{ + /// + /// Product attribute formatter interface + /// + public partial interface IProductAttributeFormatter + { + /// + /// Formats attributes + /// + /// Product + /// Attributes in XML format + /// Attributes + string FormatAttributes(Product product, string attributesXml); + + /// + /// Formats attributes + /// + /// Product + /// Attributes in XML format + /// Customer + /// Serapator + /// A value indicating whether to encode (HTML) values + /// A value indicating whether to render prices + /// A value indicating whether to render product attributes + /// A value indicating whether to render gift card attributes + /// A value indicating whether to HTML hyperink tags could be rendered (if required) + /// A value indicating if attribute VAT should be rendered with price. Decimal.MinusOne is default, i.e. don't render + /// Attributes + string FormatAttributes(Product product, string attributesXml, + Customer customer, string serapator = "
    ", bool htmlEncode = true, bool renderPrices = true, + bool renderProductAttributes = true, bool renderGiftCardAttributes = true, + bool allowHyperlinks = true, + decimal subTotal = decimal.MinusOne); + } +} diff --git a/src/Libraries/Nop.Services/Catalog/IProductAttributeParser.cs b/src/Libraries/Nop.Services/Catalog/IProductAttributeParser.cs index e68aa2553de..fe2ef5d3007 100644 --- a/src/Libraries/Nop.Services/Catalog/IProductAttributeParser.cs +++ b/src/Libraries/Nop.Services/Catalog/IProductAttributeParser.cs @@ -1,122 +1,139 @@ -using System.Collections.Generic; -using Nop.Core.Domain.Catalog; - -namespace Nop.Services.Catalog -{ - /// - /// Product attribute parser interface - /// - public partial interface IProductAttributeParser - { - #region Product attributes - - /// - /// Gets selected product attribute mappings - /// - /// Attributes in XML format - /// Selected product attribute mappings - IList ParseProductAttributeMappings(string attributesXml); - - /// - /// Get product attribute values - /// - /// Attributes in XML format - /// Product attribute mapping identifier; pass 0 to load all values - /// Product attribute values - IList ParseProductAttributeValues(string attributesXml, int productAttributeMappingId = 0); - - /// - /// Gets selected product attribute values - /// - /// Attributes in XML format - /// Product attribute mapping identifier - /// Product attribute values - IList ParseValues(string attributesXml, int productAttributeMappingId); - - /// - /// Adds an attribute - /// - /// Attributes in XML format - /// Product attribute mapping - /// Value - /// Quantity (used with AttributeValueType.AssociatedToProduct to specify the quantity entered by the customer) - /// Updated result (XML format) - string AddProductAttribute(string attributesXml, ProductAttributeMapping productAttributeMapping, string value, int? quantity = null); - - /// - /// Remove an attribute - /// - /// Attributes in XML format - /// Product attribute mapping - /// Updated result (XML format) - string RemoveProductAttribute(string attributesXml, ProductAttributeMapping productAttributeMapping); - - /// - /// Are attributes equal - /// - /// The attributes of the first product - /// The attributes of the second product - /// A value indicating whether we should ignore non-combinable attributes - /// A value indicating whether we should ignore the quantity of attribute value entered by the customer - /// Result - bool AreProductAttributesEqual(string attributesXml1, string attributesXml2, bool ignoreNonCombinableAttributes, bool ignoreQuantity = true); - - /// - /// Check whether condition of some attribute is met (if specified). Return "null" if not condition is specified - /// - /// Product attribute - /// Selected attributes (XML format) - /// Result - bool? IsConditionMet(ProductAttributeMapping pam, string selectedAttributesXml); - - /// - /// Finds a product attribute combination by attributes stored in XML - /// - /// Product - /// Attributes in XML format - /// A value indicating whether we should ignore non-combinable attributes - /// Found product attribute combination - ProductAttributeCombination FindProductAttributeCombination(Product product, - string attributesXml, bool ignoreNonCombinableAttributes = true); - - /// - /// Generate all combinations - /// - /// Product - /// A value indicating whether we should ignore non-combinable attributes - /// Attribute combinations in XML format - IList GenerateAllCombinations(Product product, bool ignoreNonCombinableAttributes = false); - - #endregion - - #region Gift card attributes - - /// - /// Add gift card attrbibutes - /// - /// Attributes in XML format - /// Recipient name - /// Recipient email - /// Sender name - /// Sender email - /// Message - /// Attributes - string AddGiftCardAttribute(string attributesXml, string recipientName, - string recipientEmail, string senderName, string senderEmail, string giftCardMessage); - - /// - /// Get gift card attrbibutes - /// - /// Attributes in XML format - /// Recipient name - /// Recipient email - /// Sender name - /// Sender email - /// Message - void GetGiftCardAttribute(string attributesXml, out string recipientName, - out string recipientEmail, out string senderName, - out string senderEmail, out string giftCardMessage); - - #endregion - } -} +using System.Collections.Generic; +using Nop.Core.Domain.Catalog; +using Nop.Services.Tax; + +namespace Nop.Services.Catalog +{ + /// + /// Product attribute parser interface + /// + public partial interface IProductAttributeParser + { + #region Product attributes + + /// + /// Gets selected product attribute mappings + /// + /// Attributes in XML format + /// Selected product attribute mappings + IList ParseProductAttributeMappings(string attributesXml); + + /// + /// Get product attribute values + /// + /// Attributes in XML format + /// Product attribute mapping identifier; pass 0 to load all values + /// Product attribute values + IList ParseProductAttributeValues(string attributesXml, int productAttributeMappingId = 0); + + /// + /// Gets selected product attribute values + /// + /// Attributes in XML format + /// Product attribute mapping identifier + /// Product attribute values + IList ParseValues(string attributesXml, int productAttributeMappingId); + + /// + /// Adds an attribute + /// + /// Attributes in XML format + /// Product attribute mapping + /// Value + /// Quantity (used with AttributeValueType.AssociatedToProduct to specify the quantity entered by the customer) + /// Updated result (XML format) + string AddProductAttribute(string attributesXml, ProductAttributeMapping productAttributeMapping, string value, int? quantity = null); + + /// + /// Remove an attribute + /// + /// Attributes in XML format + /// Product attribute mapping + /// Updated result (XML format) + string RemoveProductAttribute(string attributesXml, ProductAttributeMapping productAttributeMapping); + + /// + /// Are attributes equal + /// + /// The attributes of the first product + /// The attributes of the second product + /// A value indicating whether we should ignore non-combinable attributes + /// A value indicating whether we should ignore the quantity of attribute value entered by the customer + /// Result + bool AreProductAttributesEqual(string attributesXml1, string attributesXml2, bool ignoreNonCombinableAttributes, bool ignoreQuantity = true); + + /// + /// Check whether condition of some attribute is met (if specified). Return "null" if not condition is specified + /// + /// Product attribute + /// Selected attributes (XML format) + /// Result + bool? IsConditionMet(ProductAttributeMapping pam, string selectedAttributesXml); + + /// + /// Finds a product attribute combination by attributes stored in XML + /// + /// Product + /// Attributes in XML format + /// A value indicating whether we should ignore non-combinable attributes + /// Found product attribute combination + ProductAttributeCombination FindProductAttributeCombination(Product product, + string attributesXml, bool ignoreNonCombinableAttributes = true); + + /// + /// Generate all combinations + /// + /// Product + /// A value indicating whether we should ignore non-combinable attributes + /// Attribute combinations in XML format + IList GenerateAllCombinations(Product product, bool ignoreNonCombinableAttributes = false); + + #endregion + #region taxAttribute + /// + /// Adds tax subdivision to existing attributesXml + /// + /// Attributes in XML format + /// Set Product tax subdivision + /// Updated result (XML format) + string AddTaxAttribute(string attributesXml, TaxSummary taxSummary); + + /// + /// Parse ProductAttributesTax + /// + /// Attributes in XML format + /// SortedDictionary with vatRate and vatRateWeight + SortedDictionary ParseTaxAttribute(string attributesXml); + #endregion + + #region Gift card attributes + + /// + /// Add gift card attrbibutes + /// + /// Attributes in XML format + /// Recipient name + /// Recipient email + /// Sender name + /// Sender email + /// Message + /// Attributes + string AddGiftCardAttribute(string attributesXml, string recipientName, + string recipientEmail, string senderName, string senderEmail, string giftCardMessage); + + /// + /// Get gift card attrbibutes + /// + /// Attributes in XML format + /// Recipient name + /// Recipient email + /// Sender name + /// Sender email + /// Message + void GetGiftCardAttribute(string attributesXml, out string recipientName, + out string recipientEmail, out string senderName, + out string senderEmail, out string giftCardMessage); + + #endregion + } +} diff --git a/src/Libraries/Nop.Services/Catalog/PriceCalculationService.cs b/src/Libraries/Nop.Services/Catalog/PriceCalculationService.cs index 4169a8fe86d..654586c05b4 100644 --- a/src/Libraries/Nop.Services/Catalog/PriceCalculationService.cs +++ b/src/Libraries/Nop.Services/Catalog/PriceCalculationService.cs @@ -11,6 +11,8 @@ using Nop.Services.Catalog.Cache; using Nop.Services.Customers; using Nop.Services.Discounts; +using Nop.Services.Tax; +using Nop.Core.Domain.Tax; namespace Nop.Services.Catalog { @@ -31,6 +33,7 @@ public partial class PriceCalculationService : IPriceCalculationService private readonly ICacheManager _cacheManager; private readonly ShoppingCartSettings _shoppingCartSettings; private readonly CatalogSettings _catalogSettings; + private readonly ITaxService _taxService; #endregion @@ -38,14 +41,15 @@ public partial class PriceCalculationService : IPriceCalculationService public PriceCalculationService(IWorkContext workContext, IStoreContext storeContext, - IDiscountService discountService, + IDiscountService discountService, ICategoryService categoryService, IManufacturerService manufacturerService, - IProductAttributeParser productAttributeParser, + IProductAttributeParser productAttributeParser, IProductService productService, ICacheManager cacheManager, - ShoppingCartSettings shoppingCartSettings, - CatalogSettings catalogSettings) + ShoppingCartSettings shoppingCartSettings, + CatalogSettings catalogSettings, + ITaxService taxService) { this._workContext = workContext; this._storeContext = storeContext; @@ -57,8 +61,9 @@ public PriceCalculationService(IWorkContext workContext, this._cacheManager = cacheManager; this._shoppingCartSettings = shoppingCartSettings; this._catalogSettings = catalogSettings; + this._taxService = taxService; } - + #endregion #region Nested classes @@ -302,9 +307,9 @@ public virtual decimal GetFinalPrice(Product product, /// Applied discount amount /// Applied discounts /// Final price - public virtual decimal GetFinalPrice(Product product, + public virtual decimal GetFinalPrice(Product product, Customer customer, - decimal additionalCharge, + decimal additionalCharge, bool includeDiscounts, int quantity, out decimal discountAmount, @@ -355,10 +360,10 @@ public virtual decimal GetFinalPrice(Product product, /// Applied discount amount /// Applied discounts /// Final price - public virtual decimal GetFinalPrice(Product product, + public virtual decimal GetFinalPrice(Product product, Customer customer, decimal? overriddenProductPrice, - decimal additionalCharge, + decimal additionalCharge, bool includeDiscounts, int quantity, DateTime? rentalStartDate, @@ -376,7 +381,7 @@ public virtual decimal GetFinalPrice(Product product, product.Id, overriddenProductPrice.HasValue ? overriddenProductPrice.Value.ToString(CultureInfo.InvariantCulture) : null, additionalCharge.ToString(CultureInfo.InvariantCulture), - includeDiscounts, + includeDiscounts, quantity, string.Join(",", customer.GetCustomerRoleIds()), _storeContext.CurrentStore.Id); @@ -469,18 +474,20 @@ public virtual decimal GetUnitPrice(ShoppingCartItem shoppingCartItem, { if (shoppingCartItem == null) throw new ArgumentNullException("shoppingCartItem"); - - return GetUnitPrice(shoppingCartItem.Product, + var attributesXml = shoppingCartItem.AttributesXml; + var unitPrice = GetUnitPrice(shoppingCartItem.Product, shoppingCartItem.Customer, shoppingCartItem.ShoppingCartType, shoppingCartItem.Quantity, - shoppingCartItem.AttributesXml, + ref attributesXml, shoppingCartItem.CustomerEnteredPrice, shoppingCartItem.RentalStartDateUtc, shoppingCartItem.RentalEndDateUtc, includeDiscounts, out discountAmount, out appliedDiscounts); + shoppingCartItem.AttributesXml = attributesXml; + return unitPrice; } /// /// Gets the shopping cart unit price (one item) @@ -498,10 +505,10 @@ public virtual decimal GetUnitPrice(ShoppingCartItem shoppingCartItem, /// Applied discounts /// Shopping cart unit price (one item) public virtual decimal GetUnitPrice(Product product, - Customer customer, + Customer customer, ShoppingCartType shoppingCartType, int quantity, - string attributesXml, + ref string attributesXml, decimal customerEnteredPrice, DateTime? rentalStartDate, DateTime? rentalEndDate, bool includeDiscounts, @@ -517,7 +524,9 @@ public virtual decimal GetUnitPrice(Product product, discountAmount = decimal.Zero; appliedDiscounts = new List(); - decimal finalPrice; + var taxWeightSummary = new TaxSummary(_workContext.TaxDisplayType == TaxDisplayType.IncludingTax); + + decimal finalPrice = decimal.Zero; var combination = _productAttributeParser.FindProductAttributeCombination(product, attributesXml); if (combination != null && combination.OverriddenPrice.HasValue) @@ -532,19 +541,35 @@ public virtual decimal GetUnitPrice(Product product, product.IsRental ? rentalEndDate : null, out discountAmount, out appliedDiscounts); } - else + + //summarize price of all attributes and build up taxSummary + //needed for VAT calculation of product bundles + decimal attributesTotalPrice = decimal.Zero; + var attributeValues = _productAttributeParser.ParseProductAttributeValues(attributesXml); + if (attributeValues != null) { - //summarize price of all attributes - decimal attributesTotalPrice = decimal.Zero; - var attributeValues = _productAttributeParser.ParseProductAttributeValues(attributesXml); - if (attributeValues != null) + decimal taxRate; + foreach (var attributeValue in attributeValues) { - foreach (var attributeValue in attributeValues) + var attributePrice = GetProductAttributeValuePriceAdjustment(attributeValue); + attributesTotalPrice += attributePrice; + + if (attributeValue.AttributeValueType == AttributeValueType.AssociatedToProduct) { - attributesTotalPrice += GetProductAttributeValuePriceAdjustment(attributeValue); + //bundled product + var associatedProduct = _productService.GetProductById(attributeValue.AssociatedProductId); + if (associatedProduct != null) + { + //build taxSummary for tax subdivision + var attributePriceTax = _taxService.GetProductPrice(associatedProduct, attributePrice, customer, out taxRate); + taxWeightSummary.AddRate(taxRate, attributePrice); + } } } + } + if (combination == null || !combination.OverriddenPrice.HasValue) + { //get price of a product (with previously calculated price of all attributes) if (product.CustomerEntersPrice) { @@ -580,11 +605,29 @@ public virtual decimal GetUnitPrice(Product product, out discountAmount, out appliedDiscounts); } } - + //rounding if (_shoppingCartSettings.RoundPricesDuringCalculation) finalPrice = RoundingHelper.RoundPrice(finalPrice); + //add tax attributes + if (taxWeightSummary != null && taxWeightSummary.TaxRates.Any()) + { + decimal taxRate; + var itemAmount = _taxService.GetProductPrice(product, 0, customer, out taxRate); + var baseProductPrice = finalPrice - attributesTotalPrice; + if (baseProductPrice > decimal.Zero) + taxWeightSummary.AddRate(taxRate, baseProductPrice); + + if (taxWeightSummary.TaxRates.Count() > 1 || + (taxWeightSummary.TaxRates.FirstOrDefault().Key != taxRate && baseProductPrice != finalPrice) //use associated product taxRate when different + ) + { + taxWeightSummary.CalculateAmounts(); + attributesXml = _productAttributeParser.AddTaxAttribute(attributesXml, taxWeightSummary); + } + } + return finalPrice; } /// diff --git a/src/Libraries/Nop.Services/Catalog/ProductAttributeFormatter.cs b/src/Libraries/Nop.Services/Catalog/ProductAttributeFormatter.cs index 1b06a5c15db..7ec66ba7c1c 100644 --- a/src/Libraries/Nop.Services/Catalog/ProductAttributeFormatter.cs +++ b/src/Libraries/Nop.Services/Catalog/ProductAttributeFormatter.cs @@ -1,243 +1,276 @@ -using System; -using System.Text; -using System.Web; -using Nop.Core; -using Nop.Core.Domain.Catalog; -using Nop.Core.Domain.Customers; -using Nop.Core.Domain.Orders; -using Nop.Core.Html; -using Nop.Services.Directory; -using Nop.Services.Localization; -using Nop.Services.Media; -using Nop.Services.Tax; - -namespace Nop.Services.Catalog -{ - /// - /// Product attribute formatter - /// - public partial class ProductAttributeFormatter : IProductAttributeFormatter - { - private readonly IWorkContext _workContext; - private readonly IProductAttributeService _productAttributeService; - private readonly IProductAttributeParser _productAttributeParser; - private readonly ICurrencyService _currencyService; - private readonly ILocalizationService _localizationService; - private readonly ITaxService _taxService; - private readonly IPriceFormatter _priceFormatter; - private readonly IDownloadService _downloadService; - private readonly IWebHelper _webHelper; - private readonly IPriceCalculationService _priceCalculationService; - private readonly ShoppingCartSettings _shoppingCartSettings; - - public ProductAttributeFormatter(IWorkContext workContext, - IProductAttributeService productAttributeService, - IProductAttributeParser productAttributeParser, - ICurrencyService currencyService, - ILocalizationService localizationService, - ITaxService taxService, - IPriceFormatter priceFormatter, - IDownloadService downloadService, - IWebHelper webHelper, - IPriceCalculationService priceCalculationService, - ShoppingCartSettings shoppingCartSettings) - { - this._workContext = workContext; - this._productAttributeService = productAttributeService; - this._productAttributeParser = productAttributeParser; - this._currencyService = currencyService; - this._localizationService = localizationService; - this._taxService = taxService; - this._priceFormatter = priceFormatter; - this._downloadService = downloadService; - this._webHelper = webHelper; - this._priceCalculationService = priceCalculationService; - this._shoppingCartSettings = shoppingCartSettings; - } - - /// - /// Formats attributes - /// - /// Product - /// Attributes in XML format - /// Attributes - public virtual string FormatAttributes(Product product, string attributesXml) - { - var customer = _workContext.CurrentCustomer; - return FormatAttributes(product, attributesXml, customer); - } - - /// - /// Formats attributes - /// - /// Product - /// Attributes in XML format - /// Customer - /// Serapator - /// A value indicating whether to encode (HTML) values - /// A value indicating whether to render prices - /// A value indicating whether to render product attributes - /// A value indicating whether to render gift card attributes - /// A value indicating whether to HTML hyperink tags could be rendered (if required) - /// Attributes - public virtual string FormatAttributes(Product product, string attributesXml, - Customer customer, string serapator = "
    ", bool htmlEncode = true, bool renderPrices = true, - bool renderProductAttributes = true, bool renderGiftCardAttributes = true, - bool allowHyperlinks = true) - { - var result = new StringBuilder(); - - //attributes - if (renderProductAttributes) - { - foreach (var attribute in _productAttributeParser.ParseProductAttributeMappings(attributesXml)) - { - //attributes without values - if (!attribute.ShouldHaveValues()) - { - foreach (var value in _productAttributeParser.ParseValues(attributesXml, attribute.Id)) - { - var formattedAttribute = string.Empty; - if (attribute.AttributeControlType == AttributeControlType.MultilineTextbox) - { - //multiline textbox - var attributeName = attribute.ProductAttribute.GetLocalized(a => a.Name, _workContext.WorkingLanguage.Id); - - //encode (if required) - if (htmlEncode) - attributeName = HttpUtility.HtmlEncode(attributeName); - - //we never encode multiline textbox input - formattedAttribute = string.Format("{0}: {1}", attributeName, HtmlHelper.FormatText(value, false, true, false, false, false, false)); - } - else if (attribute.AttributeControlType == AttributeControlType.FileUpload) - { - //file upload - Guid downloadGuid; - Guid.TryParse(value, out downloadGuid); - var download = _downloadService.GetDownloadByGuid(downloadGuid); - if (download != null) - { - var fileName = string.Format("{0}{1}", download.Filename ?? download.DownloadGuid.ToString(), download.Extension); - - //encode (if required) - if (htmlEncode) - fileName = HttpUtility.HtmlEncode(fileName); - - //TODO add a method for getting URL (use routing because it handles all SEO friendly URLs) - var attributeText = allowHyperlinks ? string.Format("{2}", - _webHelper.GetStoreLocation(false), download.DownloadGuid, fileName) : fileName; - - var attributeName = attribute.ProductAttribute.GetLocalized(a => a.Name, _workContext.WorkingLanguage.Id); - - //encode (if required) - if (htmlEncode) - attributeName = HttpUtility.HtmlEncode(attributeName); - - formattedAttribute = string.Format("{0}: {1}", attributeName, attributeText); - } - } - else - { - //other attributes (textbox, datepicker) - formattedAttribute = string.Format("{0}: {1}", attribute.ProductAttribute.GetLocalized(a => a.Name, _workContext.WorkingLanguage.Id), value); - - //encode (if required) - if (htmlEncode) - formattedAttribute = HttpUtility.HtmlEncode(formattedAttribute); - } - - if (!string.IsNullOrEmpty(formattedAttribute)) - { - if (result.Length > 0) - result.Append(serapator); - result.Append(formattedAttribute); - } - } - } - //product attribute values - else - { - foreach (var attributeValue in _productAttributeParser.ParseProductAttributeValues(attributesXml, attribute.Id)) - { - var formattedAttribute = string.Format("{0}: {1}", - attribute.ProductAttribute.GetLocalized(a => a.Name, _workContext.WorkingLanguage.Id), - attributeValue.GetLocalized(a => a.Name, _workContext.WorkingLanguage.Id)); - - if (renderPrices) - { - decimal taxRate; - var attributeValuePriceAdjustment = _priceCalculationService.GetProductAttributeValuePriceAdjustment(attributeValue); - var priceAdjustmentBase = _taxService.GetProductPrice(product, attributeValuePriceAdjustment, customer, out taxRate); - var priceAdjustment = _currencyService.ConvertFromPrimaryStoreCurrency(priceAdjustmentBase, _workContext.WorkingCurrency); - if (priceAdjustmentBase > 0) - formattedAttribute += string.Format(" [+{0}]", _priceFormatter.FormatPrice(priceAdjustment, false, false)); - else if (priceAdjustmentBase < decimal.Zero) - formattedAttribute += string.Format(" [-{0}]", _priceFormatter.FormatPrice(-priceAdjustment, false, false)); - } - - //display quantity - if (_shoppingCartSettings.RenderAssociatedAttributeValueQuantity && attributeValue.AttributeValueType == AttributeValueType.AssociatedToProduct) - { - //render only when more than 1 - if (attributeValue.Quantity > 1) - formattedAttribute += string.Format(_localizationService.GetResource("ProductAttributes.Quantity"), attributeValue.Quantity); - } - - //encode (if required) - if (htmlEncode) - formattedAttribute = HttpUtility.HtmlEncode(formattedAttribute); - - if (!string.IsNullOrEmpty(formattedAttribute)) - { - if (result.Length > 0) - result.Append(serapator); - result.Append(formattedAttribute); - } - } - } - } - } - - //gift cards - if (renderGiftCardAttributes) - { - if (product.IsGiftCard) - { - string giftCardRecipientName; - string giftCardRecipientEmail; - string giftCardSenderName; - string giftCardSenderEmail; - string giftCardMessage; - _productAttributeParser.GetGiftCardAttribute(attributesXml, out giftCardRecipientName, out giftCardRecipientEmail, - out giftCardSenderName, out giftCardSenderEmail, out giftCardMessage); - - //sender - var giftCardFrom = product.GiftCardType == GiftCardType.Virtual ? - string.Format(_localizationService.GetResource("GiftCardAttribute.From.Virtual"), giftCardSenderName, giftCardSenderEmail) : - string.Format(_localizationService.GetResource("GiftCardAttribute.From.Physical"), giftCardSenderName); - //recipient - var giftCardFor = product.GiftCardType == GiftCardType.Virtual ? - string.Format(_localizationService.GetResource("GiftCardAttribute.For.Virtual"), giftCardRecipientName, giftCardRecipientEmail) : - string.Format(_localizationService.GetResource("GiftCardAttribute.For.Physical"), giftCardRecipientName); - - //encode (if required) - if (htmlEncode) - { - giftCardFrom = HttpUtility.HtmlEncode(giftCardFrom); - giftCardFor = HttpUtility.HtmlEncode(giftCardFor); - } - - if (!String.IsNullOrEmpty(result.ToString())) - { - result.Append(serapator); - } - result.Append(giftCardFrom); - result.Append(serapator); - result.Append(giftCardFor); - } - } - return result.ToString(); - } - } -} +using System; +using System.Text; +using System.Web; +using Nop.Core; +using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Customers; +using Nop.Core.Domain.Orders; +using Nop.Core.Html; +using Nop.Services.Directory; +using Nop.Services.Localization; +using Nop.Services.Media; +using Nop.Services.Tax; +using System.Collections.Generic; +using Nop.Core.Domain.Tax; + +namespace Nop.Services.Catalog +{ + /// + /// Product attribute formatter + /// + public partial class ProductAttributeFormatter : IProductAttributeFormatter + { + private readonly IWorkContext _workContext; + private readonly IProductAttributeService _productAttributeService; + private readonly IProductAttributeParser _productAttributeParser; + private readonly ICurrencyService _currencyService; + private readonly ILocalizationService _localizationService; + private readonly ITaxService _taxService; + private readonly IPriceFormatter _priceFormatter; + private readonly IDownloadService _downloadService; + private readonly IWebHelper _webHelper; + private readonly IPriceCalculationService _priceCalculationService; + private readonly ShoppingCartSettings _shoppingCartSettings; + + public ProductAttributeFormatter(IWorkContext workContext, + IProductAttributeService productAttributeService, + IProductAttributeParser productAttributeParser, + ICurrencyService currencyService, + ILocalizationService localizationService, + ITaxService taxService, + IPriceFormatter priceFormatter, + IDownloadService downloadService, + IWebHelper webHelper, + IPriceCalculationService priceCalculationService, + ShoppingCartSettings shoppingCartSettings) + { + this._workContext = workContext; + this._productAttributeService = productAttributeService; + this._productAttributeParser = productAttributeParser; + this._currencyService = currencyService; + this._localizationService = localizationService; + this._taxService = taxService; + this._priceFormatter = priceFormatter; + this._downloadService = downloadService; + this._webHelper = webHelper; + this._priceCalculationService = priceCalculationService; + this._shoppingCartSettings = shoppingCartSettings; + } + + /// + /// Formats attributes + /// + /// Product + /// Attributes in XML format + /// Attributes + public virtual string FormatAttributes(Product product, string attributesXml) + { + var customer = _workContext.CurrentCustomer; + return FormatAttributes(product, attributesXml, customer); + } + + /// + /// Formats attributes + /// + /// Product + /// Attributes in XML format + /// Customer + /// Serapator + /// A value indicating whether to encode (HTML) values + /// A value indicating whether to render prices + /// A value indicating whether to render product attributes + /// A value indicating whether to render gift card attributes + /// A value indicating whether to HTML hyperink tags could be rendered (if required) + /// /// A value indicating if attribute VAT should be rendered with price. Decimal.MinusOne is default, i.e. don't render + /// Attributes + public virtual string FormatAttributes(Product product, string attributesXml, + Customer customer, string serapator = "
    ", bool htmlEncode = true, bool renderPrices = true, + bool renderProductAttributes = true, bool renderGiftCardAttributes = true, + bool allowHyperlinks = true, + decimal subTotal = decimal.MinusOne) + { + var result = new StringBuilder(); + + //attributes + if (renderProductAttributes) + { + foreach (var attribute in _productAttributeParser.ParseProductAttributeMappings(attributesXml)) + { + //attributes without values + if (!attribute.ShouldHaveValues()) + { + foreach (var value in _productAttributeParser.ParseValues(attributesXml, attribute.Id)) + { + var formattedAttribute = string.Empty; + if (attribute.AttributeControlType == AttributeControlType.MultilineTextbox) + { + //multiline textbox + var attributeName = attribute.ProductAttribute.GetLocalized(a => a.Name, _workContext.WorkingLanguage.Id); + + //encode (if required) + if (htmlEncode) + attributeName = HttpUtility.HtmlEncode(attributeName); + + //we never encode multiline textbox input + formattedAttribute = string.Format("{0}: {1}", attributeName, HtmlHelper.FormatText(value, false, true, false, false, false, false)); + } + else if (attribute.AttributeControlType == AttributeControlType.FileUpload) + { + //file upload + Guid downloadGuid; + Guid.TryParse(value, out downloadGuid); + var download = _downloadService.GetDownloadByGuid(downloadGuid); + if (download != null) + { + var fileName = string.Format("{0}{1}", download.Filename ?? download.DownloadGuid.ToString(), download.Extension); + + //encode (if required) + if (htmlEncode) + fileName = HttpUtility.HtmlEncode(fileName); + + //TODO add a method for getting URL (use routing because it handles all SEO friendly URLs) + var attributeText = allowHyperlinks ? string.Format("{2}", + _webHelper.GetStoreLocation(false), download.DownloadGuid, fileName) : fileName; + + var attributeName = attribute.ProductAttribute.GetLocalized(a => a.Name, _workContext.WorkingLanguage.Id); + + //encode (if required) + if (htmlEncode) + attributeName = HttpUtility.HtmlEncode(attributeName); + + formattedAttribute = string.Format("{0}: {1}", attributeName, attributeText); + } + } + else + { + //other attributes (textbox, datepicker) + formattedAttribute = string.Format("{0}: {1}", attribute.ProductAttribute.GetLocalized(a => a.Name, _workContext.WorkingLanguage.Id), value); + + //encode (if required) + if (htmlEncode) + formattedAttribute = HttpUtility.HtmlEncode(formattedAttribute); + } + + if (!string.IsNullOrEmpty(formattedAttribute)) + { + if (result.Length > 0) + result.Append(serapator); + result.Append(formattedAttribute); + } + } + } + //product attribute values + else + { + foreach (var attributeValue in _productAttributeParser.ParseProductAttributeValues(attributesXml, attribute.Id)) + { + var formattedAttribute = string.Format("{0}: {1}", + attribute.ProductAttribute.GetLocalized(a => a.Name, _workContext.WorkingLanguage.Id), + attributeValue.GetLocalized(a => a.Name, _workContext.WorkingLanguage.Id)); + + if (renderPrices) + { + decimal taxRate; + var attributeValuePriceAdjustment = _priceCalculationService.GetProductAttributeValuePriceAdjustment(attributeValue); + var priceAdjustmentBase = _taxService.GetProductPrice(product, attributeValuePriceAdjustment, customer, out taxRate); + var priceAdjustment = _currencyService.ConvertFromPrimaryStoreCurrency(priceAdjustmentBase, _workContext.WorkingCurrency); + if (priceAdjustmentBase > 0) + formattedAttribute += string.Format(" [+{0}]", _priceFormatter.FormatPrice(priceAdjustment, false, false)); + else if (priceAdjustmentBase < decimal.Zero) + formattedAttribute += string.Format(" [-{0}]", _priceFormatter.FormatPrice(-priceAdjustment, false, false)); + } + + //display quantity + if (_shoppingCartSettings.RenderAssociatedAttributeValueQuantity && attributeValue.AttributeValueType == AttributeValueType.AssociatedToProduct) + { + //render only when more than 1 + if (attributeValue.Quantity > 1) + formattedAttribute += string.Format(_localizationService.GetResource("ProductAttributes.Quantity"), attributeValue.Quantity); + } + + //encode (if required) + if (htmlEncode) + formattedAttribute = HttpUtility.HtmlEncode(formattedAttribute); + + if (!string.IsNullOrEmpty(formattedAttribute)) + { + if (result.Length > 0) + result.Append(serapator); + result.Append(formattedAttribute); + } + } + } + } + } + + //gift cards + if (renderGiftCardAttributes) + { + if (product.IsGiftCard) + { + string giftCardRecipientName; + string giftCardRecipientEmail; + string giftCardSenderName; + string giftCardSenderEmail; + string giftCardMessage; + _productAttributeParser.GetGiftCardAttribute(attributesXml, out giftCardRecipientName, out giftCardRecipientEmail, + out giftCardSenderName, out giftCardSenderEmail, out giftCardMessage); + + //sender + var giftCardFrom = product.GiftCardType == GiftCardType.Virtual ? + string.Format(_localizationService.GetResource("GiftCardAttribute.From.Virtual"), giftCardSenderName, giftCardSenderEmail) : + string.Format(_localizationService.GetResource("GiftCardAttribute.From.Physical"), giftCardSenderName); + //recipient + var giftCardFor = product.GiftCardType == GiftCardType.Virtual ? + string.Format(_localizationService.GetResource("GiftCardAttribute.For.Virtual"), giftCardRecipientName, giftCardRecipientEmail) : + string.Format(_localizationService.GetResource("GiftCardAttribute.For.Physical"), giftCardRecipientName); + + //encode (if required) + if (htmlEncode) + { + giftCardFrom = HttpUtility.HtmlEncode(giftCardFrom); + giftCardFor = HttpUtility.HtmlEncode(giftCardFor); + } + + if (!String.IsNullOrEmpty(result.ToString())) + { + result.Append(serapator); + } + result.Append(giftCardFrom); + result.Append(serapator); + result.Append(giftCardFor); + } + } + + //attribute tax + if (subTotal != decimal.MinusOne) + { + var taxAttributes = _productAttributeParser.ParseTaxAttribute(attributesXml); + var attribTaxSummary = new TaxSummary(_workContext.TaxDisplayType == TaxDisplayType.IncludingTax); + var attribTax = attribTaxSummary.ParseAttributeRate(subTotal, taxAttributes); + string formattedAttribute = ""; + if (result.Length > 0) + result.Append(serapator); + result.Append(String.Format("{0} {1}
    ", + _localizationService.GetResource("ShoppingCart.VatRate"), + _localizationService.GetResource("Shoppingcart.Totals.OrderAmount"))); + + foreach (KeyValuePair kvp in attribTax) + { + decimal vatpercentage = kvp.Key; + decimal rateAmount = kvp.Value; + decimal taxRate; + var priceAdjustmentBase = _taxService.GetProductPrice(product, rateAmount, customer, out taxRate); + var priceAdjustment = _currencyService.ConvertFromPrimaryStoreCurrency(priceAdjustmentBase, _workContext.WorkingCurrency); + var price = _priceFormatter.FormatPrice(priceAdjustment, false, false); + price = htmlEncode ? HttpUtility.HtmlEncode(price) : price; + formattedAttribute = String.Format("{0} {1}
    ", kvp.Key.ToString(), price); + result.Append(formattedAttribute); + } + + + } + return result.ToString(); + } + } +} diff --git a/src/Libraries/Nop.Services/Catalog/ProductAttributeParser.cs b/src/Libraries/Nop.Services/Catalog/ProductAttributeParser.cs index df203866037..919e09b352f 100644 --- a/src/Libraries/Nop.Services/Catalog/ProductAttributeParser.cs +++ b/src/Libraries/Nop.Services/Catalog/ProductAttributeParser.cs @@ -1,783 +1,879 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Xml; -using Nop.Core.Domain.Catalog; -using Nop.Data; - -namespace Nop.Services.Catalog -{ - /// - /// Product attribute parser - /// - public partial class ProductAttributeParser : IProductAttributeParser - { - #region Fields - - private readonly IDbContext _context; - private readonly IProductAttributeService _productAttributeService; - - #endregion - - #region Ctor - - public ProductAttributeParser(IDbContext context, - IProductAttributeService productAttributeService) - { - this._context = context; - this._productAttributeService = productAttributeService; - } - - #endregion - - #region Product attributes - - /// - /// Gets selected product attribute mapping identifiers - /// - /// Attributes in XML format - /// Selected product attribute mapping identifiers - protected virtual IList ParseProductAttributeMappingIds(string attributesXml) - { - var ids = new List(); - if (String.IsNullOrEmpty(attributesXml)) - return ids; - - try - { - var xmlDoc = new XmlDocument(); - xmlDoc.LoadXml(attributesXml); - - var nodeList1 = xmlDoc.SelectNodes(@"//Attributes/ProductAttribute"); - foreach (XmlNode node1 in nodeList1) - { - if (node1.Attributes != null && node1.Attributes["ID"] != null) - { - string str1 = node1.Attributes["ID"].InnerText.Trim(); - int id; - if (int.TryParse(str1, out id)) - { - ids.Add(id); - } - } - } - } - catch (Exception exc) - { - Debug.Write(exc.ToString()); - } - return ids; - } - - /// - /// Gets selected product attribute values with the quantity entered by the customer - /// - /// Attributes in XML format - /// Product attribute mapping identifier - /// Collections of pairs of product attribute values and their quantity - protected IList> ParseValuesWithQuantity(string attributesXml, int productAttributeMappingId) - { - var selectedValues = new List>(); - if (string.IsNullOrEmpty(attributesXml)) - return selectedValues; - - try - { - var xmlDoc = new XmlDocument(); - xmlDoc.LoadXml(attributesXml); - - foreach (XmlNode attributeNode in xmlDoc.SelectNodes(@"//Attributes/ProductAttribute")) - { - if (attributeNode.Attributes != null && attributeNode.Attributes["ID"] != null) - { - int attributeId; - if (int.TryParse(attributeNode.Attributes["ID"].InnerText.Trim(), out attributeId) && attributeId == productAttributeMappingId) - { - foreach (XmlNode attributeValue in attributeNode.SelectNodes("ProductAttributeValue")) - { - var value = attributeValue.SelectSingleNode("Value").InnerText.Trim(); - var quantityNode = attributeValue.SelectSingleNode("Quantity"); - selectedValues.Add(new Tuple(value, quantityNode != null ? quantityNode.InnerText.Trim() : string.Empty)); - } - } - } - } - } - catch { } - - return selectedValues; - } - - /// - /// Gets selected product attribute mappings - /// - /// Attributes in XML format - /// Selected product attribute mappings - public virtual IList ParseProductAttributeMappings(string attributesXml) - { - var result = new List(); - if (String.IsNullOrEmpty(attributesXml)) - return result; - - var ids = ParseProductAttributeMappingIds(attributesXml); - foreach (int id in ids) - { - var attribute = _productAttributeService.GetProductAttributeMappingById(id); - if (attribute != null) - { - result.Add(attribute); - } - } - return result; - } - - /// - /// Get product attribute values - /// - /// Attributes in XML format - /// Product attribute mapping identifier; pass 0 to load all values - /// Product attribute values - public virtual IList ParseProductAttributeValues(string attributesXml, int productAttributeMappingId = 0) - { - var values = new List(); - if (string.IsNullOrEmpty(attributesXml)) - return values; - - var attributes = ParseProductAttributeMappings(attributesXml); - - //to load values only for the passed product attribute mapping - if (productAttributeMappingId > 0) - attributes = attributes.Where(attribute => attribute.Id == productAttributeMappingId).ToList(); - - foreach (var attribute in attributes) - { - if (!attribute.ShouldHaveValues()) - continue; - - foreach (var attributeValue in ParseValuesWithQuantity(attributesXml, attribute.Id)) - { - int attributeValueId; - if (!string.IsNullOrEmpty(attributeValue.Item1) && int.TryParse(attributeValue.Item1, out attributeValueId)) - { - var value = _productAttributeService.GetProductAttributeValueById(attributeValueId); - if (value != null) - { - int quantity; - if (!string.IsNullOrEmpty(attributeValue.Item2) && int.TryParse(attributeValue.Item2, out quantity) && quantity != value.Quantity) - { - //if customer enters quantity, use new entity with new quantity - var oldValue = _context.LoadOriginalCopy(value); - oldValue.ProductAttributeMapping = attribute; - oldValue.Quantity = quantity; - values.Add(oldValue); - } - else - values.Add(value); - } - } - } - } - return values; - } - - /// - /// Gets selected product attribute values - /// - /// Attributes in XML format - /// Product attribute mapping identifier - /// Product attribute values - public virtual IList ParseValues(string attributesXml, int productAttributeMappingId) - { - var selectedValues = new List(); - if (String.IsNullOrEmpty(attributesXml)) - return selectedValues; - - try - { - var xmlDoc = new XmlDocument(); - xmlDoc.LoadXml(attributesXml); - - var nodeList1 = xmlDoc.SelectNodes(@"//Attributes/ProductAttribute"); - foreach (XmlNode node1 in nodeList1) - { - if (node1.Attributes != null && node1.Attributes["ID"] != null) - { - string str1 =node1.Attributes["ID"].InnerText.Trim(); - int id; - if (int.TryParse(str1, out id)) - { - if (id == productAttributeMappingId) - { - var nodeList2 = node1.SelectNodes(@"ProductAttributeValue/Value"); - foreach (XmlNode node2 in nodeList2) - { - string value = node2.InnerText.Trim(); - selectedValues.Add(value); - } - } - } - } - } - } - catch (Exception exc) - { - Debug.Write(exc.ToString()); - } - return selectedValues; - } - - /// - /// Adds an attribute - /// - /// Attributes in XML format - /// Product attribute mapping - /// Value - /// Quantity (used with AttributeValueType.AssociatedToProduct to specify the quantity entered by the customer) - /// Updated result (XML format) - public virtual string AddProductAttribute(string attributesXml, ProductAttributeMapping productAttributeMapping, string value, int? quantity = null) - { - string result = string.Empty; - try - { - var xmlDoc = new XmlDocument(); - if (String.IsNullOrEmpty(attributesXml)) - { - var element1 = xmlDoc.CreateElement("Attributes"); - xmlDoc.AppendChild(element1); - } - else - { - xmlDoc.LoadXml(attributesXml); - } - var rootElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes"); - - XmlElement attributeElement = null; - //find existing - var nodeList1 = xmlDoc.SelectNodes(@"//Attributes/ProductAttribute"); - foreach (XmlNode node1 in nodeList1) - { - if (node1.Attributes != null && node1.Attributes["ID"] != null) - { - string str1 =node1.Attributes["ID"].InnerText.Trim(); - int id; - if (int.TryParse(str1, out id)) - { - if (id == productAttributeMapping.Id) - { - attributeElement = (XmlElement)node1; - break; - } - } - } - } - - //create new one if not found - if (attributeElement == null) - { - attributeElement = xmlDoc.CreateElement("ProductAttribute"); - attributeElement.SetAttribute("ID", productAttributeMapping.Id.ToString()); - rootElement.AppendChild(attributeElement); - } - var attributeValueElement = xmlDoc.CreateElement("ProductAttributeValue"); - attributeElement.AppendChild(attributeValueElement); - - var attributeValueValueElement = xmlDoc.CreateElement("Value"); - attributeValueValueElement.InnerText = value; - attributeValueElement.AppendChild(attributeValueValueElement); - - //the quantity entered by the customer - if (quantity.HasValue) - { - var attributeValueQuantity = xmlDoc.CreateElement("Quantity"); - attributeValueQuantity.InnerText = quantity.ToString(); - attributeValueElement.AppendChild(attributeValueQuantity); - } - - result = xmlDoc.OuterXml; - } - catch (Exception exc) - { - Debug.Write(exc.ToString()); - } - return result; - } - - /// - /// Remove an attribute - /// - /// Attributes in XML format - /// Product attribute mapping - /// Updated result (XML format) - public virtual string RemoveProductAttribute(string attributesXml, ProductAttributeMapping productAttributeMapping) - { - string result = string.Empty; - try - { - var xmlDoc = new XmlDocument(); - if (String.IsNullOrEmpty(attributesXml)) - { - var element1 = xmlDoc.CreateElement("Attributes"); - xmlDoc.AppendChild(element1); - } - else - { - xmlDoc.LoadXml(attributesXml); - } - var rootElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes"); - - XmlElement attributeElement = null; - //find existing - var nodeList1 = xmlDoc.SelectNodes(@"//Attributes/ProductAttribute"); - foreach (XmlNode node1 in nodeList1) - { - if (node1.Attributes != null && node1.Attributes["ID"] != null) - { - string str1 = node1.Attributes["ID"].InnerText.Trim(); - int id; - if (int.TryParse(str1, out id)) - { - if (id == productAttributeMapping.Id) - { - attributeElement = (XmlElement)node1; - break; - } - } - } - } - - //found - if (attributeElement != null) - { - rootElement.RemoveChild(attributeElement); - } - - result = xmlDoc.OuterXml; - } - catch (Exception exc) - { - Debug.Write(exc.ToString()); - } - return result; - } - - /// - /// Are attributes equal - /// - /// The attributes of the first product - /// The attributes of the second product - /// A value indicating whether we should ignore non-combinable attributes - /// A value indicating whether we should ignore the quantity of attribute value entered by the customer - /// Result - public virtual bool AreProductAttributesEqual(string attributesXml1, string attributesXml2, bool ignoreNonCombinableAttributes, bool ignoreQuantity = true) - { - var attributes1 = ParseProductAttributeMappings(attributesXml1); - if (ignoreNonCombinableAttributes) - { - attributes1 = attributes1.Where(x => !x.IsNonCombinable()).ToList(); - } - var attributes2 = ParseProductAttributeMappings(attributesXml2); - if (ignoreNonCombinableAttributes) - { - attributes2 = attributes2.Where(x => !x.IsNonCombinable()).ToList(); - } - if (attributes1.Count != attributes2.Count) - return false; - - bool attributesEqual = true; - foreach (var a1 in attributes1) - { - bool hasAttribute = false; - foreach (var a2 in attributes2) - { - if (a1.Id == a2.Id) - { - hasAttribute = true; - var values1Str = ParseValuesWithQuantity(attributesXml1, a1.Id); - var values2Str = ParseValuesWithQuantity(attributesXml2, a2.Id); - if (values1Str.Count == values2Str.Count) - { - foreach (var str1 in values1Str) - { - bool hasValue = false; - foreach (var str2 in values2Str) - { - //case insensitive? - //if (str1.Trim().ToLower() == str2.Trim().ToLower()) - if (str1.Item1.Trim() == str2.Item1.Trim()) - { - hasValue = ignoreQuantity ? true : str1.Item2.Trim() == str2.Item2.Trim(); - break; - } - } - - if (!hasValue) - { - attributesEqual = false; - break; - } - } - } - else - { - attributesEqual = false; - break; - } - } - } - - if (hasAttribute == false) - { - attributesEqual = false; - break; - } - } - - return attributesEqual; - } - - /// - /// Check whether condition of some attribute is met (if specified). Return "null" if not condition is specified - /// - /// Product attribute - /// Selected attributes (XML format) - /// Result - public virtual bool? IsConditionMet(ProductAttributeMapping pam, string selectedAttributesXml) - { - if (pam == null) - throw new ArgumentNullException("pam"); - - var conditionAttributeXml = pam.ConditionAttributeXml; - if (String.IsNullOrEmpty(conditionAttributeXml)) - //no condition - return null; - - //load an attribute this one depends on - var dependOnAttribute = ParseProductAttributeMappings(conditionAttributeXml).FirstOrDefault(); - if (dependOnAttribute == null) - return true; - - var valuesThatShouldBeSelected = ParseValues(conditionAttributeXml, dependOnAttribute.Id) - //a workaround here: - //ConditionAttributeXml can contain "empty" values (nothing is selected) - //but in other cases (like below) we do not store empty values - //that's why we remove empty values here - .Where(x => !String.IsNullOrEmpty(x)) - .ToList(); - var selectedValues = ParseValues(selectedAttributesXml, dependOnAttribute.Id); - if (valuesThatShouldBeSelected.Count != selectedValues.Count) - return false; - - //compare values - var allFound = true; - foreach (var t1 in valuesThatShouldBeSelected) - { - bool found = false; - foreach (var t2 in selectedValues) - if (t1 == t2) - found = true; - if (!found) - allFound = false; - } - - return allFound; - } - - /// - /// Finds a product attribute combination by attributes stored in XML - /// - /// Product - /// Attributes in XML format - /// A value indicating whether we should ignore non-combinable attributes - /// Found product attribute combination - public virtual ProductAttributeCombination FindProductAttributeCombination(Product product, - string attributesXml, bool ignoreNonCombinableAttributes = true) - { - if (product == null) - throw new ArgumentNullException("product"); - - var combinations = _productAttributeService.GetAllProductAttributeCombinations(product.Id); - return combinations.FirstOrDefault(x => - AreProductAttributesEqual(x.AttributesXml, attributesXml, ignoreNonCombinableAttributes)); - } - - /// - /// Generate all combinations - /// - /// Product - /// A value indicating whether we should ignore non-combinable attributes - /// Attribute combinations in XML format - public virtual IList GenerateAllCombinations(Product product, bool ignoreNonCombinableAttributes = false) - { - if (product == null) - throw new ArgumentNullException("product"); - - var allProductAttributMappings = _productAttributeService.GetProductAttributeMappingsByProductId(product.Id); - if (ignoreNonCombinableAttributes) - { - allProductAttributMappings = allProductAttributMappings.Where(x => !x.IsNonCombinable()).ToList(); - } - var allPossibleAttributeCombinations = new List>(); - for (int counter = 0; counter < (1 << allProductAttributMappings.Count); ++counter) - { - var combination = new List(); - for (int i = 0; i < allProductAttributMappings.Count; ++i) - { - if ((counter & (1 << i)) == 0) - { - combination.Add(allProductAttributMappings[i]); - } - } - - allPossibleAttributeCombinations.Add(combination); - } - - var allAttributesXml = new List(); - foreach (var combination in allPossibleAttributeCombinations) - { - var attributesXml = new List(); - foreach (var pam in combination) - { - if (!pam.ShouldHaveValues()) - continue; - - var attributeValues = _productAttributeService.GetProductAttributeValues(pam.Id); - if (!attributeValues.Any()) - continue; - - //checkboxes could have several values ticked - var allPossibleCheckboxCombinations = new List>(); - if (pam.AttributeControlType == AttributeControlType.Checkboxes || - pam.AttributeControlType == AttributeControlType.ReadonlyCheckboxes) - { - for (int counter = 0; counter < (1 << attributeValues.Count); ++counter) - { - var checkboxCombination = new List(); - for (int i = 0; i < attributeValues.Count; ++i) - { - if ((counter & (1 << i)) == 0) - { - checkboxCombination.Add(attributeValues[i]); - } - } - - allPossibleCheckboxCombinations.Add(checkboxCombination); - } - } - - if (!attributesXml.Any()) - { - //first set of values - if (pam.AttributeControlType == AttributeControlType.Checkboxes || - pam.AttributeControlType == AttributeControlType.ReadonlyCheckboxes) - { - //checkboxes could have several values ticked - foreach (var checkboxCombination in allPossibleCheckboxCombinations) - { - var tmp1 = ""; - foreach (var checkboxValue in checkboxCombination) - { - tmp1 = AddProductAttribute(tmp1, pam, checkboxValue.Id.ToString()); - } - if (!String.IsNullOrEmpty(tmp1)) - { - attributesXml.Add(tmp1); - } - } - } - else - { - //other attribute types (dropdownlist, radiobutton, color squares) - foreach (var attributeValue in attributeValues) - { - var tmp1 = AddProductAttribute("", pam, attributeValue.Id.ToString()); - attributesXml.Add(tmp1); - } - } - } - else - { - //next values. let's "append" them to already generated attribute combinations in XML format - var attributesXmlTmp = new List(); - if (pam.AttributeControlType == AttributeControlType.Checkboxes || - pam.AttributeControlType == AttributeControlType.ReadonlyCheckboxes) - { - //checkboxes could have several values ticked - foreach (var str1 in attributesXml) - { - foreach (var checkboxCombination in allPossibleCheckboxCombinations) - { - var tmp1 = str1; - foreach (var checkboxValue in checkboxCombination) - { - tmp1 = AddProductAttribute(tmp1, pam, checkboxValue.Id.ToString()); - } - if (!String.IsNullOrEmpty(tmp1)) - { - attributesXmlTmp.Add(tmp1); - } - } - } - } - else - { - //other attribute types (dropdownlist, radiobutton, color squares) - foreach (var attributeValue in attributeValues) - { - foreach (var str1 in attributesXml) - { - var tmp1 = AddProductAttribute(str1, pam, attributeValue.Id.ToString()); - attributesXmlTmp.Add(tmp1); - } - } - } - attributesXml.Clear(); - attributesXml.AddRange(attributesXmlTmp); - } - } - allAttributesXml.AddRange(attributesXml); - } - - //validate conditional attributes (if specified) - //minor workaround: - //once it's done (validation), then we could have some duplicated combinations in result - //we don't remove them here (for performance optimization) because anyway it'll be done in the "GenerateAllAttributeCombinations" method of ProductController - for (int i = 0; i < allAttributesXml.Count; i++) - { - var attributesXml = allAttributesXml[i]; - foreach (var attribute in allProductAttributMappings) - { - var conditionMet = IsConditionMet(attribute, attributesXml); - if (conditionMet.HasValue && !conditionMet.Value) - { - allAttributesXml[i] = RemoveProductAttribute(attributesXml, attribute); - } - } - } - return allAttributesXml; - } - - #endregion - - #region Gift card attributes - - /// - /// Add gift card attrbibutes - /// - /// Attributes in XML format - /// Recipient name - /// Recipient email - /// Sender name - /// Sender email - /// Message - /// Attributes - public string AddGiftCardAttribute(string attributesXml, string recipientName, - string recipientEmail, string senderName, string senderEmail, string giftCardMessage) - { - string result = string.Empty; - try - { - recipientName = recipientName.Trim(); - recipientEmail = recipientEmail.Trim(); - senderName = senderName.Trim(); - senderEmail = senderEmail.Trim(); - - var xmlDoc = new XmlDocument(); - if (String.IsNullOrEmpty(attributesXml)) - { - var element1 = xmlDoc.CreateElement("Attributes"); - xmlDoc.AppendChild(element1); - } - else - { - xmlDoc.LoadXml(attributesXml); - } - - var rootElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes"); - - var giftCardElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes/GiftCardInfo"); - if (giftCardElement == null) - { - giftCardElement = xmlDoc.CreateElement("GiftCardInfo"); - rootElement.AppendChild(giftCardElement); - } - - var recipientNameElement = xmlDoc.CreateElement("RecipientName"); - recipientNameElement.InnerText = recipientName; - giftCardElement.AppendChild(recipientNameElement); - - var recipientEmailElement = xmlDoc.CreateElement("RecipientEmail"); - recipientEmailElement.InnerText = recipientEmail; - giftCardElement.AppendChild(recipientEmailElement); - - var senderNameElement = xmlDoc.CreateElement("SenderName"); - senderNameElement.InnerText = senderName; - giftCardElement.AppendChild(senderNameElement); - - var senderEmailElement = xmlDoc.CreateElement("SenderEmail"); - senderEmailElement.InnerText = senderEmail; - giftCardElement.AppendChild(senderEmailElement); - - var messageElement = xmlDoc.CreateElement("Message"); - messageElement.InnerText = giftCardMessage; - giftCardElement.AppendChild(messageElement); - - result = xmlDoc.OuterXml; - } - catch (Exception exc) - { - Debug.Write(exc.ToString()); - } - return result; - } - - /// - /// Get gift card attrbibutes - /// - /// Attributes - /// Recipient name - /// Recipient email - /// Sender name - /// Sender email - /// Message - public void GetGiftCardAttribute(string attributesXml, out string recipientName, - out string recipientEmail, out string senderName, - out string senderEmail, out string giftCardMessage) - { - recipientName = string.Empty; - recipientEmail = string.Empty; - senderName = string.Empty; - senderEmail = string.Empty; - giftCardMessage = string.Empty; - - try - { - var xmlDoc = new XmlDocument(); - xmlDoc.LoadXml(attributesXml); - - var recipientNameElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes/GiftCardInfo/RecipientName"); - var recipientEmailElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes/GiftCardInfo/RecipientEmail"); - var senderNameElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes/GiftCardInfo/SenderName"); - var senderEmailElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes/GiftCardInfo/SenderEmail"); - var messageElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes/GiftCardInfo/Message"); - - if (recipientNameElement != null) - recipientName = recipientNameElement.InnerText; - if (recipientEmailElement != null) - recipientEmail = recipientEmailElement.InnerText; - if (senderNameElement != null) - senderName = senderNameElement.InnerText; - if (senderEmailElement != null) - senderEmail = senderEmailElement.InnerText; - if (messageElement != null) - giftCardMessage = messageElement.InnerText; - } - catch (Exception exc) - { - Debug.Write(exc.ToString()); - } - } - - #endregion - } -} +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Xml; +using Nop.Core.Domain.Catalog; +using Nop.Data; +using Nop.Services.Tax; +using System.Globalization; + +namespace Nop.Services.Catalog +{ + /// + /// Product attribute parser + /// + public partial class ProductAttributeParser : IProductAttributeParser + { + #region Fields + + private readonly IDbContext _context; + private readonly IProductAttributeService _productAttributeService; + + #endregion + + #region Ctor + + public ProductAttributeParser(IDbContext context, + IProductAttributeService productAttributeService) + { + this._context = context; + this._productAttributeService = productAttributeService; + } + + #endregion + + #region Product attributes + + /// + /// Gets selected product attribute mapping identifiers + /// + /// Attributes in XML format + /// Selected product attribute mapping identifiers + protected virtual IList ParseProductAttributeMappingIds(string attributesXml) + { + var ids = new List(); + if (String.IsNullOrEmpty(attributesXml)) + return ids; + + try + { + var xmlDoc = new XmlDocument(); + xmlDoc.LoadXml(attributesXml); + + var nodeList1 = xmlDoc.SelectNodes(@"//Attributes/ProductAttribute"); + foreach (XmlNode node1 in nodeList1) + { + if (node1.Attributes != null && node1.Attributes["ID"] != null) + { + string str1 = node1.Attributes["ID"].InnerText.Trim(); + int id; + if (int.TryParse(str1, out id)) + { + ids.Add(id); + } + } + } + } + catch (Exception exc) + { + Debug.Write(exc.ToString()); + } + return ids; + } + + /// + /// Gets selected product attribute values with the quantity entered by the customer + /// + /// Attributes in XML format + /// Product attribute mapping identifier + /// Collections of pairs of product attribute values and their quantity + protected IList> ParseValuesWithQuantity(string attributesXml, int productAttributeMappingId) + { + var selectedValues = new List>(); + if (string.IsNullOrEmpty(attributesXml)) + return selectedValues; + + try + { + var xmlDoc = new XmlDocument(); + xmlDoc.LoadXml(attributesXml); + + foreach (XmlNode attributeNode in xmlDoc.SelectNodes(@"//Attributes/ProductAttribute")) + { + if (attributeNode.Attributes != null && attributeNode.Attributes["ID"] != null) + { + int attributeId; + if (int.TryParse(attributeNode.Attributes["ID"].InnerText.Trim(), out attributeId) && attributeId == productAttributeMappingId) + { + foreach (XmlNode attributeValue in attributeNode.SelectNodes("ProductAttributeValue")) + { + var value = attributeValue.SelectSingleNode("Value").InnerText.Trim(); + var quantityNode = attributeValue.SelectSingleNode("Quantity"); + selectedValues.Add(new Tuple(value, quantityNode != null ? quantityNode.InnerText.Trim() : string.Empty)); + } + } + } + } + } + catch { } + + return selectedValues; + } + + /// + /// Gets selected product attribute mappings + /// + /// Attributes in XML format + /// Selected product attribute mappings + public virtual IList ParseProductAttributeMappings(string attributesXml) + { + var result = new List(); + if (String.IsNullOrEmpty(attributesXml)) + return result; + + var ids = ParseProductAttributeMappingIds(attributesXml); + foreach (int id in ids) + { + var attribute = _productAttributeService.GetProductAttributeMappingById(id); + if (attribute != null) + { + result.Add(attribute); + } + } + return result; + } + + /// + /// Get product attribute values + /// + /// Attributes in XML format + /// Product attribute mapping identifier; pass 0 to load all values + /// Product attribute values + public virtual IList ParseProductAttributeValues(string attributesXml, int productAttributeMappingId = 0) + { + var values = new List(); + if (string.IsNullOrEmpty(attributesXml)) + return values; + + var attributes = ParseProductAttributeMappings(attributesXml); + + //to load values only for the passed product attribute mapping + if (productAttributeMappingId > 0) + attributes = attributes.Where(attribute => attribute.Id == productAttributeMappingId).ToList(); + + foreach (var attribute in attributes) + { + if (!attribute.ShouldHaveValues()) + continue; + + foreach (var attributeValue in ParseValuesWithQuantity(attributesXml, attribute.Id)) + { + int attributeValueId; + if (!string.IsNullOrEmpty(attributeValue.Item1) && int.TryParse(attributeValue.Item1, out attributeValueId)) + { + var value = _productAttributeService.GetProductAttributeValueById(attributeValueId); + if (value != null) + { + int quantity; + if (!string.IsNullOrEmpty(attributeValue.Item2) && int.TryParse(attributeValue.Item2, out quantity) && quantity != value.Quantity) + { + //if customer enters quantity, use new entity with new quantity + var oldValue = _context.LoadOriginalCopy(value); + oldValue.ProductAttributeMapping = attribute; + oldValue.Quantity = quantity; + values.Add(oldValue); + } + else + values.Add(value); + } + } + } + } + return values; + } + + /// + /// Gets selected product attribute values + /// + /// Attributes in XML format + /// Product attribute mapping identifier + /// Product attribute values + public virtual IList ParseValues(string attributesXml, int productAttributeMappingId) + { + var selectedValues = new List(); + if (String.IsNullOrEmpty(attributesXml)) + return selectedValues; + + try + { + var xmlDoc = new XmlDocument(); + xmlDoc.LoadXml(attributesXml); + + var nodeList1 = xmlDoc.SelectNodes(@"//Attributes/ProductAttribute"); + foreach (XmlNode node1 in nodeList1) + { + if (node1.Attributes != null && node1.Attributes["ID"] != null) + { + string str1 =node1.Attributes["ID"].InnerText.Trim(); + int id; + if (int.TryParse(str1, out id)) + { + if (id == productAttributeMappingId) + { + var nodeList2 = node1.SelectNodes(@"ProductAttributeValue/Value"); + foreach (XmlNode node2 in nodeList2) + { + string value = node2.InnerText.Trim(); + selectedValues.Add(value); + } + } + } + } + } + } + catch (Exception exc) + { + Debug.Write(exc.ToString()); + } + return selectedValues; + } + + /// + /// Adds an attribute + /// + /// Attributes in XML format + /// Product attribute mapping + /// Value + /// Quantity (used with AttributeValueType.AssociatedToProduct to specify the quantity entered by the customer) + /// Updated result (XML format) + public virtual string AddProductAttribute(string attributesXml, ProductAttributeMapping productAttributeMapping, string value, int? quantity = null) + { + string result = string.Empty; + try + { + var xmlDoc = new XmlDocument(); + if (String.IsNullOrEmpty(attributesXml)) + { + var element1 = xmlDoc.CreateElement("Attributes"); + xmlDoc.AppendChild(element1); + } + else + { + xmlDoc.LoadXml(attributesXml); + } + var rootElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes"); + + XmlElement attributeElement = null; + //find existing + var nodeList1 = xmlDoc.SelectNodes(@"//Attributes/ProductAttribute"); + foreach (XmlNode node1 in nodeList1) + { + if (node1.Attributes != null && node1.Attributes["ID"] != null) + { + string str1 =node1.Attributes["ID"].InnerText.Trim(); + int id; + if (int.TryParse(str1, out id)) + { + if (id == productAttributeMapping.Id) + { + attributeElement = (XmlElement)node1; + break; + } + } + } + } + + //create new one if not found + if (attributeElement == null) + { + attributeElement = xmlDoc.CreateElement("ProductAttribute"); + attributeElement.SetAttribute("ID", productAttributeMapping.Id.ToString()); + rootElement.AppendChild(attributeElement); + } + var attributeValueElement = xmlDoc.CreateElement("ProductAttributeValue"); + attributeElement.AppendChild(attributeValueElement); + + var attributeValueValueElement = xmlDoc.CreateElement("Value"); + attributeValueValueElement.InnerText = value; + attributeValueElement.AppendChild(attributeValueValueElement); + + //the quantity entered by the customer + if (quantity.HasValue) + { + var attributeValueQuantity = xmlDoc.CreateElement("Quantity"); + attributeValueQuantity.InnerText = quantity.ToString(); + attributeValueElement.AppendChild(attributeValueQuantity); + } + + result = xmlDoc.OuterXml; + } + catch (Exception exc) + { + Debug.Write(exc.ToString()); + } + return result; + } + + /// + /// Remove an attribute + /// + /// Attributes in XML format + /// Product attribute mapping + /// Updated result (XML format) + public virtual string RemoveProductAttribute(string attributesXml, ProductAttributeMapping productAttributeMapping) + { + string result = string.Empty; + try + { + var xmlDoc = new XmlDocument(); + if (String.IsNullOrEmpty(attributesXml)) + { + var element1 = xmlDoc.CreateElement("Attributes"); + xmlDoc.AppendChild(element1); + } + else + { + xmlDoc.LoadXml(attributesXml); + } + var rootElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes"); + + XmlElement attributeElement = null; + //find existing + var nodeList1 = xmlDoc.SelectNodes(@"//Attributes/ProductAttribute"); + foreach (XmlNode node1 in nodeList1) + { + if (node1.Attributes != null && node1.Attributes["ID"] != null) + { + string str1 = node1.Attributes["ID"].InnerText.Trim(); + int id; + if (int.TryParse(str1, out id)) + { + if (id == productAttributeMapping.Id) + { + attributeElement = (XmlElement)node1; + break; + } + } + } + } + + //found + if (attributeElement != null) + { + rootElement.RemoveChild(attributeElement); + } + + result = xmlDoc.OuterXml; + } + catch (Exception exc) + { + Debug.Write(exc.ToString()); + } + return result; + } + + /// + /// Are attributes equal + /// + /// The attributes of the first product + /// The attributes of the second product + /// A value indicating whether we should ignore non-combinable attributes + /// A value indicating whether we should ignore the quantity of attribute value entered by the customer + /// Result + public virtual bool AreProductAttributesEqual(string attributesXml1, string attributesXml2, bool ignoreNonCombinableAttributes, bool ignoreQuantity = true) + { + var attributes1 = ParseProductAttributeMappings(attributesXml1); + if (ignoreNonCombinableAttributes) + { + attributes1 = attributes1.Where(x => !x.IsNonCombinable()).ToList(); + } + var attributes2 = ParseProductAttributeMappings(attributesXml2); + if (ignoreNonCombinableAttributes) + { + attributes2 = attributes2.Where(x => !x.IsNonCombinable()).ToList(); + } + if (attributes1.Count != attributes2.Count) + return false; + + bool attributesEqual = true; + foreach (var a1 in attributes1) + { + bool hasAttribute = false; + foreach (var a2 in attributes2) + { + if (a1.Id == a2.Id) + { + hasAttribute = true; + var values1Str = ParseValuesWithQuantity(attributesXml1, a1.Id); + var values2Str = ParseValuesWithQuantity(attributesXml2, a2.Id); + if (values1Str.Count == values2Str.Count) + { + foreach (var str1 in values1Str) + { + bool hasValue = false; + foreach (var str2 in values2Str) + { + //case insensitive? + //if (str1.Trim().ToLower() == str2.Trim().ToLower()) + if (str1.Item1.Trim() == str2.Item1.Trim()) + { + hasValue = ignoreQuantity ? true : str1.Item2.Trim() == str2.Item2.Trim(); + break; + } + } + + if (!hasValue) + { + attributesEqual = false; + break; + } + } + } + else + { + attributesEqual = false; + break; + } + } + } + + if (hasAttribute == false) + { + attributesEqual = false; + break; + } + } + + return attributesEqual; + } + + /// + /// Check whether condition of some attribute is met (if specified). Return "null" if not condition is specified + /// + /// Product attribute + /// Selected attributes (XML format) + /// Result + public virtual bool? IsConditionMet(ProductAttributeMapping pam, string selectedAttributesXml) + { + if (pam == null) + throw new ArgumentNullException("pam"); + + var conditionAttributeXml = pam.ConditionAttributeXml; + if (String.IsNullOrEmpty(conditionAttributeXml)) + //no condition + return null; + + //load an attribute this one depends on + var dependOnAttribute = ParseProductAttributeMappings(conditionAttributeXml).FirstOrDefault(); + if (dependOnAttribute == null) + return true; + + var valuesThatShouldBeSelected = ParseValues(conditionAttributeXml, dependOnAttribute.Id) + //a workaround here: + //ConditionAttributeXml can contain "empty" values (nothing is selected) + //but in other cases (like below) we do not store empty values + //that's why we remove empty values here + .Where(x => !String.IsNullOrEmpty(x)) + .ToList(); + var selectedValues = ParseValues(selectedAttributesXml, dependOnAttribute.Id); + if (valuesThatShouldBeSelected.Count != selectedValues.Count) + return false; + + //compare values + var allFound = true; + foreach (var t1 in valuesThatShouldBeSelected) + { + bool found = false; + foreach (var t2 in selectedValues) + if (t1 == t2) + found = true; + if (!found) + allFound = false; + } + + return allFound; + } + + /// + /// Finds a product attribute combination by attributes stored in XML + /// + /// Product + /// Attributes in XML format + /// A value indicating whether we should ignore non-combinable attributes + /// Found product attribute combination + public virtual ProductAttributeCombination FindProductAttributeCombination(Product product, + string attributesXml, bool ignoreNonCombinableAttributes = true) + { + if (product == null) + throw new ArgumentNullException("product"); + + var combinations = _productAttributeService.GetAllProductAttributeCombinations(product.Id); + return combinations.FirstOrDefault(x => + AreProductAttributesEqual(x.AttributesXml, attributesXml, ignoreNonCombinableAttributes)); + } + + /// + /// Generate all combinations + /// + /// Product + /// A value indicating whether we should ignore non-combinable attributes + /// Attribute combinations in XML format + public virtual IList GenerateAllCombinations(Product product, bool ignoreNonCombinableAttributes = false) + { + if (product == null) + throw new ArgumentNullException("product"); + + var allProductAttributMappings = _productAttributeService.GetProductAttributeMappingsByProductId(product.Id); + if (ignoreNonCombinableAttributes) + { + allProductAttributMappings = allProductAttributMappings.Where(x => !x.IsNonCombinable()).ToList(); + } + var allPossibleAttributeCombinations = new List>(); + for (int counter = 0; counter < (1 << allProductAttributMappings.Count); ++counter) + { + var combination = new List(); + for (int i = 0; i < allProductAttributMappings.Count; ++i) + { + if ((counter & (1 << i)) == 0) + { + combination.Add(allProductAttributMappings[i]); + } + } + + allPossibleAttributeCombinations.Add(combination); + } + + var allAttributesXml = new List(); + foreach (var combination in allPossibleAttributeCombinations) + { + var attributesXml = new List(); + foreach (var pam in combination) + { + if (!pam.ShouldHaveValues()) + continue; + + var attributeValues = _productAttributeService.GetProductAttributeValues(pam.Id); + if (!attributeValues.Any()) + continue; + + //checkboxes could have several values ticked + var allPossibleCheckboxCombinations = new List>(); + if (pam.AttributeControlType == AttributeControlType.Checkboxes || + pam.AttributeControlType == AttributeControlType.ReadonlyCheckboxes) + { + for (int counter = 0; counter < (1 << attributeValues.Count); ++counter) + { + var checkboxCombination = new List(); + for (int i = 0; i < attributeValues.Count; ++i) + { + if ((counter & (1 << i)) == 0) + { + checkboxCombination.Add(attributeValues[i]); + } + } + + allPossibleCheckboxCombinations.Add(checkboxCombination); + } + } + + if (!attributesXml.Any()) + { + //first set of values + if (pam.AttributeControlType == AttributeControlType.Checkboxes || + pam.AttributeControlType == AttributeControlType.ReadonlyCheckboxes) + { + //checkboxes could have several values ticked + foreach (var checkboxCombination in allPossibleCheckboxCombinations) + { + var tmp1 = ""; + foreach (var checkboxValue in checkboxCombination) + { + tmp1 = AddProductAttribute(tmp1, pam, checkboxValue.Id.ToString()); + } + if (!String.IsNullOrEmpty(tmp1)) + { + attributesXml.Add(tmp1); + } + } + } + else + { + //other attribute types (dropdownlist, radiobutton, color squares) + foreach (var attributeValue in attributeValues) + { + var tmp1 = AddProductAttribute("", pam, attributeValue.Id.ToString()); + attributesXml.Add(tmp1); + } + } + } + else + { + //next values. let's "append" them to already generated attribute combinations in XML format + var attributesXmlTmp = new List(); + if (pam.AttributeControlType == AttributeControlType.Checkboxes || + pam.AttributeControlType == AttributeControlType.ReadonlyCheckboxes) + { + //checkboxes could have several values ticked + foreach (var str1 in attributesXml) + { + foreach (var checkboxCombination in allPossibleCheckboxCombinations) + { + var tmp1 = str1; + foreach (var checkboxValue in checkboxCombination) + { + tmp1 = AddProductAttribute(tmp1, pam, checkboxValue.Id.ToString()); + } + if (!String.IsNullOrEmpty(tmp1)) + { + attributesXmlTmp.Add(tmp1); + } + } + } + } + else + { + //other attribute types (dropdownlist, radiobutton, color squares) + foreach (var attributeValue in attributeValues) + { + foreach (var str1 in attributesXml) + { + var tmp1 = AddProductAttribute(str1, pam, attributeValue.Id.ToString()); + attributesXmlTmp.Add(tmp1); + } + } + } + attributesXml.Clear(); + attributesXml.AddRange(attributesXmlTmp); + } + } + allAttributesXml.AddRange(attributesXml); + } + + //validate conditional attributes (if specified) + //minor workaround: + //once it's done (validation), then we could have some duplicated combinations in result + //we don't remove them here (for performance optimization) because anyway it'll be done in the "GenerateAllAttributeCombinations" method of ProductController + for (int i = 0; i < allAttributesXml.Count; i++) + { + var attributesXml = allAttributesXml[i]; + foreach (var attribute in allProductAttributMappings) + { + var conditionMet = IsConditionMet(attribute, attributesXml); + if (conditionMet.HasValue && !conditionMet.Value) + { + allAttributesXml[i] = RemoveProductAttribute(attributesXml, attribute); + } + } + } + return allAttributesXml; + } + + #endregion + #region taxAttribute + /// + /// Adds tax subdivision to existing attributesXml + /// + /// Attributes in XML format + /// Set Product tax subdivision + /// Updated result (XML format) + public virtual string AddTaxAttribute(string attributesXml, TaxSummary taxSummary) + { + string result = string.Empty; + try + { + var xmlDoc = new XmlDocument(); + if (String.IsNullOrEmpty(attributesXml)) + { + var element1 = xmlDoc.CreateElement("Attributes"); + xmlDoc.AppendChild(element1); + } + else + { + xmlDoc.LoadXml(attributesXml); + } + var rootElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes"); + var attributeTaxElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes/ProductAttributesTax"); + + if (taxSummary.TaxRates.Any()) + { + //create new ProductAttributesTax element if not found + if (attributeTaxElement == null) + { + attributeTaxElement = xmlDoc.CreateElement("ProductAttributesTax"); + rootElement.AppendChild(attributeTaxElement); + } + + foreach (KeyValuePair kvp in taxSummary.TaxRates) + { + decimal vatpercentage = kvp.Key; + TaxRateEntry taxrate = kvp.Value; + + //find existing + var strVatPer = vatpercentage.ToString(CultureInfo.InvariantCulture); + var strNode = String.Format("//Attributes/ProductAttributesTax/TaxRate[@Rate='{0}']", strVatPer); + var attributeTaxRateElement = (XmlElement)xmlDoc.SelectSingleNode(@strNode); + + //create new one if not found + if (attributeTaxRateElement == null) + { + attributeTaxRateElement = xmlDoc.CreateElement("TaxRate"); + attributeTaxRateElement.SetAttribute("Rate", strVatPer); + attributeTaxElement.AppendChild(attributeTaxRateElement); + } + attributeTaxRateElement.SetAttribute("RateWeight", taxrate.VatRateWeight.ToString(CultureInfo.InvariantCulture)); + + } + } + else + { + //remove attributeTaxElement when no taxRates + if (attributeTaxElement != null) + { + rootElement.RemoveChild(attributeTaxElement); + } + } + result = xmlDoc.OuterXml; + } + catch (Exception exc) + { + Debug.Write(exc.ToString()); + } + return result; + } + /// + /// Parse ProductAttributesTax + /// + /// Attributes in XML format + /// SortedDictionary with vatRate and vatRateWeight + public SortedDictionary ParseTaxAttribute(string attributesXml) + { + var taxDict = new SortedDictionary(); + var xmlDoc = new XmlDocument(); + if (!String.IsNullOrEmpty(attributesXml)) + { + xmlDoc.LoadXml(attributesXml); + foreach (XmlElement e in xmlDoc.SelectNodes("//Attributes/ProductAttributesTax/TaxRate")) + { + var style = NumberStyles.AllowDecimalPoint; + decimal rate = decimal.Zero; decimal rateWeight = decimal.Zero; + if (decimal.TryParse(e.GetAttribute("Rate"), style, CultureInfo.InvariantCulture, out rate) && decimal.TryParse(e.GetAttribute("RateWeight"), style, CultureInfo.InvariantCulture, out rateWeight)) + taxDict.Add(rate, rateWeight); + } + + } + return taxDict; + } + #endregion + #region Gift card attributes + + /// + /// Add gift card attrbibutes + /// + /// Attributes in XML format + /// Recipient name + /// Recipient email + /// Sender name + /// Sender email + /// Message + /// Attributes + public string AddGiftCardAttribute(string attributesXml, string recipientName, + string recipientEmail, string senderName, string senderEmail, string giftCardMessage) + { + string result = string.Empty; + try + { + recipientName = recipientName.Trim(); + recipientEmail = recipientEmail.Trim(); + senderName = senderName.Trim(); + senderEmail = senderEmail.Trim(); + + var xmlDoc = new XmlDocument(); + if (String.IsNullOrEmpty(attributesXml)) + { + var element1 = xmlDoc.CreateElement("Attributes"); + xmlDoc.AppendChild(element1); + } + else + { + xmlDoc.LoadXml(attributesXml); + } + + var rootElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes"); + + var giftCardElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes/GiftCardInfo"); + if (giftCardElement == null) + { + giftCardElement = xmlDoc.CreateElement("GiftCardInfo"); + rootElement.AppendChild(giftCardElement); + } + + var recipientNameElement = xmlDoc.CreateElement("RecipientName"); + recipientNameElement.InnerText = recipientName; + giftCardElement.AppendChild(recipientNameElement); + + var recipientEmailElement = xmlDoc.CreateElement("RecipientEmail"); + recipientEmailElement.InnerText = recipientEmail; + giftCardElement.AppendChild(recipientEmailElement); + + var senderNameElement = xmlDoc.CreateElement("SenderName"); + senderNameElement.InnerText = senderName; + giftCardElement.AppendChild(senderNameElement); + + var senderEmailElement = xmlDoc.CreateElement("SenderEmail"); + senderEmailElement.InnerText = senderEmail; + giftCardElement.AppendChild(senderEmailElement); + + var messageElement = xmlDoc.CreateElement("Message"); + messageElement.InnerText = giftCardMessage; + giftCardElement.AppendChild(messageElement); + + result = xmlDoc.OuterXml; + } + catch (Exception exc) + { + Debug.Write(exc.ToString()); + } + return result; + } + + /// + /// Get gift card attrbibutes + /// + /// Attributes + /// Recipient name + /// Recipient email + /// Sender name + /// Sender email + /// Message + public void GetGiftCardAttribute(string attributesXml, out string recipientName, + out string recipientEmail, out string senderName, + out string senderEmail, out string giftCardMessage) + { + recipientName = string.Empty; + recipientEmail = string.Empty; + senderName = string.Empty; + senderEmail = string.Empty; + giftCardMessage = string.Empty; + + try + { + var xmlDoc = new XmlDocument(); + xmlDoc.LoadXml(attributesXml); + + var recipientNameElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes/GiftCardInfo/RecipientName"); + var recipientEmailElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes/GiftCardInfo/RecipientEmail"); + var senderNameElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes/GiftCardInfo/SenderName"); + var senderEmailElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes/GiftCardInfo/SenderEmail"); + var messageElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes/GiftCardInfo/Message"); + + if (recipientNameElement != null) + recipientName = recipientNameElement.InnerText; + if (recipientEmailElement != null) + recipientEmail = recipientEmailElement.InnerText; + if (senderNameElement != null) + senderName = senderNameElement.InnerText; + if (senderEmailElement != null) + senderEmail = senderEmailElement.InnerText; + if (messageElement != null) + giftCardMessage = messageElement.InnerText; + } + catch (Exception exc) + { + Debug.Write(exc.ToString()); + } + } + + #endregion + } +} diff --git a/src/Libraries/Nop.Services/Common/PdfService7.cs b/src/Libraries/Nop.Services/Common/PdfService7.cs index e8c112a4652..56ccedd41a5 100644 --- a/src/Libraries/Nop.Services/Common/PdfService7.cs +++ b/src/Libraries/Nop.Services/Common/PdfService7.cs @@ -685,7 +685,8 @@ public virtual void PrintOrdersToPdf(Stream stream, IList orders, int lan tabPage.AddCell(new Cell().Add(paraPdf).AddStyle(styleCell)); } //vatrate - paraPdf = new Paragraph(_priceFormatter.FormatTaxRate(orderItem.VatRate)).AddStyle(styleNormal).SetTextAlignment(TextAlignment.CENTER); + bool hasAttribVat = !String.IsNullOrEmpty(orderItem.AttributesXml) && orderItem.AttributesXml.Contains(" - string.Format("{0}{1}:{2}:{3}:{4}:{5}:{6}; ", current, - next.Key.ToString(CultureInfo.InvariantCulture), - (next.Value.SubtotalAmount + next.Value.ShippingAmount + next.Value.PaymentFeeAmount).ToString(CultureInfo.InvariantCulture), - (next.Value.SubTotalDiscAmount + next.Value.InvoiceDiscountAmount).ToString(CultureInfo.InvariantCulture), - next.Value.BaseAmount.ToString(CultureInfo.InvariantCulture), - next.Value.VatAmount.ToString(CultureInfo.InvariantCulture), - next.Value.AmountIncludingVAT.ToString(CultureInfo.InvariantCulture) - ) - ); + details.TaxRates = taxrates.GenerateTaxRateString(); //order total (and applied discounts, gift cards, reward points) details.OrderDiscountAmount = includingTax ? taxSummaryIncl.TotalInvDiscAmount : taxSummaryExcl.TotalInvDiscAmount; @@ -502,6 +493,8 @@ protected virtual PlaceOrderContainter PreparePlaceOrderDetails(ProcessPaymentRe details.RedeemedRewardPointsAmount = redeemedRewardPointsAmount; details.AppliedGiftCards = appliedGiftCards; details.OrderTotal = includingTax ? orderTotalIncl ?? 0 : orderTotalExcl ?? 0; + details.OrderAmount = includingTax ? taxSummaryIncl.TotalAmount : taxSummaryExcl.TotalAmount; + details.OrderAmountIncl = includingTax ? taxSummaryIncl.TotalAmountIncludingVAT : taxSummaryExcl.TotalAmountIncludingVAT; //discount history foreach (var disc in orderAppliedDiscounts) @@ -573,7 +566,7 @@ protected virtual PlaceOrderContainter PrepareRecurringOrderDetails(ProcessPayme //billing address if (details.InitialOrder.BillingAddress == null) throw new NopException("Billing address is not available"); - + details.BillingAddress = (Address)details.InitialOrder.BillingAddress.Clone(); if (details.BillingAddress.Country != null && !details.BillingAddress.Country.AllowsBilling) throw new NopException(string.Format("Country '{0}' is not allowed for billing", details.BillingAddress.Country.Name)); @@ -884,7 +877,7 @@ protected virtual void ReturnBackRedeemedRewardPoints(Order order) /// A value indicating whether to activate gift cards; true - activate, false - deactivate protected virtual void SetActivatedValueForPurchasedGiftCards(Order order, bool activate) { - var giftCards = _giftCardService.GetAllGiftCards(purchasedWithOrderId: order.Id, + var giftCards = _giftCardService.GetAllGiftCards(purchasedWithOrderId: order.Id, isGiftCardActivated: !activate); foreach (var gc in giftCards) { @@ -1316,7 +1309,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR details.AppliedDiscounts.Add(disc); //attributes - var attributeDescription = _productAttributeFormatter.FormatAttributes(sc.Product, sc.AttributesXml, details.Customer); + var attributeDescription = _productAttributeFormatter.FormatAttributes(sc.Product, sc.AttributesXml, details.Customer, subTotal: _taxSettings.PricesIncludeTax ? scSubTotalInclTax : scSubTotalExclTax); var itemWeight = _shippingService.GetShoppingCartItemWeight(sc); @@ -1465,7 +1458,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR //check order status CheckOrderStatus(order); - //raise event + //raise event _eventPublisher.Publish(new OrderPlacedEvent(order)); if (order.PaymentStatus == PaymentStatus.Paid) @@ -1559,7 +1552,7 @@ public virtual void UpdateOrderTotals(UpdateOrderParameters updateOrderParameter updatedOrderItem.ItemWeight = _shippingService.GetShoppingCartItemWeight(updatedShoppingCartItem); updatedOrderItem.OriginalProductCost = _priceCalculationService.GetProductCost(updatedShoppingCartItem.Product, updatedShoppingCartItem.AttributesXml); updatedOrderItem.AttributeDescription = _productAttributeFormatter.FormatAttributes(updatedShoppingCartItem.Product, - updatedShoppingCartItem.AttributesXml, updatedOrder.Customer); + updatedShoppingCartItem.AttributesXml, updatedOrder.Customer,subTotal: updatedOrder.CustomerTaxDisplayType == TaxDisplayType.IncludingTax ? updatedOrderItem.PriceInclTax : updatedOrderItem.PriceExclTax); //gift cards if (updatedShoppingCartItem.Product.IsGiftCard) @@ -1682,7 +1675,7 @@ public virtual void DeleteOrder(Order order) CreatedOnUtc = DateTime.UtcNow }); _orderService.UpdateOrder(order); - + //now delete an order _orderService.DeleteOrder(order); } @@ -1857,7 +1850,7 @@ public virtual IEnumerable ProcessNextRecurringPayment(RecurringPayment //check order status CheckOrderStatus(order); - //raise event + //raise event _eventPublisher.Publish(new OrderPlacedEvent(order)); if (order.PaymentStatus == PaymentStatus.Paid) @@ -2366,7 +2359,7 @@ public virtual IList Capture(Order order) _orderService.UpdateOrder(order); CheckOrderStatus(order); - + if (order.PaymentStatus == PaymentStatus.Paid) { ProcessOrderPaid(order); @@ -2454,7 +2447,7 @@ public virtual void MarkOrderAsPaid(Order order) _orderService.UpdateOrder(order); CheckOrderStatus(order); - + if (order.PaymentStatus == PaymentStatus.Paid) { ProcessOrderPaid(order); @@ -2490,7 +2483,7 @@ public virtual bool CanRefund(Order order) return false; } - + /// /// Refunds an order (from admin panel) /// @@ -2558,7 +2551,7 @@ public virtual IList Refund(Order order) _orderService.UpdateOrder(order); } - //raise event + //raise event _eventPublisher.Publish(new OrderRefundedEvent(order, request.AmountToRefund)); } @@ -2682,7 +2675,7 @@ public virtual void RefundOffline(Order order) _orderService.UpdateOrder(order); } - //raise event + //raise event _eventPublisher.Publish(new OrderRefundedEvent(order, amountToRefund)); } @@ -2791,7 +2784,7 @@ public virtual IList PartiallyRefund(Order order, decimal amountToRefund _orderService.UpdateOrder(order); } - //raise event + //raise event _eventPublisher.Publish(new OrderRefundedEvent(order, amountToRefund)); } } @@ -2869,7 +2862,7 @@ public virtual void PartiallyRefundOffline(Order order, decimal amountToRefund) { if (order == null) throw new ArgumentNullException("order"); - + if (!CanPartiallyRefundOffline(order, amountToRefund)) throw new NopException("You can't partially refund (offline) this order"); @@ -2917,7 +2910,7 @@ public virtual void PartiallyRefundOffline(Order order, decimal amountToRefund) }); _orderService.UpdateOrder(order); } - //raise event + //raise event _eventPublisher.Publish(new OrderRefundedEvent(order, amountToRefund)); } @@ -3085,7 +3078,7 @@ public virtual void ReOrder(Order order) foreach (var orderItem in order.OrderItems) { _shoppingCartService.AddToCart(order.Customer, orderItem.Product, - ShoppingCartType.ShoppingCart, order.StoreId, + ShoppingCartType.ShoppingCart, order.StoreId, orderItem.AttributesXml, orderItem.UnitPriceExclTax, orderItem.RentalStartDateUtc, orderItem.RentalEndDateUtc, orderItem.Quantity, false); @@ -3095,7 +3088,7 @@ public virtual void ReOrder(Order order) //comment the code below if you want to disable this functionality _genericAttributeService.SaveAttribute(order.Customer, SystemCustomerAttributeNames.CheckoutAttributes, order.CheckoutAttributesXml, order.StoreId); } - + /// /// Check whether return request is allowed /// @@ -3124,7 +3117,7 @@ public virtual bool IsReturnRequestAllowed(Order order) //ensure that we have at least one returnable product return order.OrderItems.Any(oi => !oi.Product.NotReturnable); } - + /// diff --git a/src/Libraries/Nop.Services/Orders/OrderTotalCalculationService.cs b/src/Libraries/Nop.Services/Orders/OrderTotalCalculationService.cs index 94ded4673ca..9c170915837 100644 --- a/src/Libraries/Nop.Services/Orders/OrderTotalCalculationService.cs +++ b/src/Libraries/Nop.Services/Orders/OrderTotalCalculationService.cs @@ -43,6 +43,7 @@ public partial class OrderTotalCalculationService : IOrderTotalCalculationServic private readonly ShippingSettings _shippingSettings; private readonly ShoppingCartSettings _shoppingCartSettings; private readonly CatalogSettings _catalogSettings; + private readonly IProductAttributeParser _productAttributeParser; #endregion #region Ctor @@ -66,6 +67,7 @@ public partial class OrderTotalCalculationService : IOrderTotalCalculationServic /// Shipping settings /// Shopping cart settings /// Catalog settings + /// Product Attribute Parser public OrderTotalCalculationService(IWorkContext workContext, IStoreContext storeContext, IPriceCalculationService priceCalculationService, @@ -81,7 +83,8 @@ public OrderTotalCalculationService(IWorkContext workContext, RewardPointsSettings rewardPointsSettings, ShippingSettings shippingSettings, ShoppingCartSettings shoppingCartSettings, - CatalogSettings catalogSettings) + CatalogSettings catalogSettings, + IProductAttributeParser productAttributeParser) { this._workContext = workContext; this._storeContext = storeContext; @@ -99,6 +102,7 @@ public OrderTotalCalculationService(IWorkContext workContext, this._shippingSettings = shippingSettings; this._shoppingCartSettings = shoppingCartSettings; this._catalogSettings = catalogSettings; + this._productAttributeParser = productAttributeParser; } #endregion @@ -269,7 +273,7 @@ public virtual void GetShoppingCartSubTotal(IList cart, decimal taxRate; //restored cartitem - if (shoppingCartItem.VatRate.HasValue) + if (shoppingCartItem.VatRate.HasValue && shoppingCartItem.SubTotalExclTax != null && shoppingCartItem.SubTotalInclTax != null) { itemAmount = includingTax ? shoppingCartItem.SubTotalInclTax ?? 0 : shoppingCartItem.SubTotalExclTax ?? 0; taxRate = shoppingCartItem.VatRate ?? 0; @@ -282,9 +286,19 @@ public virtual void GetShoppingCartSubTotal(IList cart, itemAmount = _taxService.GetProductPrice(shoppingCartItem.Product, sciSubTotal, includingTax, customer, out taxRate); subTotalAmount += itemAmount; } + //tax rates - if (itemAmount > decimal.Zero) //MF 22.11.16 taxRate > decimal.Zero &&: VatBase is need also when tax is 0 to show up in summary - taxSummary.AddRate(taxRate, itemAmount); + var attributesXml = shoppingCartItem.AttributesXml; + SortedDictionary attributeTaxWeight = !String.IsNullOrEmpty(attributesXml) ? _productAttributeParser.ParseTaxAttribute(attributesXml) : null; + if (attributeTaxWeight == null || !attributeTaxWeight.Any()) + { + if (itemAmount > decimal.Zero) //MF 22.11.16 taxRate > decimal.Zero &&: VatBase is need also when tax is 0 to show up in summary + taxSummary.AddRate(taxRate, itemAmount); + } + else //tax in attributes + { + taxSummary.AddAttributeRate(itemAmount, attributeTaxWeight); + } } //checkout attributes @@ -495,16 +509,7 @@ public virtual void UpdateOrderTotals(UpdateOrderParameters updateOrderParameter #region Tax rates //tax rates var taxrates = includingTax ? taxSummaryIncl : taxSummaryExcl; - updatedOrder.TaxRates = taxrates.TaxRates.Aggregate(string.Empty, (current, next) => - string.Format("{0}{1}:{2}:{3}:{4}:{5}:{6}; ", current, - next.Key.ToString(CultureInfo.InvariantCulture), - (next.Value.SubtotalAmount + next.Value.ShippingAmount + next.Value.PaymentFeeAmount).ToString(CultureInfo.InvariantCulture), - (next.Value.SubTotalDiscAmount + next.Value.InvoiceDiscountAmount).ToString(CultureInfo.InvariantCulture), - next.Value.BaseAmount.ToString(CultureInfo.InvariantCulture), - next.Value.VatAmount.ToString(CultureInfo.InvariantCulture), - next.Value.AmountIncludingVAT.ToString(CultureInfo.InvariantCulture) - ) - ); + updatedOrder.TaxRates = taxrates.GenerateTaxRateString(); #endregion @@ -862,7 +867,7 @@ public virtual decimal GetTaxTotal(IList cart, if (paymentMethodAdditionalFee != decimal.Zero ) //tfc case of taxable fee > 0 - if (paymentMethodAdditionalFee > decimal.Zero && _taxSettings.PaymentMethodAdditionalFeeIsTaxable) + if (paymentMethodAdditionalFee > decimal.Zero && _taxSettings.PaymentMethodAdditionalFeeIsTaxable) taxSummary.AddRate(taxRate, paymentfeeAmount: paymentMethodAdditionalFee); //tfc case fee < 0 is considered as discount or as surcharge when not taxable else diff --git a/src/Libraries/Nop.Services/Tax/TaxService.cs b/src/Libraries/Nop.Services/Tax/TaxService.cs index f883203b342..dd1f7186159 100644 --- a/src/Libraries/Nop.Services/Tax/TaxService.cs +++ b/src/Libraries/Nop.Services/Tax/TaxService.cs @@ -422,9 +422,6 @@ public virtual decimal GetProductPrice(Product product, int taxCategoryId, return price; } - - - if (priceIncludesTax) { //"price" already includes tax diff --git a/src/Libraries/Nop.Services/Tax/TaxSummary.cs b/src/Libraries/Nop.Services/Tax/TaxSummary.cs index 87213341666..440e9077875 100644 --- a/src/Libraries/Nop.Services/Tax/TaxSummary.cs +++ b/src/Libraries/Nop.Services/Tax/TaxSummary.cs @@ -1,23 +1,28 @@  using Nop.Services.Catalog; using System.Collections.Generic; +using System.Globalization; using System.Linq; +using System.Text; namespace Nop.Services.Tax { /// /// TaxRate entry to store amounts per vatrate - /// all value are per rate and most of them calculated + /// all values are per rate and most of them calculated /// only VatRate, ItemAmount, ShippingAmount and PaymentFeeAmount can be set /// public partial class TaxRateEntry { public decimal VatRate { get; set; } + public decimal VatRateWeight { get; internal set; } //weight of VatRate used for product sets [it's (sum of associated products price per VatRate) / (total price of assoc. prod.) ] public decimal SubtotalAmount { get; set; } public decimal ShippingAmount { get; set; } public decimal PaymentFeeAmount { get; set; } //can be negative when discounted + public decimal Amount { get; internal set; } //sum of above amounts public decimal SubTotalDiscAmount { get; internal set; } public decimal InvoiceDiscountAmount { get; internal set; } + public decimal DiscountAmount { get; internal set; } //sum of above discounts public decimal BaseAmount { get; internal set; } public decimal VatAmount { get; internal set; } public decimal AmountIncludingVAT { get; internal set; } @@ -68,6 +73,19 @@ public TaxSummary(bool includingTax) private bool HasChanged = false; #region utilities + public string GenerateTaxRateString() + { + return this.TaxRates.Aggregate(string.Empty, (current, next) => + string.Format("{0}{1}:{2}:{3}:{4}:{5}:{6}; ", current, + next.Key.ToString(CultureInfo.InvariantCulture), + next.Value.Amount.ToString(CultureInfo.InvariantCulture), + next.Value.DiscountAmount.ToString(CultureInfo.InvariantCulture), + next.Value.BaseAmount.ToString(CultureInfo.InvariantCulture), + next.Value.VatAmount.ToString(CultureInfo.InvariantCulture), + next.Value.AmountIncludingVAT.ToString(CultureInfo.InvariantCulture) + ) + ); + } public SortedDictionary GenerateOldTaxrateDict() { return new SortedDictionary(TaxRates.ToDictionary(x => x.Key, x => x.Value.VatAmount)); @@ -101,7 +119,51 @@ public void AddRate(decimal vatRate, decimal itemAmount = 0, decimal shippingAmo } HasChanged = true; } + /// + /// + /// + /// Total Amount of Attributes + /// dictionary of taxRate weights on that amount + public void AddAttributeRate(decimal attribAmount, SortedDictionary attributeTaxWeight) + { + var dummyResult = ParseOrAddAttributeRate(attribAmount, attributeTaxWeight, doAdd: true); + } + + public SortedDictionary ParseAttributeRate(decimal attribAmount, SortedDictionary attributeTaxWeight) + { + return ParseOrAddAttributeRate(attribAmount, attributeTaxWeight, doAdd: false); + } + public SortedDictionary ParseOrAddAttributeRate(decimal attribAmount, SortedDictionary attributeTaxWeight, bool doAdd = true) + { + var result = new SortedDictionary(); + int i = 0; + int c = attributeTaxWeight.Count(); + decimal reminder = attribAmount; + foreach (KeyValuePair kvp in attributeTaxWeight) + { + i += 1; + decimal vatpercentage = kvp.Key; + decimal rateWeight = kvp.Value; + var attribAmountWeighted = RoundingHelper.RoundAmount(attribAmount * rateWeight); + if (i < c) + { + if (doAdd) + AddRate(vatpercentage, attribAmountWeighted); + else + result.Add(kvp.Key, attribAmountWeighted); + reminder -= attribAmountWeighted; + } + else + { + if (doAdd) + AddRate(vatpercentage, reminder); + else + result.Add(kvp.Key, reminder); + } + } + return result; + } public void SetSubtotalDiscAmount(decimal totalSubTotalDiscAmount, decimal totalAmount = 0) { if (totalAmount == 0) @@ -120,7 +182,7 @@ public void SetSubtotalDiscAmount(decimal totalSubTotalDiscAmount, decimal total /// Base amount to which apply fee public void SetPaymentFeeDiscAmount(decimal totalPaymentFeeDiscAmount, decimal totalAmount) { - PercentagePaymentFeeDiscount = totalPaymentFeeDiscAmount / totalAmount * 100; + PercentagePaymentFeeDiscount = totalPaymentFeeDiscAmount / totalAmount * 100; HasChanged = true; } @@ -192,8 +254,11 @@ public void CalculateAmounts() //last remainder get's lost as it can't be considered anywhere else. This has no implication and only lowers or highers discount. + taxrate.Amount = taxrate.SubtotalAmount + taxrate.ShippingAmount + taxrate.PaymentFeeAmount; + taxrate.DiscountAmount = taxrate.SubTotalDiscAmount + taxrate.InvoiceDiscountAmount; + //VAT: always round VAT first - decimal rateamount = taxrate.SubtotalAmount + taxrate.ShippingAmount + taxrate.PaymentFeeAmount - taxrate.SubTotalDiscAmount - taxrate.InvoiceDiscountAmount; + decimal rateamount = taxrate.Amount - taxrate.DiscountAmount; if (PricesIncludeTax) { taxrate.AmountIncludingVAT = rateamount; @@ -221,7 +286,24 @@ public void CalculateAmounts() TotalAmount += taxrate.BaseAmount; TotalAmountVAT += taxrate.VatAmount; TotalAmountIncludingVAT += taxrate.AmountIncludingVAT; - + } + int i = 0; + int c = TaxRates.Count(); + decimal totWeight = decimal.Zero; + foreach (KeyValuePair kvp in this.TaxRates) + { + i++; + decimal vatpercentage = kvp.Key; + TaxRateEntry taxrate = kvp.Value; + if (i < c) + { + taxrate.VatRateWeight = (this.PricesIncludeTax ? (taxrate.AmountIncludingVAT / this.TotalAmountIncludingVAT) : (taxrate.BaseAmount / this.TotalAmount)); + totWeight += taxrate.VatRateWeight; + } + else + { + taxrate.VatRateWeight = decimal.One - totWeight; //assure sum of VatRateWeight = 1 + } } } #endregion diff --git a/src/Presentation/Nop.Web/Administration/Controllers/OrderController.cs b/src/Presentation/Nop.Web/Administration/Controllers/OrderController.cs index 719d22dfe95..f4fe5176184 100644 --- a/src/Presentation/Nop.Web/Administration/Controllers/OrderController.cs +++ b/src/Presentation/Nop.Web/Administration/Controllers/OrderController.cs @@ -868,6 +868,7 @@ protected virtual OrderModel.AddOrderProductModel.ProductDetailsModel PrepareAdd }; //attributes + string attributesXml = ""; var attributes = _productAttributeService.GetProductAttributeMappingsByProductId(product.Id); foreach (var attribute in attributes) { @@ -911,10 +912,37 @@ protected virtual OrderModel.AddOrderProductModel.ProductDetailsModel PrepareAdd PriceAdjustmentValue = priceAdjustment }); } + + //creating XML for "read-only checkboxes" attributes + foreach (var selectedAttributeId in attributeValues + .Where(v => v.IsPreSelected) + .Select(v => v.Id) + .ToList()) + { + attributesXml = _productAttributeParser.AddProductAttribute(attributesXml, + attribute, selectedAttributeId.ToString()); + } } model.ProductAttributes.Add(attributeModel); } + + //Get final price using attributes + List scDiscounts; + decimal discountAmount; + decimal finalPrice = _priceCalculationService.GetUnitPrice(product, + order.Customer, + ShoppingCartType.ShoppingCart, + 1, ref attributesXml, 0, + null, null, + true, out discountAmount, out scDiscounts); + decimal finalPriceInclTax = RoundingHelper.RoundPrice(_taxService.GetProductPrice(product, finalPrice, true, order.Customer, out taxRate)); + decimal finalPriceExclTax = RoundingHelper.RoundPrice(_taxService.GetProductPrice(product, finalPrice, false, order.Customer, out taxRate)); + model.UnitPriceExclTax = finalPriceExclTax; + model.UnitPriceInclTax = finalPriceInclTax; + model.SubTotalExclTax = RoundingHelper.RoundAmount(finalPriceExclTax); + model.SubTotalInclTax = RoundingHelper.RoundAmount(finalPriceInclTax); + model.HasCondition = model.ProductAttributes.Any(a => a.HasCondition); //gift card model.GiftCard.IsGiftCard = product.IsGiftCard; @@ -1988,6 +2016,30 @@ public virtual ActionResult ProductDetails_AttributeChange(int productId, bool v var errors = new List(); var attributeXml = ParseProductAttributes(product, form, errors); + //Get final price using attributes + decimal finalPriceInclTax = decimal.Zero; decimal finalPriceExclTax = decimal.Zero; + decimal finalSubInclTax = decimal.Zero; decimal finalSubExclTax = decimal.Zero; + + List scDiscounts; + decimal discountAmount; + decimal taxRate; + int quantity; + decimal finalPrice = _priceCalculationService.GetUnitPrice(product, + _workContext.CurrentCustomer, + ShoppingCartType.ShoppingCart, + 1, ref attributeXml, 0, + null, null, + true, out discountAmount, out scDiscounts); + finalPriceInclTax = RoundingHelper.RoundPrice(_taxService.GetProductPrice(product, finalPrice, true, _workContext.CurrentCustomer, out taxRate)); + finalPriceExclTax = RoundingHelper.RoundPrice(_taxService.GetProductPrice(product, finalPrice, false, _workContext.CurrentCustomer, out taxRate)); + finalSubInclTax = RoundingHelper.RoundAmount(finalPriceInclTax); + finalSubExclTax = RoundingHelper.RoundAmount(finalPriceExclTax); + if (int.TryParse(form["Quantity"], out quantity)) + { + finalSubInclTax = RoundingHelper.RoundAmount(_taxService.GetProductPrice(product, finalPriceInclTax * quantity, true, _workContext.CurrentCustomer, out taxRate)); + finalSubExclTax = RoundingHelper.RoundAmount(_taxService.GetProductPrice(product, finalPriceInclTax * quantity, false, _workContext.CurrentCustomer, out taxRate)); + } + //conditional attributes var enabledAttributeMappingIds = new List(); var disabledAttributeMappingIds = new List(); @@ -2011,6 +2063,10 @@ public virtual ActionResult ProductDetails_AttributeChange(int productId, bool v { enabledattributemappingids = enabledAttributeMappingIds.ToArray(), disabledattributemappingids = disabledAttributeMappingIds.ToArray(), + finalPriceInclTax, + finalPriceExclTax, + finalSubInclTax, + finalSubExclTax, message = errors.Any() ? errors.ToArray() : null }); } @@ -2689,6 +2745,16 @@ public virtual ActionResult AddProductToOrderDetails(int orderId, int productId, //attributes var attributesXml = ParseProductAttributes(product, form, warnings); + //attributes tax + List scDiscounts; + decimal discountAmount; + decimal finalPrice = _priceCalculationService.GetUnitPrice(product, + _workContext.CurrentCustomer, + ShoppingCartType.ShoppingCart, + 1, ref attributesXml, 0, + null, null, + true, out discountAmount, out scDiscounts); + #region Gift cards string recipientName = ""; @@ -2753,7 +2819,7 @@ public virtual ActionResult AddProductToOrderDetails(int orderId, int productId, //no errors //attributes - var attributeDescription = _productAttributeFormatter.FormatAttributes(product, attributesXml, order.Customer); + var attributeDescription = _productAttributeFormatter.FormatAttributes(product, attributesXml, order.Customer, subTotal: order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax ? priceInclTax : priceExclTax); //save item var orderItem = new OrderItem diff --git a/src/Presentation/Nop.Web/Administration/Views/Order/_ProductAddAttributes.cshtml b/src/Presentation/Nop.Web/Administration/Views/Order/_ProductAddAttributes.cshtml index b8e1cc75a0a..5815b9ad357 100644 --- a/src/Presentation/Nop.Web/Administration/Views/Order/_ProductAddAttributes.cshtml +++ b/src/Presentation/Nop.Web/Administration/Views/Order/_ProductAddAttributes.cshtml @@ -299,6 +299,18 @@ if (data.message) { alert(data.message); } + if (data.finalPriceInclTax) { + setAttribValue('#UnitPriceInclTax', data.finalPriceInclTax); + } + if (data.finalPriceInclTax) { + setAttribValue('#UnitPriceExclTax', data.finalPriceExclTax); + } + if (data.finalPriceInclTax) { + setAttribValue('#SubTotalInclTax', data.finalSubInclTax); + } + if (data.finalPriceInclTax) { + setAttribValue('#SubTotalExclTax', data.finalSubExclTax); + } } }); } @@ -307,6 +319,12 @@ @(attributeChangeHandlerFuncName)(); @Html.Raw(attributeChangeScriptsBuilder.ToString()) }); + + function setAttribValue(attrib, value) + { + $(attrib).val(value).parent().children(':first-child').val(value); + return; + } - - } -
    - @if (String.IsNullOrEmpty(Model.MinOrderSubtotalWarning) && !Model.HideCheckoutButton) - { - - - } -
    -
    - @*Payment method buttons (e.g. GoogleCheckoutButton, Paypal Express)*@ - @Html.Partial("_ButtonPaymentMethod", Model) -
    - } - - - if (Model.IsEditable) - { - @Html.Action("CrossSellProducts", "Product") - } - } - } - else - { -
    - @T("ShoppingCart.CartIsEmpty") -
    - } - @Html.Widget("order_summary_content_after") +@model ShoppingCartModel +@using Nop.Core +@using Nop.Core.Domain.Tax +@using Nop.Core.Infrastructure +@using Nop.Web.Models.ShoppingCart; +@{ + var webHelper = EngineContext.Current.Resolve(); +} +
    + @Html.Widget("order_summary_content_before") + @Html.Partial("_OrderReviewData", Model.OrderReviewData) + @if (Model.Items.Count > 0) + { + if (Model.Warnings.Count > 0) + { +
    +
      + @foreach (var warning in Model.Warnings) + { +
    • @warning
    • + } +
    +
    + } + @*we add enctype = "multipart/form-data" because "File upload" attribute control type requires it*@ + using (Html.BeginRouteForm("ShoppingCart", FormMethod.Post, new { enctype = "multipart/form-data", id = "shopping-cart-form" })) + { + @Html.AntiForgeryToken() +
    + + + @if (Model.IsEditable) + { + + } + @if (Model.ShowSku) + { + + } + @if (Model.ShowProductImages) + { + + } + + + + + + + + + @if (Model.IsEditable) + { + + } + @if (Model.ShowSku) + { + + } + @if (Model.ShowProductImages) + { + + } + + + + + + + + + @foreach (var item in Model.Items) + { + + @if (Model.IsEditable) + { + + } + @if (Model.ShowSku) + { + + } + @if (Model.ShowProductImages) + { + + } + + + + + + + } + +
    + @T("ShoppingCart.Remove") + + @T("ShoppingCart.SKU") + + @T("ShoppingCart.Image") + + @T("ShoppingCart.Product(s)") + + @T("ShoppingCart.UnitPrice") + + @T("ShoppingCart.Quantity") + + @T("ShoppingCart.VatRate") + + @T("ShoppingCart.ItemTotal") +
    + @if (item.DisableRemoval) + { +   + } + else + { + + + } + + + @item.Sku + + @item.Picture.AlternateText + + @item.ProductName + @if (!String.IsNullOrEmpty(item.AttributeInfo)) + { +
    + @Html.Raw(item.AttributeInfo) +
    + } + @if (!String.IsNullOrEmpty(item.RecurringInfo)) + { +
    + @Html.Raw(item.RecurringInfo) +
    + } + @if (!String.IsNullOrEmpty(item.RentalInfo)) + { +
    + @Html.Raw(item.RentalInfo) +
    + } + @if (Model.IsEditable && item.AllowItemEditing) + { + var editCartItemUrl = Url.RouteUrl("Product", new {SeName = item.ProductSeName}); + editCartItemUrl = webHelper.ModifyQueryString(editCartItemUrl, "updatecartitemid=" + item.Id, null); + + } + @if (item.Warnings.Count > 0) + { +
    +
      + @foreach (var warning in item.Warnings) + { +
    • @warning
    • + } +
    +
    + } +
    + + @item.UnitPrice + + + @if (Model.IsEditable) + { + if (item.AllowedQuantities.Count > 0) + { + + } + else + { + + } + + } + else + { + @item.Quantity + } + + + @*@item.VatRate.ToString("G29")*@ + @item.VatRate + + + @item.SubTotal + @if (!String.IsNullOrEmpty(item.Discount)) + { +
    + @T("ShoppingCart.ItemYouSave", item.Discount) +
    + if (item.MaximumDiscountedQty.HasValue) + { +
    + @T("ShoppingCart.MaximumDiscountedQty", item.MaximumDiscountedQty.Value) +
    + } + } +
    +
    + if (Model.IsEditable && Model.Items.Count > 0 && Model.DisplayTaxShippingInfo) + { + var inclTax = EngineContext.Current.Resolve().TaxDisplayType == TaxDisplayType.IncludingTax; + //tax info is already included in the price (incl/excl tax). that's why we display only shipping info here + //of course, you can modify appropriate locales to include VAT info there +
    + @T(inclTax ? "ShoppingCart.TaxShipping.InclTax" : "ShoppingCart.TaxShipping.ExclTax", Url.RouteUrl("Topic", new { SeName = Html.GetTopicSeName("shippinginfo") })) +
    + } +
    + @if (Model.IsEditable) + { +
    + + +
    + } + @if (Model.IsEditable) + { + @Html.Partial("_CheckoutAttributes", Model.CheckoutAttributes, new ViewDataDictionary()) + } + @if (!String.IsNullOrEmpty(Model.CheckoutAttributeInfo)) + { +
    + @Html.Raw(Model.CheckoutAttributeInfo) +
    + } +
    + + if (Model.IsEditable) + { + @Html.Action("CrossSellProducts", "Product") + } + } + } + else + { +
    + @T("ShoppingCart.CartIsEmpty") +
    + } + @Html.Widget("order_summary_content_after")
    \ No newline at end of file diff --git a/src/Tests/Nop.Services.Tests/Catalog/PriceCalculationServiceTests.cs b/src/Tests/Nop.Services.Tests/Catalog/PriceCalculationServiceTests.cs index 2cdde076c3a..69d2615f554 100644 --- a/src/Tests/Nop.Services.Tests/Catalog/PriceCalculationServiceTests.cs +++ b/src/Tests/Nop.Services.Tests/Catalog/PriceCalculationServiceTests.cs @@ -16,6 +16,7 @@ using Nop.Tests; using NUnit.Framework; using Rhino.Mocks; +using Nop.Services.Tax; namespace Nop.Services.Tests.Catalog { @@ -33,7 +34,7 @@ public class PriceCalculationServiceTests : ServiceTest private ShoppingCartSettings _shoppingCartSettings; private CatalogSettings _catalogSettings; private ICacheManager _cacheManager; - + private ITaxService _taxService; private Store _store; [SetUp] @@ -66,7 +67,8 @@ public class PriceCalculationServiceTests : ServiceTest _productAttributeParser, _productService, _cacheManager, - _shoppingCartSettings, + _shoppingCartSettings, + _taxService, _catalogSettings); var nopEngine = MockRepository.GenerateMock(); diff --git a/src/Tests/Nop.Services.Tests/Orders/OrderProcessingServiceTests.cs b/src/Tests/Nop.Services.Tests/Orders/OrderProcessingServiceTests.cs index 402f6ea9063..9eabb5148f9 100644 --- a/src/Tests/Nop.Services.Tests/Orders/OrderProcessingServiceTests.cs +++ b/src/Tests/Nop.Services.Tests/Orders/OrderProcessingServiceTests.cs @@ -127,7 +127,7 @@ public class OrderProcessingServiceTests : ServiceTest _priceCalcService = new PriceCalculationService(_workContext, _storeContext, _discountService, _categoryService, _manufacturerService, _productAttributeParser, _productService, - cacheManager, _shoppingCartSettings, _catalogSettings); + cacheManager, _shoppingCartSettings, _catalogSettings, _taxService); _eventPublisher = MockRepository.GenerateMock(); _eventPublisher.Expect(x => x.Publish(Arg.Is.Anything)); @@ -188,7 +188,7 @@ public class OrderProcessingServiceTests : ServiceTest _priceCalcService, _taxService, _shippingService, _paymentService, _checkoutAttributeParser, _discountService, _giftCardService, _genericAttributeService, _rewardPointService, - _taxSettings, _rewardPointsSettings, _shippingSettings, _shoppingCartSettings, _catalogSettings); + _taxSettings, _rewardPointsSettings, _shippingSettings, _shoppingCartSettings, _catalogSettings, _productAttributeParser); _orderService = MockRepository.GenerateMock(); _webHelper = MockRepository.GenerateMock(); diff --git a/src/Tests/Nop.Services.Tests/Orders/OrderTotalCalculationServiceTests.cs b/src/Tests/Nop.Services.Tests/Orders/OrderTotalCalculationServiceTests.cs index 3704aff3f43..d10c12fcc9e 100644 --- a/src/Tests/Nop.Services.Tests/Orders/OrderTotalCalculationServiceTests.cs +++ b/src/Tests/Nop.Services.Tests/Orders/OrderTotalCalculationServiceTests.cs @@ -92,7 +92,7 @@ public class OrderTotalCalculationServiceTests : ServiceTest _discountService, _categoryService, _manufacturerService, _productAttributeParser, _productService, cacheManager, - _shoppingCartSettings, _catalogSettings); + _shoppingCartSettings, _catalogSettings, _taxService); _eventPublisher = MockRepository.GenerateMock(); _eventPublisher.Expect(x => x.Publish(Arg.Is.Anything)); @@ -155,7 +155,7 @@ public class OrderTotalCalculationServiceTests : ServiceTest _priceCalcService, _taxService, _shippingService, _paymentService, _checkoutAttributeParser, _discountService, _giftCardService, _genericAttributeService, _rewardPointService, _taxSettings, _rewardPointsSettings, - _shippingSettings, _shoppingCartSettings, _catalogSettings); + _shippingSettings, _shoppingCartSettings, _catalogSettings, _productAttributeParser); } [Test] From d2edeb2928f0dd89cbd576249422cb0f830f33bc Mon Sep 17 00:00:00 2001 From: Markus Falk Date: Mon, 6 Feb 2017 11:22:44 +0100 Subject: [PATCH 03/13] PrepareProductOverviewPriceModel: Display set price --- .../Nop.Web/Factories/ProductModelFactory.cs | 49 ++++++++++++++++--- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/src/Presentation/Nop.Web/Factories/ProductModelFactory.cs b/src/Presentation/Nop.Web/Factories/ProductModelFactory.cs index efba2dc5322..d189b4d2f4b 100644 --- a/src/Presentation/Nop.Web/Factories/ProductModelFactory.cs +++ b/src/Presentation/Nop.Web/Factories/ProductModelFactory.cs @@ -30,13 +30,14 @@ using Nop.Web.Models.Catalog; using Nop.Web.Models.Common; using Nop.Web.Models.Media; +using Nop.Services.Discounts; namespace Nop.Web.Factories { public partial class ProductModelFactory : IProductModelFactory { #region Fields - + private readonly ISpecificationAttributeService _specificationAttributeService; private readonly ICategoryService _categoryService; private readonly IManufacturerService _manufacturerService; @@ -239,7 +240,7 @@ protected virtual ProductOverviewModel.ProductPriceModel PrepareProductOverviewP if (associatedProduct.HasTierPrices) { //calculate price for the maximum quantity if we have tier prices, and choose minimal - tmpMinPossiblePrice = Math.Min(tmpMinPossiblePrice, + tmpMinPossiblePrice = Math.Min(tmpMinPossiblePrice, _priceCalculationService.GetFinalPrice(associatedProduct, _workContext.CurrentCustomer, quantity: int.MaxValue)); } @@ -341,10 +342,44 @@ protected virtual ProductOverviewModel.ProductPriceModel PrepareProductOverviewP if (product.HasTierPrices) { //calculate price for the maximum quantity if we have tier prices, and choose minimal - minPossiblePrice = Math.Min(minPossiblePrice, + minPossiblePrice = Math.Min(minPossiblePrice, _priceCalculationService.GetFinalPrice(product, _workContext.CurrentCustomer, quantity: int.MaxValue)); } + //attributes "set" + string attributesXml = ""; + var attributes = _productAttributeService.GetProductAttributeMappingsByProductId(product.Id); + foreach (var attribute in attributes) + { + if (attribute.ShouldHaveValues()) + { + //values + var attributeValues = _productAttributeService.GetProductAttributeValues(attribute.Id); + + //creating XML for "read-only checkboxes" attributes + foreach (var selectedAttributeId in attributeValues + .Where(v => v.IsPreSelected) + .Select(v => v.Id) + .ToList()) + { + attributesXml = _productAttributeParser.AddProductAttribute(attributesXml, + attribute, selectedAttributeId.ToString()); + } + } + + } + //Get final price using attributes + if (!string.IsNullOrEmpty(attributesXml)) + { + List scDiscounts; + decimal discountAmount; + minPossiblePrice = _priceCalculationService.GetUnitPrice(product, + _workContext.CurrentCustomer, + ShoppingCartType.ShoppingCart, + 1, ref attributesXml, 0, + null, null, + true, out discountAmount, out scDiscounts); + } decimal taxRate; decimal oldPriceBase = _taxService.GetProductPrice(product, product.OldPrice, out taxRate); decimal finalPriceBase = _taxService.GetProductPrice(product, minPossiblePrice, out taxRate); @@ -1192,7 +1227,7 @@ public virtual ProductDetailsModel PrepareProductDetailsModel(Product product, { model.ProductTags = PrepareProductTagModels(product); } - + //pictures model.DefaultPictureZoomEnabled = _mediaSettings.DefaultPictureZoomEnabled; var pictureModels = PrepareProductDetailsPictureModel(product, isAssociatedProduct); @@ -1233,7 +1268,8 @@ public virtual ProductDetailsModel PrepareProductDetailsModel(Product product, //product attributes model.ProductAttributes = PrepareProductAttributeModels(product, updatecartitem); - + + //product specifications //do not prepare this model for the associated products. anyway it's not used if (!isAssociatedProduct) @@ -1327,7 +1363,7 @@ public virtual ProductReviewsModel PrepareProductReviewsModel(ProductReviewsMode return model; } - + public virtual CustomerProductReviewsModel PrepareCustomerProductReviewsModel(int? page) { var pageSize = _catalogSettings.ProductReviewsPageSizeOnAccountPage; @@ -1447,7 +1483,6 @@ public virtual IList PrepareProductSpecificationModel }).ToList() ); } - #endregion } } From 0896dd67571b44e90560840ab01882df65ec2296 Mon Sep 17 00:00:00 2001 From: Markus Falk Date: Tue, 21 Feb 2017 23:28:25 +0100 Subject: [PATCH 04/13] VAT Rounding: Renamed Vat to Tax, several fixes (#1323) Purchase reward points (#1809) taxable reward points with several new settings - rewardpointssettings.earnedrewardpointsaretaxable Fixes on associated product tax. Taxation splits now on associated products also for excl. tax. Remade taxSummary --- .../Nop.Core/Domain/Catalog/Product.cs | 1431 ++-- .../Domain/Catalog/ProductEditorSettings.cs | 5 + .../Domain/Customers/RewardPointsHistory.cs | 129 +- .../Domain/Customers/RewardPointsSettings.cs | 148 +- src/Libraries/Nop.Core/Domain/Orders/Order.cs | 48 +- .../Nop.Core/Domain/Orders/OrderItem.cs | 277 +- .../Domain/Orders/ShoppingCartItem.cs | 324 +- .../Domain/Orders/ShoppingCartSettings.cs | 232 +- .../Customers/RewardPointsHistoryMap.cs | 49 +- .../Nop.Data/Mapping/Orders/OrderItemMap.cs | 62 +- .../Nop.Data/Mapping/Orders/OrderMap.cs | 113 +- .../Mapping/Orders/ShoppingCartItemMap.cs | 64 +- .../Catalog/CopyProductService.cs | 1273 ++-- .../Catalog/IPriceCalculationService.cs | 25 +- .../Catalog/IProductAttributeFormatter.cs | 4 +- .../Catalog/IProductAttributeParser.cs | 10 +- .../Catalog/PriceCalculationService.cs | 1598 ++-- .../Catalog/ProductAttributeFormatter.cs | 48 +- .../Catalog/ProductAttributeParser.cs | 16 +- .../Nop.Services/Catalog/RoundingHelper.cs | 13 + .../Nop.Services/Common/PdfService.cs | 2727 +++---- .../Nop.Services/Common/PdfService7.cs | 43 +- .../Customers/CustomerRegistrationService.cs | 928 +-- .../Nop.Services/Discounts/DiscountService.cs | 1459 ++-- .../ExportImport/ExportManager.cs | 41 +- .../ExportImport/ImportManager.cs | 35 +- .../CodeFirstInstallationService.cs | 78 +- .../Messages/MessageTokenProvider.cs | 2747 +++---- .../Nop.Services/Nop.Services.csproj | 1 + .../Nop.Services/Orders/GiftCardService.cs | 396 +- .../Orders/IOrderTotalCalculationService.cs | 433 +- .../Orders/IRewardPointService.cs | 59 +- .../Orders/OrderProcessingService.cs | 6525 +++++++++-------- .../Orders/OrderTotalCalculationService.cs | 592 +- .../Nop.Services/Orders/RewardPointService.cs | 141 +- .../Nop.Services/Orders/RewardPoints.cs | 83 + .../Orders/ShoppingCartExtensions.cs | 302 +- .../Orders/UpdateOrderParameters.cs | 2 +- src/Libraries/Nop.Services/Tax/ITaxService.cs | 553 +- src/Libraries/Nop.Services/Tax/TaxService.cs | 1783 ++--- src/Libraries/Nop.Services/Tax/TaxSummary.cs | 587 +- .../PayPalDirectPaymentProcessor.cs | 10 +- .../WidgetsGoogleAnalyticsController.cs | 526 +- .../Controllers/AffiliateController.cs | 837 +-- .../Controllers/CustomerController.cs | 4830 ++++++------ .../Controllers/OrderController.cs | 49 +- .../Controllers/SettingController.cs | 89 +- .../Controllers/ShippingController.cs | 2004 ++--- .../Mapper/AdminMapperConfiguration.cs | 6 +- .../Models/Catalog/ProductModel.cs | 2054 +++--- .../Models/Common/AddressModel.cs | 291 +- .../Models/Customers/CustomerModel.cs | 755 +- .../Models/Orders/OrderModel.cs | 1081 +-- .../Settings/ProductEditorSettingsModel.cs | 2 + .../Settings/RewardPointsSettingsModel.cs | 124 +- .../Views/Customer/_CreateOrUpdate.cshtml | 63 +- .../Order/AddProductToOrderDetails.cshtml | 298 +- .../Views/Order/_OrderDetails.Info.cshtml | 1955 ++--- .../Views/Order/_OrderDetails.Products.cshtml | 818 +-- .../Views/Product/_CreateOrUpdate.Info.cshtml | 48 + .../_ProductEditorSettingsModal.cshtml | 9 + .../Views/Setting/RewardPoints.cshtml | 60 + .../Install/Fast/create_required_data.sql | 2 + .../App_Data/Install/SqlServer.Indexes.sql | 6 + .../Localization/defaultResources.nopres.xml | 279 +- .../Nop.Web/Controllers/CheckoutController.cs | 3576 ++++----- .../Controllers/ShoppingCartController.cs | 3698 +++++----- .../Nop.Web/Factories/CheckoutModelFactory.cs | 53 +- .../Nop.Web/Factories/OrderModelFactory.cs | 57 +- .../Nop.Web/Factories/ProductModelFactory.cs | 111 +- .../Factories/ShoppingCartModelFactory.cs | 156 +- .../Cache/ModelCacheEventConsumer.cs | 4 +- .../Models/Catalog/ProductDetailsModel.cs | 618 +- .../Models/Order/CustomerRewardPointsModel.cs | 10 +- .../Nop.Web/Models/Order/OrderDetailsModel.cs | 294 +- .../ShoppingCart/MiniShoppingCartModel.cs | 100 +- .../Models/ShoppingCart/OrderTotalsModel.cs | 149 +- .../Models/ShoppingCart/ShoppingCartModel.cs | 407 +- .../Themes/DefaultClean/Content/css/print.css | 36 +- .../DefaultClean/Content/css/styles.css | 7 + .../Views/Order/CustomerRewardPoints.cshtml | 192 +- .../Nop.Web/Views/Order/Details.cshtml | 110 +- .../Product/ProductTemplate.Grouped.cshtml | 6 + .../Product/ProductTemplate.Simple.cshtml | 6 + .../Views/ShoppingCart/OrderSummary.cshtml | 17 +- .../Views/ShoppingCart/OrderTotals.cshtml | 485 +- .../Catalog/PriceCalculationServiceTests.cs | 1 + .../ExportImport/ExportManagerTests.cs | 527 +- .../Orders/OrderProcessingServiceTests.cs | 1292 ++-- .../OrderTotalCalculationServiceTests.cs | 2836 +++---- .../Nop.Services.Tests/Tax/TaxServiceTests.cs | 402 +- .../3.80-the next version/upgrade.sql | 508 +- 92 files changed, 29958 insertions(+), 27280 deletions(-) create mode 100644 src/Libraries/Nop.Services/Orders/RewardPoints.cs diff --git a/src/Libraries/Nop.Core/Domain/Catalog/Product.cs b/src/Libraries/Nop.Core/Domain/Catalog/Product.cs index 28f40b8c6f3..ed4950a259c 100644 --- a/src/Libraries/Nop.Core/Domain/Catalog/Product.cs +++ b/src/Libraries/Nop.Core/Domain/Catalog/Product.cs @@ -1,710 +1,723 @@ -using System; -using System.Collections.Generic; -using Nop.Core.Domain.Discounts; -using Nop.Core.Domain.Localization; -using Nop.Core.Domain.Security; -using Nop.Core.Domain.Seo; -using Nop.Core.Domain.Stores; - -namespace Nop.Core.Domain.Catalog -{ - /// - /// Represents a product - /// - public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAclSupported, IStoreMappingSupported - { - private ICollection _productCategories; - private ICollection _productManufacturers; - private ICollection _productPictures; - private ICollection _productReviews; - private ICollection _productSpecificationAttributes; - private ICollection _productTags; - private ICollection _productAttributeMappings; - private ICollection _productAttributeCombinations; - private ICollection _tierPrices; - private ICollection _appliedDiscounts; - private ICollection _productWarehouseInventory; - - - /// - /// Gets or sets the product type identifier - /// - public int ProductTypeId { get; set; } - /// - /// Gets or sets the parent product identifier. It's used to identify associated products (only with "grouped" products) - /// - public int ParentGroupedProductId { get; set; } - /// - /// Gets or sets the values indicating whether this product is visible in catalog or search results. - /// It's used when this product is associated to some "grouped" one - /// This way associated products could be accessed/added/etc only from a grouped product details page - /// - public bool VisibleIndividually { get; set; } - - /// - /// Gets or sets the name - /// - public string Name { get; set; } - /// - /// Gets or sets the short description - /// - public string ShortDescription { get; set; } - /// - /// Gets or sets the full description - /// - public string FullDescription { get; set; } - - /// - /// Gets or sets the admin comment - /// - public string AdminComment { get; set; } - - /// - /// Gets or sets a value of used product template identifier - /// - public int ProductTemplateId { get; set; } - - /// - /// Gets or sets a vendor identifier - /// - public int VendorId { get; set; } - - /// - /// Gets or sets a value indicating whether to show the product on home page - /// - public bool ShowOnHomePage { get; set; } - - /// - /// Gets or sets the meta keywords - /// - public string MetaKeywords { get; set; } - /// - /// Gets or sets the meta description - /// - public string MetaDescription { get; set; } - /// - /// Gets or sets the meta title - /// - public string MetaTitle { get; set; } - - /// - /// Gets or sets a value indicating whether the product allows customer reviews - /// - public bool AllowCustomerReviews { get; set; } - /// - /// Gets or sets the rating sum (approved reviews) - /// - public int ApprovedRatingSum { get; set; } - /// - /// Gets or sets the rating sum (not approved reviews) - /// - public int NotApprovedRatingSum { get; set; } - /// - /// Gets or sets the total rating votes (approved reviews) - /// - public int ApprovedTotalReviews { get; set; } - /// - /// Gets or sets the total rating votes (not approved reviews) - /// - public int NotApprovedTotalReviews { get; set; } - - /// - /// Gets or sets a value indicating whether the entity is subject to ACL - /// - public bool SubjectToAcl { get; set; } - /// - /// Gets or sets a value indicating whether the entity is limited/restricted to certain stores - /// - public bool LimitedToStores { get; set; } - - /// - /// Gets or sets the SKU - /// - public string Sku { get; set; } - /// - /// Gets or sets the manufacturer part number - /// - public string ManufacturerPartNumber { get; set; } - /// - /// 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). - /// - public string Gtin { get; set; } - - /// - /// Gets or sets a value indicating whether the product is gift card - /// - public bool IsGiftCard { get; set; } - /// - /// Gets or sets the gift card type identifier - /// - public int GiftCardTypeId { get; set; } - /// - /// Gets or sets gift card amount that can be used after purchase. If not specified, then product price will be used. - /// - public decimal? OverriddenGiftCardAmount { get; set; } - - /// - /// Gets or sets a value indicating whether the product requires that other products are added to the cart (Product X requires Product Y) - /// - public bool RequireOtherProducts { get; set; } - /// - /// Gets or sets a required product identifiers (comma separated) - /// - public string RequiredProductIds { get; set; } - /// - /// Gets or sets a value indicating whether required products are automatically added to the cart - /// - public bool AutomaticallyAddRequiredProducts { get; set; } - - /// - /// Gets or sets a value indicating whether the product is download - /// - public bool IsDownload { get; set; } - /// - /// Gets or sets the download identifier - /// - public int DownloadId { get; set; } - /// - /// Gets or sets a value indicating whether this downloadable product can be downloaded unlimited number of times - /// - public bool UnlimitedDownloads { get; set; } - /// - /// Gets or sets the maximum number of downloads - /// - public int MaxNumberOfDownloads { get; set; } - /// - /// Gets or sets the number of days during customers keeps access to the file. - /// - public int? DownloadExpirationDays { get; set; } - /// - /// Gets or sets the download activation type - /// - public int DownloadActivationTypeId { get; set; } - /// - /// Gets or sets a value indicating whether the product has a sample download file - /// - public bool HasSampleDownload { get; set; } - /// - /// Gets or sets the sample download identifier - /// - public int SampleDownloadId { get; set; } - /// - /// Gets or sets a value indicating whether the product has user agreement - /// - public bool HasUserAgreement { get; set; } - /// - /// Gets or sets the text of license agreement - /// - public string UserAgreementText { get; set; } - - /// - /// Gets or sets a value indicating whether the product is recurring - /// - public bool IsRecurring { get; set; } - /// - /// Gets or sets the cycle length - /// - public int RecurringCycleLength { get; set; } - /// - /// Gets or sets the cycle period - /// - public int RecurringCyclePeriodId { get; set; } - /// - /// Gets or sets the total cycles - /// - public int RecurringTotalCycles { get; set; } - - /// - /// Gets or sets a value indicating whether the product is rental - /// - public bool IsRental { get; set; } - /// - /// Gets or sets the rental length for some period (price for this period) - /// - public int RentalPriceLength { get; set; } - /// - /// Gets or sets the rental period (price for this period) - /// - public int RentalPricePeriodId { get; set; } - - /// - /// Gets or sets a value indicating whether the entity is ship enabled - /// - public bool IsShipEnabled { get; set; } - /// - /// Gets or sets a value indicating whether the entity is free shipping - /// - public bool IsFreeShipping { get; set; } - /// - /// Gets or sets a value this product should be shipped separately (each item) - /// - public bool ShipSeparately { get; set; } - /// - /// Gets or sets the additional shipping charge - /// - public decimal AdditionalShippingCharge { get; set; } - /// - /// Gets or sets a delivery date identifier - /// - public int DeliveryDateId { get; set; } - - /// - /// Gets or sets a value indicating whether the product is marked as tax exempt - /// - public bool IsTaxExempt { get; set; } - /// - /// Gets or sets the tax category identifier - /// - public int TaxCategoryId { get; set; } - /// - /// Gets or sets a value indicating whether the product is telecommunications or broadcasting or electronic services - /// - public bool IsTelecommunicationsOrBroadcastingOrElectronicServices { get; set; } - - /// - /// Gets or sets a value indicating how to manage inventory - /// - public int ManageInventoryMethodId { get; set; } - /// - /// Gets or sets a product availability range identifier - /// - public int ProductAvailabilityRangeId { get; set; } - /// - /// Gets or sets a value indicating whether multiple warehouses are used for this product - /// - public bool UseMultipleWarehouses { get; set; } - /// - /// Gets or sets a warehouse identifier - /// - public int WarehouseId { get; set; } - /// - /// Gets or sets the stock quantity - /// - public int StockQuantity { get; set; } - /// - /// Gets or sets a value indicating whether to display stock availability - /// - public bool DisplayStockAvailability { get; set; } - /// - /// Gets or sets a value indicating whether to display stock quantity - /// - public bool DisplayStockQuantity { get; set; } - /// - /// Gets or sets the minimum stock quantity - /// - public int MinStockQuantity { get; set; } - /// - /// Gets or sets the low stock activity identifier - /// - public int LowStockActivityId { get; set; } - /// - /// Gets or sets the quantity when admin should be notified - /// - public int NotifyAdminForQuantityBelow { get; set; } - /// - /// Gets or sets a value backorder mode identifier - /// - public int BackorderModeId { get; set; } - /// - /// Gets or sets a value indicating whether to back in stock subscriptions are allowed - /// - public bool AllowBackInStockSubscriptions { get; set; } - /// - /// Gets or sets the order minimum quantity - /// - public int OrderMinimumQuantity { get; set; } - /// - /// Gets or sets the order maximum quantity - /// - public int OrderMaximumQuantity { get; set; } - /// - /// Gets or sets the comma seperated list of allowed quantities. null or empty if any quantity is allowed - /// - public string AllowedQuantities { get; set; } - /// - /// Gets or sets a value indicating whether we allow adding to the cart/wishlist only attribute combinations that exist and have stock greater than zero. - /// This option is used only when we have "manage inventory" set to "track inventory by product attributes" - /// - public bool AllowAddingOnlyExistingAttributeCombinations { get; set; } - /// - /// Gets or sets a value indicating whether this product is returnable (a customer is allowed to submit return request with this product) - /// - public bool NotReturnable { get; set; } - - /// - /// Gets or sets a value indicating whether to disable buy (Add to cart) button - /// - public bool DisableBuyButton { get; set; } - /// - /// Gets or sets a value indicating whether to disable "Add to wishlist" button - /// - public bool DisableWishlistButton { get; set; } - /// - /// Gets or sets a value indicating whether this item is available for Pre-Order - /// - public bool AvailableForPreOrder { get; set; } - /// - /// Gets or sets the start date and time of the product availability (for pre-order products) - /// - public DateTime? PreOrderAvailabilityStartDateTimeUtc { get; set; } - /// - /// Gets or sets a value indicating whether to show "Call for Pricing" or "Call for quote" instead of price - /// - public bool CallForPrice { get; set; } - /// - /// Gets or sets the price - /// - public decimal Price { get; set; } - /// - /// Gets or sets the old price - /// - public decimal OldPrice { get; set; } - /// - /// Gets or sets the product cost - /// - public decimal ProductCost { get; set; } - /// - /// Gets or sets a value indicating whether a customer enters price - /// - public bool CustomerEntersPrice { get; set; } - /// - /// Gets or sets the minimum price entered by a customer - /// - public decimal MinimumCustomerEnteredPrice { get; set; } - /// - /// Gets or sets the maximum price entered by a customer - /// - public decimal MaximumCustomerEnteredPrice { get; set; } - - /// - /// Gets or sets a value indicating whether base price (PAngV) is enabled. Used by German users. - /// - public bool BasepriceEnabled { get; set; } - /// - /// Gets or sets an amount in product for PAngV - /// - public decimal BasepriceAmount { get; set; } - /// - /// Gets or sets a unit of product for PAngV (MeasureWeight entity) - /// - public int BasepriceUnitId { get; set; } - /// - /// Gets or sets a reference amount for PAngV - /// - public decimal BasepriceBaseAmount { get; set; } - /// - /// Gets or sets a reference unit for PAngV (MeasureWeight entity) - /// - public int BasepriceBaseUnitId { get; set; } - - - /// - /// Gets or sets a value indicating whether this product is marked as new - /// - public bool MarkAsNew { get; set; } - /// - /// Gets or sets the start date and time of the new product (set product as "New" from date). Leave empty to ignore this property - /// - public DateTime? MarkAsNewStartDateTimeUtc { get; set; } - /// - /// Gets or sets the end date and time of the new product (set product as "New" to date). Leave empty to ignore this property - /// - public DateTime? MarkAsNewEndDateTimeUtc { get; set; } - - /// - /// 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 navigation property - /// - /// - public bool HasTierPrices { get; set; } - /// - /// Gets or sets a value indicating whether this product has discounts applied - /// The same as if we run this.AppliedDiscounts.Count > 0 - /// We use this property for performance optimization: - /// if this property is set to false, then we do not need to load Applied Discounts navigation property - /// - /// - public bool HasDiscountsApplied { get; set; } - - /// - /// Gets or sets the weight - /// - public decimal Weight { get; set; } - /// - /// Gets or sets the length - /// - public decimal Length { get; set; } - /// - /// Gets or sets the width - /// - public decimal Width { get; set; } - /// - /// Gets or sets the height - /// - public decimal Height { get; set; } - - /// - /// Gets or sets the available start date and time - /// - public DateTime? AvailableStartDateTimeUtc { get; set; } - /// - /// Gets or sets the available end date and time - /// - public DateTime? AvailableEndDateTimeUtc { get; set; } - - /// - /// Gets or sets a display order. - /// This value is used when sorting associated products (used with "grouped" products) - /// This value is used when sorting home page products - /// - public int DisplayOrder { get; set; } - /// - /// Gets or sets a value indicating whether the entity is published - /// - public bool Published { get; set; } - /// - /// Gets or sets a value indicating whether the entity has been deleted - /// - public bool Deleted { get; set; } - - /// - /// Gets or sets the date and time of product creation - /// - public DateTime CreatedOnUtc { get; set; } - /// - /// Gets or sets the date and time of product update - /// - public DateTime UpdatedOnUtc { get; set; } - - - - - - - /// - /// Gets or sets the product type - /// - public ProductType ProductType - { - get - { - return (ProductType)this.ProductTypeId; - } - set - { - this.ProductTypeId = (int)value; - } - } - - /// - /// Gets or sets the backorder mode - /// - public BackorderMode BackorderMode - { - get - { - return (BackorderMode)this.BackorderModeId; - } - set - { - this.BackorderModeId = (int)value; - } - } - - /// - /// Gets or sets the download activation type - /// - public DownloadActivationType DownloadActivationType - { - get - { - return (DownloadActivationType)this.DownloadActivationTypeId; - } - set - { - this.DownloadActivationTypeId = (int)value; - } - } - - /// - /// Gets or sets the gift card type - /// - public GiftCardType GiftCardType - { - get - { - return (GiftCardType)this.GiftCardTypeId; - } - set - { - this.GiftCardTypeId = (int)value; - } - } - - /// - /// Gets or sets the low stock activity - /// - public LowStockActivity LowStockActivity - { - get - { - return (LowStockActivity)this.LowStockActivityId; - } - set - { - this.LowStockActivityId = (int)value; - } - } - - /// - /// Gets or sets the value indicating how to manage inventory - /// - public ManageInventoryMethod ManageInventoryMethod - { - get - { - return (ManageInventoryMethod)this.ManageInventoryMethodId; - } - set - { - this.ManageInventoryMethodId = (int)value; - } - } - - /// - /// Gets or sets the cycle period for recurring products - /// - public RecurringProductCyclePeriod RecurringCyclePeriod - { - get - { - return (RecurringProductCyclePeriod)this.RecurringCyclePeriodId; - } - set - { - this.RecurringCyclePeriodId = (int)value; - } - } - - /// - /// Gets or sets the period for rental products - /// - public RentalPricePeriod RentalPricePeriod - { - get - { - return (RentalPricePeriod)this.RentalPricePeriodId; - } - set - { - this.RentalPricePeriodId = (int)value; - } - } - - - - - - - /// - /// Gets or sets the collection of ProductCategory - /// - public virtual ICollection ProductCategories - { - get { return _productCategories ?? (_productCategories = new List()); } - protected set { _productCategories = value; } - } - - /// - /// Gets or sets the collection of ProductManufacturer - /// - public virtual ICollection ProductManufacturers - { - get { return _productManufacturers ?? (_productManufacturers = new List()); } - protected set { _productManufacturers = value; } - } - - /// - /// Gets or sets the collection of ProductPicture - /// - public virtual ICollection ProductPictures - { - get { return _productPictures ?? (_productPictures = new List()); } - protected set { _productPictures = value; } - } - - /// - /// Gets or sets the collection of product reviews - /// - public virtual ICollection ProductReviews - { - get { return _productReviews ?? (_productReviews = new List()); } - protected set { _productReviews = value; } - } - - /// - /// Gets or sets the product specification attribute - /// - public virtual ICollection ProductSpecificationAttributes - { - get { return _productSpecificationAttributes ?? (_productSpecificationAttributes = new List()); } - protected set { _productSpecificationAttributes = value; } - } - - /// - /// Gets or sets the product tags - /// - public virtual ICollection ProductTags - { - get { return _productTags ?? (_productTags = new List()); } - protected set { _productTags = value; } - } - - /// - /// Gets or sets the product attribute mappings - /// - public virtual ICollection ProductAttributeMappings - { - get { return _productAttributeMappings ?? (_productAttributeMappings = new List()); } - protected set { _productAttributeMappings = value; } - } - - /// - /// Gets or sets the product attribute combinations - /// - public virtual ICollection ProductAttributeCombinations - { - get { return _productAttributeCombinations ?? (_productAttributeCombinations = new List()); } - protected set { _productAttributeCombinations = value; } - } - - /// - /// Gets or sets the tier prices - /// - public virtual ICollection TierPrices - { - get { return _tierPrices ?? (_tierPrices = new List()); } - protected set { _tierPrices = value; } - } - - /// - /// Gets or sets the collection of applied discounts - /// - public virtual ICollection AppliedDiscounts - { - get { return _appliedDiscounts ?? (_appliedDiscounts = new List()); } - protected set { _appliedDiscounts = value; } - } - - /// - /// Gets or sets the collection of "ProductWarehouseInventory" records. We use it only when "UseMultipleWarehouses" is set to "true" and ManageInventoryMethod" to "ManageStock" - /// - public virtual ICollection ProductWarehouseInventory - { - get { return _productWarehouseInventory ?? (_productWarehouseInventory = new List()); } - protected set { _productWarehouseInventory = value; } - } - } +using System; +using System.Collections.Generic; +using Nop.Core.Domain.Discounts; +using Nop.Core.Domain.Localization; +using Nop.Core.Domain.Security; +using Nop.Core.Domain.Seo; +using Nop.Core.Domain.Stores; + +namespace Nop.Core.Domain.Catalog +{ + /// + /// Represents a product + /// + public partial class Product : BaseEntity, ILocalizedEntity, ISlugSupported, IAclSupported, IStoreMappingSupported + { + private ICollection _productCategories; + private ICollection _productManufacturers; + private ICollection _productPictures; + private ICollection _productReviews; + private ICollection _productSpecificationAttributes; + private ICollection _productTags; + private ICollection _productAttributeMappings; + private ICollection _productAttributeCombinations; + private ICollection _tierPrices; + private ICollection _appliedDiscounts; + private ICollection _productWarehouseInventory; + + + /// + /// Gets or sets the product type identifier + /// + public int ProductTypeId { get; set; } + /// + /// Gets or sets the parent product identifier. It's used to identify associated products (only with "grouped" products) + /// + public int ParentGroupedProductId { get; set; } + /// + /// Gets or sets the values indicating whether this product is visible in catalog or search results. + /// It's used when this product is associated to some "grouped" one + /// This way associated products could be accessed/added/etc only from a grouped product details page + /// + public bool VisibleIndividually { get; set; } + + /// + /// Gets or sets the name + /// + public string Name { get; set; } + /// + /// Gets or sets the short description + /// + public string ShortDescription { get; set; } + /// + /// Gets or sets the full description + /// + public string FullDescription { get; set; } + + /// + /// Gets or sets the admin comment + /// + public string AdminComment { get; set; } + + /// + /// Gets or sets a value of used product template identifier + /// + public int ProductTemplateId { get; set; } + + /// + /// Gets or sets a vendor identifier + /// + public int VendorId { get; set; } + + /// + /// Gets or sets a value indicating whether to show the product on home page + /// + public bool ShowOnHomePage { get; set; } + + /// + /// Gets or sets the meta keywords + /// + public string MetaKeywords { get; set; } + /// + /// Gets or sets the meta description + /// + public string MetaDescription { get; set; } + /// + /// Gets or sets the meta title + /// + public string MetaTitle { get; set; } + + /// + /// Gets or sets a value indicating whether the product allows customer reviews + /// + public bool AllowCustomerReviews { get; set; } + /// + /// Gets or sets the rating sum (approved reviews) + /// + public int ApprovedRatingSum { get; set; } + /// + /// Gets or sets the rating sum (not approved reviews) + /// + public int NotApprovedRatingSum { get; set; } + /// + /// Gets or sets the total rating votes (approved reviews) + /// + public int ApprovedTotalReviews { get; set; } + /// + /// Gets or sets the total rating votes (not approved reviews) + /// + public int NotApprovedTotalReviews { get; set; } + + /// + /// Gets or sets a value indicating whether the entity is subject to ACL + /// + public bool SubjectToAcl { get; set; } + /// + /// Gets or sets a value indicating whether the entity is limited/restricted to certain stores + /// + public bool LimitedToStores { get; set; } + + /// + /// Gets or sets the SKU + /// + public string Sku { get; set; } + /// + /// Gets or sets the manufacturer part number + /// + public string ManufacturerPartNumber { get; set; } + /// + /// 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). + /// + public string Gtin { get; set; } + + /// + /// Gets or sets a value indicating whether the product is gift card + /// + public bool IsGiftCard { get; set; } + /// + /// Gets or sets the gift card type identifier + /// + public int GiftCardTypeId { get; set; } + /// + /// Gets or sets gift card amount that can be used after purchase. If not specified, then product price will be used. + /// + public decimal? OverriddenGiftCardAmount { get; set; } + + /// + /// Gets or sets a value indicating whether the product are reward points + /// + public bool IsRewardPoints { get; set; } + /// + /// Gets or sets a value of Reward Points exchange rate when purchased + /// + public decimal? OverriddenRPExchangeRate { get; set; } + + /// + /// Gets or sets a value indicating whether the product is excluded from reward points calculation + /// + public bool ExcludeFromRewardPoints { get; set; } + /// + /// Gets or sets a value indicating whether the product requires that other products are added to the cart (Product X requires Product Y) + /// + public bool RequireOtherProducts { get; set; } + /// + /// Gets or sets a required product identifiers (comma separated) + /// + public string RequiredProductIds { get; set; } + /// + /// Gets or sets a value indicating whether required products are automatically added to the cart + /// + public bool AutomaticallyAddRequiredProducts { get; set; } + + /// + /// Gets or sets a value indicating whether the product is download + /// + public bool IsDownload { get; set; } + /// + /// Gets or sets the download identifier + /// + public int DownloadId { get; set; } + /// + /// Gets or sets a value indicating whether this downloadable product can be downloaded unlimited number of times + /// + public bool UnlimitedDownloads { get; set; } + /// + /// Gets or sets the maximum number of downloads + /// + public int MaxNumberOfDownloads { get; set; } + /// + /// Gets or sets the number of days during customers keeps access to the file. + /// + public int? DownloadExpirationDays { get; set; } + /// + /// Gets or sets the download activation type + /// + public int DownloadActivationTypeId { get; set; } + /// + /// Gets or sets a value indicating whether the product has a sample download file + /// + public bool HasSampleDownload { get; set; } + /// + /// Gets or sets the sample download identifier + /// + public int SampleDownloadId { get; set; } + /// + /// Gets or sets a value indicating whether the product has user agreement + /// + public bool HasUserAgreement { get; set; } + /// + /// Gets or sets the text of license agreement + /// + public string UserAgreementText { get; set; } + + /// + /// Gets or sets a value indicating whether the product is recurring + /// + public bool IsRecurring { get; set; } + /// + /// Gets or sets the cycle length + /// + public int RecurringCycleLength { get; set; } + /// + /// Gets or sets the cycle period + /// + public int RecurringCyclePeriodId { get; set; } + /// + /// Gets or sets the total cycles + /// + public int RecurringTotalCycles { get; set; } + + /// + /// Gets or sets a value indicating whether the product is rental + /// + public bool IsRental { get; set; } + /// + /// Gets or sets the rental length for some period (price for this period) + /// + public int RentalPriceLength { get; set; } + /// + /// Gets or sets the rental period (price for this period) + /// + public int RentalPricePeriodId { get; set; } + + /// + /// Gets or sets a value indicating whether the entity is ship enabled + /// + public bool IsShipEnabled { get; set; } + /// + /// Gets or sets a value indicating whether the entity is free shipping + /// + public bool IsFreeShipping { get; set; } + /// + /// Gets or sets a value this product should be shipped separately (each item) + /// + public bool ShipSeparately { get; set; } + /// + /// Gets or sets the additional shipping charge + /// + public decimal AdditionalShippingCharge { get; set; } + /// + /// Gets or sets a delivery date identifier + /// + public int DeliveryDateId { get; set; } + + /// + /// Gets or sets a value indicating whether the product is marked as tax exempt + /// + public bool IsTaxExempt { get; set; } + /// + /// Gets or sets the tax category identifier + /// + public int TaxCategoryId { get; set; } + /// + /// Gets or sets a value indicating whether the product is telecommunications or broadcasting or electronic services + /// + public bool IsTelecommunicationsOrBroadcastingOrElectronicServices { get; set; } + + /// + /// Gets or sets a value indicating how to manage inventory + /// + public int ManageInventoryMethodId { get; set; } + /// + /// Gets or sets a product availability range identifier + /// + public int ProductAvailabilityRangeId { get; set; } + /// + /// Gets or sets a value indicating whether multiple warehouses are used for this product + /// + public bool UseMultipleWarehouses { get; set; } + /// + /// Gets or sets a warehouse identifier + /// + public int WarehouseId { get; set; } + /// + /// Gets or sets the stock quantity + /// + public int StockQuantity { get; set; } + /// + /// Gets or sets a value indicating whether to display stock availability + /// + public bool DisplayStockAvailability { get; set; } + /// + /// Gets or sets a value indicating whether to display stock quantity + /// + public bool DisplayStockQuantity { get; set; } + /// + /// Gets or sets the minimum stock quantity + /// + public int MinStockQuantity { get; set; } + /// + /// Gets or sets the low stock activity identifier + /// + public int LowStockActivityId { get; set; } + /// + /// Gets or sets the quantity when admin should be notified + /// + public int NotifyAdminForQuantityBelow { get; set; } + /// + /// Gets or sets a value backorder mode identifier + /// + public int BackorderModeId { get; set; } + /// + /// Gets or sets a value indicating whether to back in stock subscriptions are allowed + /// + public bool AllowBackInStockSubscriptions { get; set; } + /// + /// Gets or sets the order minimum quantity + /// + public int OrderMinimumQuantity { get; set; } + /// + /// Gets or sets the order maximum quantity + /// + public int OrderMaximumQuantity { get; set; } + /// + /// Gets or sets the comma seperated list of allowed quantities. null or empty if any quantity is allowed + /// + public string AllowedQuantities { get; set; } + /// + /// Gets or sets a value indicating whether we allow adding to the cart/wishlist only attribute combinations that exist and have stock greater than zero. + /// This option is used only when we have "manage inventory" set to "track inventory by product attributes" + /// + public bool AllowAddingOnlyExistingAttributeCombinations { get; set; } + /// + /// Gets or sets a value indicating whether this product is returnable (a customer is allowed to submit return request with this product) + /// + public bool NotReturnable { get; set; } + + /// + /// Gets or sets a value indicating whether to disable buy (Add to cart) button + /// + public bool DisableBuyButton { get; set; } + /// + /// Gets or sets a value indicating whether to disable "Add to wishlist" button + /// + public bool DisableWishlistButton { get; set; } + /// + /// Gets or sets a value indicating whether this item is available for Pre-Order + /// + public bool AvailableForPreOrder { get; set; } + /// + /// Gets or sets the start date and time of the product availability (for pre-order products) + /// + public DateTime? PreOrderAvailabilityStartDateTimeUtc { get; set; } + /// + /// Gets or sets a value indicating whether to show "Call for Pricing" or "Call for quote" instead of price + /// + public bool CallForPrice { get; set; } + /// + /// Gets or sets the price + /// + public decimal Price { get; set; } + /// + /// Gets or sets the old price + /// + public decimal OldPrice { get; set; } + /// + /// Gets or sets the product cost + /// + public decimal ProductCost { get; set; } + /// + /// Gets or sets a value indicating whether a customer enters price + /// + public bool CustomerEntersPrice { get; set; } + /// + /// Gets or sets the minimum price entered by a customer + /// + public decimal MinimumCustomerEnteredPrice { get; set; } + /// + /// Gets or sets the maximum price entered by a customer + /// + public decimal MaximumCustomerEnteredPrice { get; set; } + + /// + /// Gets or sets a value indicating whether base price (PAngV) is enabled. Used by German users. + /// + public bool BasepriceEnabled { get; set; } + /// + /// Gets or sets an amount in product for PAngV + /// + public decimal BasepriceAmount { get; set; } + /// + /// Gets or sets a unit of product for PAngV (MeasureWeight entity) + /// + public int BasepriceUnitId { get; set; } + /// + /// Gets or sets a reference amount for PAngV + /// + public decimal BasepriceBaseAmount { get; set; } + /// + /// Gets or sets a reference unit for PAngV (MeasureWeight entity) + /// + public int BasepriceBaseUnitId { get; set; } + + + /// + /// Gets or sets a value indicating whether this product is marked as new + /// + public bool MarkAsNew { get; set; } + /// + /// Gets or sets the start date and time of the new product (set product as "New" from date). Leave empty to ignore this property + /// + public DateTime? MarkAsNewStartDateTimeUtc { get; set; } + /// + /// Gets or sets the end date and time of the new product (set product as "New" to date). Leave empty to ignore this property + /// + public DateTime? MarkAsNewEndDateTimeUtc { get; set; } + + /// + /// 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 navigation property + /// + /// + public bool HasTierPrices { get; set; } + /// + /// Gets or sets a value indicating whether this product has discounts applied + /// The same as if we run this.AppliedDiscounts.Count > 0 + /// We use this property for performance optimization: + /// if this property is set to false, then we do not need to load Applied Discounts navigation property + /// + /// + public bool HasDiscountsApplied { get; set; } + + /// + /// Gets or sets the weight + /// + public decimal Weight { get; set; } + /// + /// Gets or sets the length + /// + public decimal Length { get; set; } + /// + /// Gets or sets the width + /// + public decimal Width { get; set; } + /// + /// Gets or sets the height + /// + public decimal Height { get; set; } + + /// + /// Gets or sets the available start date and time + /// + public DateTime? AvailableStartDateTimeUtc { get; set; } + /// + /// Gets or sets the available end date and time + /// + public DateTime? AvailableEndDateTimeUtc { get; set; } + + /// + /// Gets or sets a display order. + /// This value is used when sorting associated products (used with "grouped" products) + /// This value is used when sorting home page products + /// + public int DisplayOrder { get; set; } + /// + /// Gets or sets a value indicating whether the entity is published + /// + public bool Published { get; set; } + /// + /// Gets or sets a value indicating whether the entity has been deleted + /// + public bool Deleted { get; set; } + + /// + /// Gets or sets the date and time of product creation + /// + public DateTime CreatedOnUtc { get; set; } + /// + /// Gets or sets the date and time of product update + /// + public DateTime UpdatedOnUtc { get; set; } + + + + + + + /// + /// Gets or sets the product type + /// + public ProductType ProductType + { + get + { + return (ProductType)this.ProductTypeId; + } + set + { + this.ProductTypeId = (int)value; + } + } + + /// + /// Gets or sets the backorder mode + /// + public BackorderMode BackorderMode + { + get + { + return (BackorderMode)this.BackorderModeId; + } + set + { + this.BackorderModeId = (int)value; + } + } + + /// + /// Gets or sets the download activation type + /// + public DownloadActivationType DownloadActivationType + { + get + { + return (DownloadActivationType)this.DownloadActivationTypeId; + } + set + { + this.DownloadActivationTypeId = (int)value; + } + } + + /// + /// Gets or sets the gift card type + /// + public GiftCardType GiftCardType + { + get + { + return (GiftCardType)this.GiftCardTypeId; + } + set + { + this.GiftCardTypeId = (int)value; + } + } + + /// + /// Gets or sets the low stock activity + /// + public LowStockActivity LowStockActivity + { + get + { + return (LowStockActivity)this.LowStockActivityId; + } + set + { + this.LowStockActivityId = (int)value; + } + } + + /// + /// Gets or sets the value indicating how to manage inventory + /// + public ManageInventoryMethod ManageInventoryMethod + { + get + { + return (ManageInventoryMethod)this.ManageInventoryMethodId; + } + set + { + this.ManageInventoryMethodId = (int)value; + } + } + + /// + /// Gets or sets the cycle period for recurring products + /// + public RecurringProductCyclePeriod RecurringCyclePeriod + { + get + { + return (RecurringProductCyclePeriod)this.RecurringCyclePeriodId; + } + set + { + this.RecurringCyclePeriodId = (int)value; + } + } + + /// + /// Gets or sets the period for rental products + /// + public RentalPricePeriod RentalPricePeriod + { + get + { + return (RentalPricePeriod)this.RentalPricePeriodId; + } + set + { + this.RentalPricePeriodId = (int)value; + } + } + + + + + + + /// + /// Gets or sets the collection of ProductCategory + /// + public virtual ICollection ProductCategories + { + get { return _productCategories ?? (_productCategories = new List()); } + protected set { _productCategories = value; } + } + + /// + /// Gets or sets the collection of ProductManufacturer + /// + public virtual ICollection ProductManufacturers + { + get { return _productManufacturers ?? (_productManufacturers = new List()); } + protected set { _productManufacturers = value; } + } + + /// + /// Gets or sets the collection of ProductPicture + /// + public virtual ICollection ProductPictures + { + get { return _productPictures ?? (_productPictures = new List()); } + protected set { _productPictures = value; } + } + + /// + /// Gets or sets the collection of product reviews + /// + public virtual ICollection ProductReviews + { + get { return _productReviews ?? (_productReviews = new List()); } + protected set { _productReviews = value; } + } + + /// + /// Gets or sets the product specification attribute + /// + public virtual ICollection ProductSpecificationAttributes + { + get { return _productSpecificationAttributes ?? (_productSpecificationAttributes = new List()); } + protected set { _productSpecificationAttributes = value; } + } + + /// + /// Gets or sets the product tags + /// + public virtual ICollection ProductTags + { + get { return _productTags ?? (_productTags = new List()); } + protected set { _productTags = value; } + } + + /// + /// Gets or sets the product attribute mappings + /// + public virtual ICollection ProductAttributeMappings + { + get { return _productAttributeMappings ?? (_productAttributeMappings = new List()); } + protected set { _productAttributeMappings = value; } + } + + /// + /// Gets or sets the product attribute combinations + /// + public virtual ICollection ProductAttributeCombinations + { + get { return _productAttributeCombinations ?? (_productAttributeCombinations = new List()); } + protected set { _productAttributeCombinations = value; } + } + + /// + /// Gets or sets the tier prices + /// + public virtual ICollection TierPrices + { + get { return _tierPrices ?? (_tierPrices = new List()); } + protected set { _tierPrices = value; } + } + + /// + /// Gets or sets the collection of applied discounts + /// + public virtual ICollection AppliedDiscounts + { + get { return _appliedDiscounts ?? (_appliedDiscounts = new List()); } + protected set { _appliedDiscounts = value; } + } + + /// + /// Gets or sets the collection of "ProductWarehouseInventory" records. We use it only when "UseMultipleWarehouses" is set to "true" and ManageInventoryMethod" to "ManageStock" + /// + public virtual ICollection ProductWarehouseInventory + { + get { return _productWarehouseInventory ?? (_productWarehouseInventory = new List()); } + protected set { _productWarehouseInventory = value; } + } + } } \ No newline at end of file diff --git a/src/Libraries/Nop.Core/Domain/Catalog/ProductEditorSettings.cs b/src/Libraries/Nop.Core/Domain/Catalog/ProductEditorSettings.cs index 4a947873850..3967630f131 100644 --- a/src/Libraries/Nop.Core/Domain/Catalog/ProductEditorSettings.cs +++ b/src/Libraries/Nop.Core/Domain/Catalog/ProductEditorSettings.cs @@ -135,9 +135,14 @@ public class ProductEditorSettings : ISettings /// public bool IsGiftCard { get; set; } + /// + /// Gets or sets a value indicating whether 'reward points' section is shown + /// + public bool IsRewardPoints { get; set; } /// /// Gets or sets a value indicating whether 'Downloadable product' field is shown /// + public bool DownloadableProduct { get; set; } /// diff --git a/src/Libraries/Nop.Core/Domain/Customers/RewardPointsHistory.cs b/src/Libraries/Nop.Core/Domain/Customers/RewardPointsHistory.cs index d8ffd332d95..a9c815b1791 100644 --- a/src/Libraries/Nop.Core/Domain/Customers/RewardPointsHistory.cs +++ b/src/Libraries/Nop.Core/Domain/Customers/RewardPointsHistory.cs @@ -1,56 +1,73 @@ -using System; -using Nop.Core.Domain.Orders; - -namespace Nop.Core.Domain.Customers -{ - /// - /// Represents a reward point history entry - /// - public partial class RewardPointsHistory : BaseEntity - { - /// - /// Gets or sets the customer identifier - /// - public int CustomerId { get; set; } - - /// - /// Gets or sets the store identifier in which these reward points were awarded or redeemed - /// - public int StoreId { get; set; } - - /// - /// Gets or sets the points redeemed/added - /// - public int Points { get; set; } - - /// - /// Gets or sets the points balance - /// - public int? PointsBalance { get; set; } - - /// - /// Gets or sets the used amount - /// - public decimal UsedAmount { get; set; } - - /// - /// Gets or sets the message - /// - public string Message { get; set; } - - /// - /// Gets or sets the date and time of instance creation - /// - public DateTime CreatedOnUtc { get; set; } - - /// - /// Gets or sets the order for which points were redeemed as a payment (spent by a customer when placing this order) - /// - public virtual Order UsedWithOrder { get; set; } - - /// - /// Gets or sets the customer - /// - public virtual Customer Customer { get; set; } - } -} +using System; +using Nop.Core.Domain.Orders; + +namespace Nop.Core.Domain.Customers +{ + /// + /// Represents a reward point history entry + /// + public partial class RewardPointsHistory : BaseEntity + { + /// + /// Gets or sets the customer identifier + /// + public int CustomerId { get; set; } + + /// + /// Gets or sets the store identifier in which these reward points were awarded or redeemed + /// + public int StoreId { get; set; } + + /// + /// Gets or sets the points redeemed/added + /// + public int Points { get; set; } + /// + /// Gets or sets the purchased points redeemed/added + /// + public int PointsPurchased { get; set; } + /// + /// Gets or sets the points balance + /// + public int? PointsBalance { get; set; } + /// + /// Gets or sets the purchased points balance + /// + public int? PointsBalancePurchased { get; set; } + /// + /// Gets or sets the used amount. Opposite sign as respective points! + /// + public decimal UsedAmount { get; set; } + /// + /// Gets or sets the used purchased amount. Opposite sign as respective points! + /// + public decimal UsedAmountPurchased { get; set; } + /// + /// Gets or sets the message + /// + public string Message { get; set; } + + /// + /// Gets or sets the date and time of instance creation + /// + public DateTime CreatedOnUtc { get; set; } + + /// + /// Gets or sets the order for which points were redeemed as a payment (spent by a customer when placing this order) + /// + public virtual Order UsedWithOrder { get; set; } + + /// + /// Gets or sets the customer + /// + public virtual Customer Customer { get; set; } + /// + /// Gets or sets the associated order item identifier + /// + public int? PurchasedWithOrderItemId { get; set; } + /// + /// Gets or sets the associated order item + /// + public virtual OrderItem PurchasedWithOrderItem { get; set; } + } +} diff --git a/src/Libraries/Nop.Core/Domain/Customers/RewardPointsSettings.cs b/src/Libraries/Nop.Core/Domain/Customers/RewardPointsSettings.cs index e84e087ba8f..f2cdc1ccfc9 100644 --- a/src/Libraries/Nop.Core/Domain/Customers/RewardPointsSettings.cs +++ b/src/Libraries/Nop.Core/Domain/Customers/RewardPointsSettings.cs @@ -1,62 +1,88 @@ -using Nop.Core.Configuration; - -namespace Nop.Core.Domain.Customers -{ - public class RewardPointsSettings : ISettings - { - /// - /// Gets or sets a value indicating whether Reward Points Program is enabled - /// - public bool Enabled { get; set; } - - /// - /// Gets or sets a value of Reward Points exchange rate - /// - public decimal ExchangeRate { get; set; } - - /// - /// Gets or sets the minimum reward points to use - /// - public int MinimumRewardPointsToUse { 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 purchases (amount in primary store currency) - /// - public decimal PointsForPurchases_Amount { get; set; } - - /// - /// Gets or sets a number of points awarded for purchases - /// - public int PointsForPurchases_Points { get; set; } - - /// - /// Gets or sets a delay before activation points - /// - public int ActivationDelay { get; set; } - - /// - /// Gets or sets the period of activation delay - /// - public int ActivationDelayPeriodId { get; set; } - - /// - /// Gets or sets a value indicating whether "You will earn" message should be displayed - /// - public bool DisplayHowMuchWillBeEarned { get; set; } - - /// - /// Gets or sets a value indicating whether all reward points are accumulated in one balance for all stores and they can be used in any store. Otherwise, each store has its own rewards points and they can only be used in that store. - /// - public bool PointsAccumulatedForAllStores { get; set; } - - /// - /// Gets or sets the page size is for history of reward points on my account page - /// - public int PageSize { get; set; } - } +using Nop.Core.Configuration; + +namespace Nop.Core.Domain.Customers +{ + public class RewardPointsSettings : ISettings + { + /// + /// Gets or sets a value indicating whether Reward Points Program is enabled + /// + public bool Enabled { get; set; } + + /// + /// Gets or sets a value of Reward Points exchange rate + /// + public decimal ExchangeRate { get; set; } + + /// + /// Gets or sets the minimum reward points to use + /// + public int MinimumRewardPointsToUse { 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 purchases (amount in primary store currency) + /// + public decimal PointsForPurchases_Amount { get; set; } + + /// + /// Gets or sets a number of points awarded for purchases + /// + public int PointsForPurchases_Points { get; set; } + + /// + /// Gets or sets a delay before activation points + /// + public int ActivationDelay { get; set; } + + /// + /// Gets or sets the period of activation delay + /// + public int ActivationDelayPeriodId { get; set; } + + /// + /// Gets or sets a value indicating whether "You will earn" message should be displayed + /// + public bool DisplayHowMuchWillBeEarned { get; set; } + + /// + /// Gets or sets a value indicating whether all reward points are accumulated in one balance for all stores and they can be used in any store. Otherwise, each store has its own rewards points and they can only be used in that store. + /// + public bool PointsAccumulatedForAllStores { get; set; } + + /// + /// Gets or sets the page size is for history of reward points on my account page + /// + public int PageSize { get; set; } + + /// + /// Gets or sets a value indicating whether earned reward points are taxable + /// + public bool EarnedRewardPointsAreTaxable { get; set; } + /// + /// Gets or sets a value indicating whether reward points can be earned on shipping + /// + public bool AwardPointsIncludeShipping { get; set; } + /// + /// Gets or sets a value indicating whether reward points can be earned on payment fee + /// + public bool AwardPointsIncludePaymentMethodAdditionalFee { get; set; } + /// + /// Gets or sets a value indicating whether giftcards applied to payment amount should be excluded for reward points calculation + /// + public bool AwardPointsExcludeGiftCard { get; set; } + /// + /// Gets or sets a value indicating whether purchased reward points applied to payment amount should be excluded for reward points calculation + /// + public bool AwardPointsExcludePurchasedRewardPoints { get; set; } + /// + /// Gets or sets a value indicating whether reward points can only be earned when using purchased reward points for payment + /// + public bool EarnRewardPointsOnlyWhenUsingPurchasedRewardPoints { get; set; } + + } } \ No newline at end of file diff --git a/src/Libraries/Nop.Core/Domain/Orders/Order.cs b/src/Libraries/Nop.Core/Domain/Orders/Order.cs index f11141295f1..473f9d8d940 100644 --- a/src/Libraries/Nop.Core/Domain/Orders/Order.cs +++ b/src/Libraries/Nop.Core/Domain/Orders/Order.cs @@ -51,12 +51,12 @@ protected virtual SortedDictionary ParseTaxRates(string tax decimal rate = decimal.Parse(taxes[0].Trim(), CultureInfo.InvariantCulture); taxRatesDictionary.Add(rate, new TaxRateRec() { - VatRate = rate, + TaxRate = rate, Amount = decimal.Parse(taxes[1].Trim(), CultureInfo.InvariantCulture), DiscountAmount = decimal.Parse(taxes[2].Trim(), CultureInfo.InvariantCulture), BaseAmount = decimal.Parse(taxes[3].Trim(), CultureInfo.InvariantCulture), - VatAmount = decimal.Parse(taxes[4].Trim(), CultureInfo.InvariantCulture), - AmountIncludingVAT = decimal.Parse(taxes[5].Trim(), CultureInfo.InvariantCulture) + TaxAmount = decimal.Parse(taxes[4].Trim(), CultureInfo.InvariantCulture), + AmountIncludingTax = decimal.Parse(taxes[5].Trim(), CultureInfo.InvariantCulture) }); } catch (Exception exc) @@ -70,12 +70,12 @@ protected virtual SortedDictionary ParseTaxRates(string tax if (!taxRatesDictionary.Any()) taxRatesDictionary.Add(decimal.Zero, new TaxRateRec() { - VatRate = decimal.Zero, + TaxRate = decimal.Zero, Amount = decimal.Zero, DiscountAmount = decimal.Zero, BaseAmount = decimal.Zero, - VatAmount = decimal.Zero, - AmountIncludingVAT = decimal.Zero + TaxAmount = decimal.Zero, + AmountIncludingTax = decimal.Zero }); return taxRatesDictionary; @@ -183,13 +183,18 @@ protected virtual SortedDictionary ParseTaxRates(string tax /// /// Gets or sets the order shipping (incl tax) /// + /// public decimal OrderShippingInclTax { get; set; } - /// /// Gets or sets the order shipping (excl tax) /// + /// public decimal OrderShippingExclTax { get; set; } + /// Gets or sets the order shipping (non taxable) + /// + public decimal OrderShippingNonTaxable { get; set; } + /// /// Gets or sets the payment method additional fee (incl tax) /// @@ -200,6 +205,10 @@ protected virtual SortedDictionary ParseTaxRates(string tax /// public decimal PaymentMethodAdditionalFeeExclTax { get; set; } + /// + /// Gets or sets the payment method additional fee (non taxable) + /// + public decimal PaymentMethodAdditionalFeeNonTaxable { get; set; } /// /// Gets or sets the tax rates /// @@ -373,6 +382,25 @@ protected virtual SortedDictionary ParseTaxRates(string tax /// Gets or sets the order total amount incl. tax /// public decimal OrderAmountIncl { get; set; } //MF 09.12.16 + /// + /// Gets or sets the order total discount amount incl. tax + /// + public decimal OrderDiscountIncl { get; set; } + /// + /// Gets or sets the order total earned reward points base amount incl. tax + /// + public decimal EarnedRewardPointsBaseAmountIncl { get; set; } + /// + /// Gets or sets the order total earned reward points base amount excl. tax + /// + public decimal EarnedRewardPointsBaseAmountExcl { get; set; } + /// + /// Gets or sets the custom order number without prefix + /// + public string CustomOrderNumber { get; set; } + /// + + #endregion #region Navigation properties @@ -528,12 +556,12 @@ public SortedDictionary TaxRatesDictionary #region Nested classes public partial class TaxRateRec { - public decimal VatRate { get; set; } + public decimal TaxRate { get; set; } public decimal Amount { get; set; } public decimal DiscountAmount { get; set; } public decimal BaseAmount { get; set; } - public decimal VatAmount { get; set; } - public decimal AmountIncludingVAT { get; set; } + public decimal TaxAmount { get; set; } + public decimal AmountIncludingTax { get; set; } } #endregion } diff --git a/src/Libraries/Nop.Core/Domain/Orders/OrderItem.cs b/src/Libraries/Nop.Core/Domain/Orders/OrderItem.cs index 5c35a560b87..89255e33205 100644 --- a/src/Libraries/Nop.Core/Domain/Orders/OrderItem.cs +++ b/src/Libraries/Nop.Core/Domain/Orders/OrderItem.cs @@ -1,134 +1,143 @@ -using System; -using System.Collections.Generic; -using Nop.Core.Domain.Catalog; - -namespace Nop.Core.Domain.Orders -{ - /// - /// Represents an order item - /// - public partial class OrderItem : BaseEntity - { - private ICollection _associatedGiftCards; - - /// - /// Gets or sets the order item identifier - /// - public Guid OrderItemGuid { get; set; } - - /// - /// Gets or sets the order identifier - /// - public int OrderId { get; set; } - - /// - /// Gets or sets the product identifier - /// - public int ProductId { get; set; } - - /// - /// Gets or sets the quantity - /// - public int Quantity { get; set; } - - /// - /// Gets or sets the unit price in primary store currency (incl tax) - /// - public decimal UnitPriceInclTax { get; set; } - - /// - /// Gets or sets the unit price in primary store currency (excl tax) - /// - public decimal UnitPriceExclTax { get; set; } - - /// - /// Gets or sets the price in primary store currency (incl tax) - /// - public decimal PriceInclTax { get; set; } - - /// - /// Gets or sets the price in primary store currency (excl tax) - /// - public decimal PriceExclTax { get; set; } - - /// - /// Gets or sets the discount amount (incl tax) - /// - public decimal DiscountAmountInclTax { get; set; } - - /// - /// Gets or sets the discount amount (excl tax) - /// - public decimal DiscountAmountExclTax { get; set; } - - /// - /// Gets or sets the original cost of this order item (when an order was placed), qty 1 - /// - public decimal OriginalProductCost { get; set; } - - /// - /// Gets or sets the attribute description - /// - public string AttributeDescription { get; set; } - - /// - /// Gets or sets the product attributes in XML format - /// - public string AttributesXml { get; set; } - - /// - /// Gets or sets the download count - /// - public int DownloadCount { get; set; } - - /// - /// Gets or sets a value indicating whether download is activated - /// - public bool IsDownloadActivated { get; set; } - - /// - /// Gets or sets a license download identifier (in case this is a downloadable product) - /// - public int? LicenseDownloadId { get; set; } - - /// - /// Gets or sets the total weight of one item - /// It's nullable for compatibility with the previous version of nopCommerce where was no such property - /// - public decimal? ItemWeight { get; set; } - - /// - /// Gets or sets the rental product start date (null if it's not a rental product) - /// - public DateTime? RentalStartDateUtc { get; set; } - - /// - /// Gets or sets the rental product end date (null if it's not a rental product) - /// - public DateTime? RentalEndDateUtc { get; set; } - - /// - /// Gets the order - /// - public virtual Order Order { get; set; } - - /// - /// Gets the product - /// - public virtual Product Product { get; set; } - - /// - /// Gets or sets the associated gift card - /// - public virtual ICollection AssociatedGiftCards - { - get { return _associatedGiftCards ?? (_associatedGiftCards = new List()); } - protected set { _associatedGiftCards = value; } - } - - /// - /// VAT% of product - /// - public decimal VatRate { get; set; } //MF 25.11.16 - } -} +using System; +using System.Collections.Generic; +using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Customers; + +namespace Nop.Core.Domain.Orders +{ + /// + /// Represents an order item + /// + public partial class OrderItem : BaseEntity + { + private ICollection _associatedGiftCards; + private ICollection _associatedRewardPoints; + /// + /// Gets or sets the order item identifier + /// + public Guid OrderItemGuid { get; set; } + + /// + /// Gets or sets the order identifier + /// + public int OrderId { get; set; } + + /// + /// Gets or sets the product identifier + /// + public int ProductId { get; set; } + + /// + /// Gets or sets the quantity + /// + public int Quantity { get; set; } + + /// + /// Gets or sets the unit price in primary store currency (incl tax) + /// + public decimal UnitPriceInclTax { get; set; } + + /// + /// Gets or sets the unit price in primary store currency (excl tax) + /// + public decimal UnitPriceExclTax { get; set; } + + /// + /// Gets or sets the price in primary store currency (incl tax) + /// + public decimal PriceInclTax { get; set; } + + /// + /// Gets or sets the price in primary store currency (excl tax) + /// + public decimal PriceExclTax { get; set; } + + /// + /// Gets or sets the discount amount (incl tax) + /// + public decimal DiscountAmountInclTax { get; set; } + + /// + /// Gets or sets the discount amount (excl tax) + /// + public decimal DiscountAmountExclTax { get; set; } + + /// + /// Gets or sets the original cost of this order item (when an order was placed), qty 1 + /// + public decimal OriginalProductCost { get; set; } + + /// + /// Gets or sets the attribute description + /// + public string AttributeDescription { get; set; } + + /// + /// Gets or sets the product attributes in XML format + /// + public string AttributesXml { get; set; } + + /// + /// Gets or sets the download count + /// + public int DownloadCount { get; set; } + + /// + /// Gets or sets a value indicating whether download is activated + /// + public bool IsDownloadActivated { get; set; } + + /// + /// Gets or sets a license download identifier (in case this is a downloadable product) + /// + public int? LicenseDownloadId { get; set; } + + /// + /// Gets or sets the total weight of one item + /// It's nullable for compatibility with the previous version of nopCommerce where was no such property + /// + public decimal? ItemWeight { get; set; } + + /// + /// Gets or sets the rental product start date (null if it's not a rental product) + /// + public DateTime? RentalStartDateUtc { get; set; } + + /// + /// Gets or sets the rental product end date (null if it's not a rental product) + /// + public DateTime? RentalEndDateUtc { get; set; } + + /// + /// Gets the order + /// + public virtual Order Order { get; set; } + + /// + /// Gets the product + /// + public virtual Product Product { get; set; } + + /// + /// Gets or sets the associated gift card + /// + public virtual ICollection AssociatedGiftCards + { + get { return _associatedGiftCards ?? (_associatedGiftCards = new List()); } + protected set { _associatedGiftCards = value; } + } + + /// + /// tax% of product + /// + public decimal TaxRate { get; set; } //MF 25.11.16 + /// + /// Gets or sets the associated reward points + /// + public virtual ICollection AssociatedRewardPoints + { + get { return _associatedRewardPoints ?? (_associatedRewardPoints = new List()); } + protected set { _associatedRewardPoints = value; } + } + } +} diff --git a/src/Libraries/Nop.Core/Domain/Orders/ShoppingCartItem.cs b/src/Libraries/Nop.Core/Domain/Orders/ShoppingCartItem.cs index 62d533609e5..5ffe1b9abb7 100644 --- a/src/Libraries/Nop.Core/Domain/Orders/ShoppingCartItem.cs +++ b/src/Libraries/Nop.Core/Domain/Orders/ShoppingCartItem.cs @@ -1,162 +1,162 @@ -using System; -using Nop.Core.Domain.Catalog; -using Nop.Core.Domain.Customers; - -namespace Nop.Core.Domain.Orders -{ - /// - /// Represents a shopping cart item - /// - public partial class ShoppingCartItem : BaseEntity - { - /// - /// Gets or sets the store identifier - /// - public int StoreId { get; set; } - - /// - /// Gets or sets the shopping cart type identifier - /// - public int ShoppingCartTypeId { get; set; } - - /// - /// Gets or sets the customer identifier - /// - public int CustomerId { get; set; } - - /// - /// Gets or sets the product identifier - /// - public int ProductId { get; set; } - - /// - /// Gets or sets the product attributes in XML format - /// - public string AttributesXml { get; set; } - - /// - /// Gets or sets the price enter by a customer - /// - public decimal CustomerEnteredPrice { get; set; } - - /// - /// Gets or sets the quantity - /// - public int Quantity { get; set; } - /// - /// Gets or sets the rental product start date (null if it's not a rental product) - /// - public DateTime? RentalStartDateUtc { get; set; } - - /// - /// Gets or sets the rental product end date (null if it's not a rental product) - /// - public DateTime? RentalEndDateUtc { get; set; } - - /// - /// Gets or sets the date and time of instance creation - /// - public DateTime CreatedOnUtc { get; set; } - - /// - /// Gets or sets the date and time of instance update - /// - public DateTime UpdatedOnUtc { get; set; } - - /// - /// Gets the log type - /// - public ShoppingCartType ShoppingCartType - { - get - { - return (ShoppingCartType)this.ShoppingCartTypeId; - } - set - { - this.ShoppingCartTypeId = (int)value; - } - } - - /// - /// Gets or sets the product - /// - public virtual Product Product { get; set; } - - /// - /// Gets or sets the customer - /// - public virtual Customer Customer { get; set; } - - /// - /// Gets a value indicating whether the shopping cart item is free shipping - /// - public bool IsFreeShipping - { - get - { - var product = this.Product; - if (product != null) - return product.IsFreeShipping; - return true; - } - } - - /// - /// Gets a value indicating whether the shopping cart item is ship enabled - /// - public bool IsShipEnabled - { - get - { - var product = this.Product; - if (product != null) - return product.IsShipEnabled; - return false; - } - } - - /// - /// Gets the additional shipping charge - /// - public decimal AdditionalShippingCharge - { - get - { - decimal additionalShippingCharge = decimal.Zero; - var product = this.Product; - if (product != null) - additionalShippingCharge = product.AdditionalShippingCharge * Quantity; - return additionalShippingCharge; - } - } - - /// - /// Gets a value indicating whether the shopping cart item is tax exempt - /// - public bool IsTaxExempt - { - get - { - var product = this.Product; - if (product != null) - return product.IsTaxExempt; - return false; - } - } - //fields for restored cart - /// - /// VatRate for restored cart. Only used by UpdateOrderTotal and can be null - /// - public decimal? VatRate { get; set; } - /// - /// Subtotal of item with tax - /// - public decimal? SubTotalInclTax { get; set; } - - /// - /// Subtotal of item without tax - /// - public decimal? SubTotalExclTax { get; set; } - } -} +using System; +using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Customers; + +namespace Nop.Core.Domain.Orders +{ + /// + /// Represents a shopping cart item + /// + public partial class ShoppingCartItem : BaseEntity + { + /// + /// Gets or sets the store identifier + /// + public int StoreId { get; set; } + + /// + /// Gets or sets the shopping cart type identifier + /// + public int ShoppingCartTypeId { get; set; } + + /// + /// Gets or sets the customer identifier + /// + public int CustomerId { get; set; } + + /// + /// Gets or sets the product identifier + /// + public int ProductId { get; set; } + + /// + /// Gets or sets the product attributes in XML format + /// + public string AttributesXml { get; set; } + + /// + /// Gets or sets the price enter by a customer + /// + public decimal CustomerEnteredPrice { get; set; } + + /// + /// Gets or sets the quantity + /// + public int Quantity { get; set; } + /// + /// Gets or sets the rental product start date (null if it's not a rental product) + /// + public DateTime? RentalStartDateUtc { get; set; } + + /// + /// Gets or sets the rental product end date (null if it's not a rental product) + /// + public DateTime? RentalEndDateUtc { get; set; } + + /// + /// Gets or sets the date and time of instance creation + /// + public DateTime CreatedOnUtc { get; set; } + + /// + /// Gets or sets the date and time of instance update + /// + public DateTime UpdatedOnUtc { get; set; } + + /// + /// Gets the log type + /// + public ShoppingCartType ShoppingCartType + { + get + { + return (ShoppingCartType)this.ShoppingCartTypeId; + } + set + { + this.ShoppingCartTypeId = (int)value; + } + } + + /// + /// Gets or sets the product + /// + public virtual Product Product { get; set; } + + /// + /// Gets or sets the customer + /// + public virtual Customer Customer { get; set; } + + /// + /// Gets a value indicating whether the shopping cart item is free shipping + /// + public bool IsFreeShipping + { + get + { + var product = this.Product; + if (product != null) + return product.IsFreeShipping; + return true; + } + } + + /// + /// Gets a value indicating whether the shopping cart item is ship enabled + /// + public bool IsShipEnabled + { + get + { + var product = this.Product; + if (product != null) + return product.IsShipEnabled; + return false; + } + } + + /// + /// Gets the additional shipping charge + /// + public decimal AdditionalShippingCharge + { + get + { + decimal additionalShippingCharge = decimal.Zero; + var product = this.Product; + if (product != null) + additionalShippingCharge = product.AdditionalShippingCharge * Quantity; + return additionalShippingCharge; + } + } + + /// + /// Gets a value indicating whether the shopping cart item is tax exempt + /// + public bool IsTaxExempt + { + get + { + var product = this.Product; + if (product != null) + return product.IsTaxExempt; + return false; + } + } + //fields for restored cart + /// + /// TaxRate for restored cart. Only used by UpdateOrderTotal and can be null + /// + public decimal? TaxRate { get; set; } + /// + /// Subtotal of item with tax + /// + public decimal? SubTotalInclTax { get; set; } + + /// + /// Subtotal of item without tax + /// + public decimal? SubTotalExclTax { get; set; } + } +} diff --git a/src/Libraries/Nop.Core/Domain/Orders/ShoppingCartSettings.cs b/src/Libraries/Nop.Core/Domain/Orders/ShoppingCartSettings.cs index 65493a6169c..11655ef6266 100644 --- a/src/Libraries/Nop.Core/Domain/Orders/ShoppingCartSettings.cs +++ b/src/Libraries/Nop.Core/Domain/Orders/ShoppingCartSettings.cs @@ -1,115 +1,119 @@ - -using Nop.Core.Configuration; - -namespace Nop.Core.Domain.Orders -{ - public class ShoppingCartSettings : ISettings - { - /// - /// Gets or sets a value indicating whether a custoemr should be redirected to the shopping cart page after adding a product to the cart/wishlist - /// - public bool DisplayCartAfterAddingProduct { get; set; } - - /// - /// Gets or sets a value indicating whether a custoemr should be redirected to the shopping cart page after adding a product to the cart/wishlist - /// - public bool DisplayWishlistAfterAddingProduct { get; set; } - - /// - /// Gets or sets a value indicating maximum number of items in the shopping cart - /// - public int MaximumShoppingCartItems { get; set; } - - /// - /// Gets or sets a value indicating maximum number of items in the wishlist - /// - public int MaximumWishlistItems { get; set; } - - /// - /// Gets or sets a value indicating whether to show product images in the mini-shopping cart block - /// - public bool AllowOutOfStockItemsToBeAddedToWishlist { get; set; } - - /// - /// Gets or sets a value indicating whether to move items from wishlist to cart when clicking "Add to cart" button. Otherwise, they are copied. - /// - public bool MoveItemsFromWishlistToCart { get; set; } - - /// - /// Gets or sets a value indicating whether shopping carts (and wishlist) are shared between stores (in multi-store environment) - /// - public bool CartsSharedBetweenStores { get; set; } - - /// - /// Gets or sets a value indicating whether to show product image on shopping cart page - /// - public bool ShowProductImagesOnShoppingCart { get; set; } - - /// - /// Gets or sets a value indicating whether to show product image on wishlist page - /// - public bool ShowProductImagesOnWishList { get; set; } - - /// - /// Gets or sets a value indicating whether to show discount box on shopping cart page - /// - public bool ShowDiscountBox { get; set; } - - /// - /// Gets or sets a value indicating whether to show gift card box on shopping cart page - /// - public bool ShowGiftCardBox { 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 - /// - public bool EmailWishlistEnabled { get; set; } - - /// - /// Gets or sets a value indicating whether to enabled "email a wishlist" for anonymous users. - /// - public bool AllowAnonymousUsersToEmailWishlist { get; set; } - - /// Gets or sets a value indicating whether mini-shopping cart is enabled - /// - public bool MiniShoppingCartEnabled { get; set; } - - /// - /// Gets or sets a value indicating whether to show product images in the mini-shopping cart block - /// - public bool ShowProductImagesInMiniShoppingCart { get; set; } - - /// Gets or sets a maximum number of products which can be displayed in the mini-shopping cart block - /// - public int MiniShoppingCartProductNumber { get; set; } - - //Round is already an issue. - //When enabled it can cause one issue: http://www.nopcommerce.com/boards/t/7679/vattax-rounding-error-important-fix.aspx - //When disable it causes another one: http://www.nopcommerce.com/boards/t/11419/nop-20-order-of-steps-in-checkout.aspx?p=3#46924 - /// - /// Gets or sets a value indicating whether to round calculated prices and total during calculation - /// - public bool RoundPricesDuringCalculation { get; set; } - - /// - /// Gets or sets a value indicating whether we should group shopping cart items for the same products - /// For example, a customer could have two shopping cart items for the same products (different product attributes) - /// - public bool GroupTierPricesForDistinctShoppingCartItems { get; set; } - - /// - /// Gets or sets a value indicating whether a customer will beable to edit products in the cart - /// - public bool AllowCartItemEditing { get; set; } - - /// - /// Gets or sets a value indicating whether a customer will see quantity of attribute values associated to products (when qty > 1) - /// - public bool RenderAssociatedAttributeValueQuantity { get; set; } - } + +using Nop.Core.Configuration; + +namespace Nop.Core.Domain.Orders +{ + public class ShoppingCartSettings : ISettings + { + /// + /// Gets or sets a value indicating whether a custoemr should be redirected to the shopping cart page after adding a product to the cart/wishlist + /// + public bool DisplayCartAfterAddingProduct { get; set; } + + /// + /// Gets or sets a value indicating whether a custoemr should be redirected to the shopping cart page after adding a product to the cart/wishlist + /// + public bool DisplayWishlistAfterAddingProduct { get; set; } + + /// + /// Gets or sets a value indicating maximum number of items in the shopping cart + /// + public int MaximumShoppingCartItems { get; set; } + + /// + /// Gets or sets a value indicating maximum number of items in the wishlist + /// + public int MaximumWishlistItems { get; set; } + + /// + /// Gets or sets a value indicating whether to show product images in the mini-shopping cart block + /// + public bool AllowOutOfStockItemsToBeAddedToWishlist { get; set; } + + /// + /// Gets or sets a value indicating whether to move items from wishlist to cart when clicking "Add to cart" button. Otherwise, they are copied. + /// + public bool MoveItemsFromWishlistToCart { get; set; } + + /// + /// Gets or sets a value indicating whether shopping carts (and wishlist) are shared between stores (in multi-store environment) + /// + public bool CartsSharedBetweenStores { get; set; } + + /// + /// Gets or sets a value indicating whether to show product image on shopping cart page + /// + public bool ShowProductImagesOnShoppingCart { get; set; } + + /// + /// Gets or sets a value indicating whether to show product image on wishlist page + /// + public bool ShowProductImagesOnWishList { get; set; } + + /// + /// Gets or sets a value indicating whether to show discount box on shopping cart page + /// + public bool ShowDiscountBox { get; set; } + + /// + /// Gets or sets a value indicating whether to show gift card box on shopping cart page + /// + public bool ShowGiftCardBox { 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 + /// + public bool EmailWishlistEnabled { get; set; } + + /// + /// Gets or sets a value indicating whether to enabled "email a wishlist" for anonymous users. + /// + public bool AllowAnonymousUsersToEmailWishlist { get; set; } + + /// Gets or sets a value indicating whether mini-shopping cart is enabled + /// + public bool MiniShoppingCartEnabled { get; set; } + + /// + /// Gets or sets a value indicating whether to show product images in the mini-shopping cart block + /// + public bool ShowProductImagesInMiniShoppingCart { get; set; } + + /// Gets or sets a maximum number of products which can be displayed in the mini-shopping cart block + /// + public int MiniShoppingCartProductNumber { get; set; } + + //Round is already an issue. + //When enabled it can cause one issue: http://www.nopcommerce.com/boards/t/7679/vattax-rounding-error-important-fix.aspx + //When disable it causes another one: http://www.nopcommerce.com/boards/t/11419/nop-20-order-of-steps-in-checkout.aspx?p=3#46924 + /// + /// Gets or sets a value indicating whether to round calculated prices and total during calculation + /// + public bool RoundPricesDuringCalculation { get; set; } + + /// + /// Gets or sets a value indicating whether we should group shopping cart items for the same products + /// For example, a customer could have two shopping cart items for the same products (different product attributes) + /// + public bool GroupTierPricesForDistinctShoppingCartItems { get; set; } + + /// + /// Gets or sets a value indicating whether a customer will beable to edit products in the cart + /// + public bool AllowCartItemEditing { get; set; } + + /// + /// Gets or sets a value indicating whether a customer will see quantity of attribute values associated to products (when qty > 1) + /// + public bool RenderAssociatedAttributeValueQuantity { get; set; } + /// + /// Gets or sets a value indicating whether a customer will see the price of attribute values associated to products + /// + public bool RenderProductAttributePrices { get; set; } + } } \ No newline at end of file diff --git a/src/Libraries/Nop.Data/Mapping/Customers/RewardPointsHistoryMap.cs b/src/Libraries/Nop.Data/Mapping/Customers/RewardPointsHistoryMap.cs index 73d3f16ac9a..0444c3fc9cc 100644 --- a/src/Libraries/Nop.Data/Mapping/Customers/RewardPointsHistoryMap.cs +++ b/src/Libraries/Nop.Data/Mapping/Customers/RewardPointsHistoryMap.cs @@ -1,23 +1,28 @@ -using Nop.Core.Domain.Customers; - -namespace Nop.Data.Mapping.Customers -{ - public partial class RewardPointsHistoryMap : NopEntityTypeConfiguration - { - public RewardPointsHistoryMap() - { - this.ToTable("RewardPointsHistory"); - this.HasKey(rph => rph.Id); - - this.Property(rph => rph.UsedAmount).HasPrecision(18, 4); - - this.HasRequired(rph => rph.Customer) - .WithMany() - .HasForeignKey(rph => rph.CustomerId); - - this.HasOptional(rph => rph.UsedWithOrder) - .WithOptionalDependent(o => o.RedeemedRewardPointsEntry) - .WillCascadeOnDelete(false); - } - } +using Nop.Core.Domain.Customers; + +namespace Nop.Data.Mapping.Customers +{ + public partial class RewardPointsHistoryMap : NopEntityTypeConfiguration + { + public RewardPointsHistoryMap() + { + this.ToTable("RewardPointsHistory"); + this.HasKey(rph => rph.Id); + + this.Property(rph => rph.UsedAmount).HasPrecision(18, 4); + this.Property(rph => rph.UsedAmountPurchased).HasPrecision(18, 4); + + this.HasRequired(rph => rph.Customer) + .WithMany() + .HasForeignKey(rph => rph.CustomerId); + + this.HasOptional(rph => rph.UsedWithOrder) + .WithOptionalDependent(o => o.RedeemedRewardPointsEntry) + .WillCascadeOnDelete(false); + + this.HasOptional(rph => rph.PurchasedWithOrderItem) + .WithMany(orderItem => orderItem.AssociatedRewardPoints) + .HasForeignKey(rph => rph.PurchasedWithOrderItemId); + } + } } \ No newline at end of file diff --git a/src/Libraries/Nop.Data/Mapping/Orders/OrderItemMap.cs b/src/Libraries/Nop.Data/Mapping/Orders/OrderItemMap.cs index 99b6ea2c6d0..7e8d477815c 100644 --- a/src/Libraries/Nop.Data/Mapping/Orders/OrderItemMap.cs +++ b/src/Libraries/Nop.Data/Mapping/Orders/OrderItemMap.cs @@ -1,32 +1,32 @@ -using Nop.Core.Domain.Orders; - -namespace Nop.Data.Mapping.Orders -{ - public partial class OrderItemMap : NopEntityTypeConfiguration - { - public OrderItemMap() - { - this.ToTable("OrderItem"); - this.HasKey(orderItem => orderItem.Id); - - this.Property(orderItem => orderItem.UnitPriceInclTax).HasPrecision(18, 4); - this.Property(orderItem => orderItem.UnitPriceExclTax).HasPrecision(18, 4); - this.Property(orderItem => orderItem.PriceInclTax).HasPrecision(18, 4); - this.Property(orderItem => orderItem.PriceExclTax).HasPrecision(18, 4); - this.Property(orderItem => orderItem.DiscountAmountInclTax).HasPrecision(18, 4); - this.Property(orderItem => orderItem.DiscountAmountExclTax).HasPrecision(18, 4); - this.Property(orderItem => orderItem.OriginalProductCost).HasPrecision(18, 4); - this.Property(orderItem => orderItem.ItemWeight).HasPrecision(18, 4); - this.Property(orderItem => orderItem.VatRate).HasPrecision(18, 4); //MF 25.11.16 - - - this.HasRequired(orderItem => orderItem.Order) - .WithMany(o => o.OrderItems) - .HasForeignKey(orderItem => orderItem.OrderId); - - this.HasRequired(orderItem => orderItem.Product) - .WithMany() - .HasForeignKey(orderItem => orderItem.ProductId); - } - } +using Nop.Core.Domain.Orders; + +namespace Nop.Data.Mapping.Orders +{ + public partial class OrderItemMap : NopEntityTypeConfiguration + { + public OrderItemMap() + { + this.ToTable("OrderItem"); + this.HasKey(orderItem => orderItem.Id); + + this.Property(orderItem => orderItem.UnitPriceInclTax).HasPrecision(18, 4); + this.Property(orderItem => orderItem.UnitPriceExclTax).HasPrecision(18, 4); + this.Property(orderItem => orderItem.PriceInclTax).HasPrecision(18, 4); + this.Property(orderItem => orderItem.PriceExclTax).HasPrecision(18, 4); + this.Property(orderItem => orderItem.DiscountAmountInclTax).HasPrecision(18, 4); + this.Property(orderItem => orderItem.DiscountAmountExclTax).HasPrecision(18, 4); + this.Property(orderItem => orderItem.OriginalProductCost).HasPrecision(18, 4); + this.Property(orderItem => orderItem.ItemWeight).HasPrecision(18, 4); + this.Property(orderItem => orderItem.TaxRate).HasPrecision(18, 4); //MF 25.11.16 + + + this.HasRequired(orderItem => orderItem.Order) + .WithMany(o => o.OrderItems) + .HasForeignKey(orderItem => orderItem.OrderId); + + this.HasRequired(orderItem => orderItem.Product) + .WithMany() + .HasForeignKey(orderItem => orderItem.ProductId); + } + } } \ No newline at end of file diff --git a/src/Libraries/Nop.Data/Mapping/Orders/OrderMap.cs b/src/Libraries/Nop.Data/Mapping/Orders/OrderMap.cs index d8a0ee13603..ce70a23d292 100644 --- a/src/Libraries/Nop.Data/Mapping/Orders/OrderMap.cs +++ b/src/Libraries/Nop.Data/Mapping/Orders/OrderMap.cs @@ -1,55 +1,60 @@ -using Nop.Core.Domain.Orders; - -namespace Nop.Data.Mapping.Orders -{ - public partial class OrderMap : NopEntityTypeConfiguration - { - public OrderMap() - { - this.ToTable("Order"); - this.HasKey(o => o.Id); - this.Property(o => o.CurrencyRate).HasPrecision(18, 8); - this.Property(o => o.OrderSubtotalInclTax).HasPrecision(18, 4); - this.Property(o => o.OrderSubtotalExclTax).HasPrecision(18, 4); - this.Property(o => o.OrderSubTotalDiscountInclTax).HasPrecision(18, 4); - this.Property(o => o.OrderSubTotalDiscountExclTax).HasPrecision(18, 4); - this.Property(o => o.OrderShippingInclTax).HasPrecision(18, 4); - this.Property(o => o.OrderShippingExclTax).HasPrecision(18, 4); - this.Property(o => o.PaymentMethodAdditionalFeeInclTax).HasPrecision(18, 4); - this.Property(o => o.PaymentMethodAdditionalFeeExclTax).HasPrecision(18, 4); - this.Property(o => o.OrderTax).HasPrecision(18, 4); - this.Property(o => o.OrderDiscount).HasPrecision(18, 4); - this.Property(o => o.OrderAmount).HasPrecision(18, 4); //MF 09.12.16 - this.Property(o => o.OrderAmountIncl).HasPrecision(18, 4); //MF 09.12.16 - this.Property(o => o.OrderTotal).HasPrecision(18, 4); - this.Property(o => o.RefundedAmount).HasPrecision(18, 4); - this.Property(o => o.CustomOrderNumber).IsRequired(); - - this.Ignore(o => o.OrderStatus); - this.Ignore(o => o.PaymentStatus); - this.Ignore(o => o.ShippingStatus); - this.Ignore(o => o.CustomerTaxDisplayType); - this.Ignore(o => o.TaxRatesDictionary); - - this.HasRequired(o => o.Customer) - .WithMany() - .HasForeignKey(o => o.CustomerId); - - //code below is commented because it causes some issues on big databases - http://www.nopcommerce.com/boards/t/11126/bug-version-20-command-confirm-takes-several-minutes-using-big-databases.aspx - //this.HasRequired(o => o.BillingAddress).WithOptional().Map(x => x.MapKey("BillingAddressId")).WillCascadeOnDelete(false); - //this.HasOptional(o => o.ShippingAddress).WithOptionalDependent().Map(x => x.MapKey("ShippingAddressId")).WillCascadeOnDelete(false); - this.HasRequired(o => o.BillingAddress) - .WithMany() - .HasForeignKey(o => o.BillingAddressId) - .WillCascadeOnDelete(false); - this.HasOptional(o => o.ShippingAddress) - .WithMany() - .HasForeignKey(o => o.ShippingAddressId) - .WillCascadeOnDelete(false); - this.HasOptional(o => o.PickupAddress) - .WithMany() - .HasForeignKey(o => o.PickupAddressId) - .WillCascadeOnDelete(false); - } - } +using Nop.Core.Domain.Orders; + +namespace Nop.Data.Mapping.Orders +{ + public partial class OrderMap : NopEntityTypeConfiguration + { + public OrderMap() + { + this.ToTable("Order"); + this.HasKey(o => o.Id); + this.Property(o => o.CurrencyRate).HasPrecision(18, 8); + this.Property(o => o.OrderSubtotalInclTax).HasPrecision(18, 4); + this.Property(o => o.OrderSubtotalExclTax).HasPrecision(18, 4); + this.Property(o => o.OrderSubTotalDiscountInclTax).HasPrecision(18, 4); + this.Property(o => o.OrderSubTotalDiscountExclTax).HasPrecision(18, 4); + this.Property(o => o.OrderShippingInclTax).HasPrecision(18, 4); + this.Property(o => o.OrderShippingExclTax).HasPrecision(18, 4); + this.Property(o => o.OrderShippingNonTaxable).HasPrecision(18, 4); + this.Property(o => o.PaymentMethodAdditionalFeeInclTax).HasPrecision(18, 4); + this.Property(o => o.PaymentMethodAdditionalFeeExclTax).HasPrecision(18, 4); + this.Property(o => o.PaymentMethodAdditionalFeeNonTaxable).HasPrecision(18, 4); + this.Property(o => o.OrderTax).HasPrecision(18, 4); + this.Property(o => o.OrderDiscount).HasPrecision(18, 4); + this.Property(o => o.OrderAmount).HasPrecision(18, 4); //MF 09.12.16 + this.Property(o => o.OrderAmountIncl).HasPrecision(18, 4); //MF 09.12.16 + this.Property(o => o.OrderDiscountIncl).HasPrecision(18, 4); + this.Property(o => o.EarnedRewardPointsBaseAmountIncl).HasPrecision(18, 4); + this.Property(o => o.EarnedRewardPointsBaseAmountExcl).HasPrecision(18, 4); + this.Property(o => o.OrderTotal).HasPrecision(18, 4); + this.Property(o => o.RefundedAmount).HasPrecision(18, 4); + this.Property(o => o.CustomOrderNumber).IsRequired(); + + this.Ignore(o => o.OrderStatus); + this.Ignore(o => o.PaymentStatus); + this.Ignore(o => o.ShippingStatus); + this.Ignore(o => o.CustomerTaxDisplayType); + this.Ignore(o => o.TaxRatesDictionary); + + this.HasRequired(o => o.Customer) + .WithMany() + .HasForeignKey(o => o.CustomerId); + + //code below is commented because it causes some issues on big databases - http://www.nopcommerce.com/boards/t/11126/bug-version-20-command-confirm-takes-several-minutes-using-big-databases.aspx + //this.HasRequired(o => o.BillingAddress).WithOptional().Map(x => x.MapKey("BillingAddressId")).WillCascadeOnDelete(false); + //this.HasOptional(o => o.ShippingAddress).WithOptionalDependent().Map(x => x.MapKey("ShippingAddressId")).WillCascadeOnDelete(false); + this.HasRequired(o => o.BillingAddress) + .WithMany() + .HasForeignKey(o => o.BillingAddressId) + .WillCascadeOnDelete(false); + this.HasOptional(o => o.ShippingAddress) + .WithMany() + .HasForeignKey(o => o.ShippingAddressId) + .WillCascadeOnDelete(false); + this.HasOptional(o => o.PickupAddress) + .WithMany() + .HasForeignKey(o => o.PickupAddressId) + .WillCascadeOnDelete(false); + } + } } \ No newline at end of file diff --git a/src/Libraries/Nop.Data/Mapping/Orders/ShoppingCartItemMap.cs b/src/Libraries/Nop.Data/Mapping/Orders/ShoppingCartItemMap.cs index edaa5eb28cd..55a6cba7e16 100644 --- a/src/Libraries/Nop.Data/Mapping/Orders/ShoppingCartItemMap.cs +++ b/src/Libraries/Nop.Data/Mapping/Orders/ShoppingCartItemMap.cs @@ -1,32 +1,32 @@ -using Nop.Core.Domain.Orders; - -namespace Nop.Data.Mapping.Orders -{ - public partial class ShoppingCartItemMap : NopEntityTypeConfiguration - { - public ShoppingCartItemMap() - { - this.ToTable("ShoppingCartItem"); - this.HasKey(sci => sci.Id); - - this.Property(sci => sci.CustomerEnteredPrice).HasPrecision(18, 4); - - this.Ignore(sci => sci.ShoppingCartType); - this.Ignore(sci => sci.IsFreeShipping); - this.Ignore(sci => sci.IsShipEnabled); - this.Ignore(sci => sci.AdditionalShippingCharge); - this.Ignore(sci => sci.IsTaxExempt); - this.Ignore(sci => sci.VatRate); - this.Ignore(sci => sci.SubTotalExclTax); - this.Ignore(sci => sci.SubTotalInclTax); - - this.HasRequired(sci => sci.Customer) - .WithMany(c => c.ShoppingCartItems) - .HasForeignKey(sci => sci.CustomerId); - - this.HasRequired(sci => sci.Product) - .WithMany() - .HasForeignKey(sci => sci.ProductId); - } - } -} +using Nop.Core.Domain.Orders; + +namespace Nop.Data.Mapping.Orders +{ + public partial class ShoppingCartItemMap : NopEntityTypeConfiguration + { + public ShoppingCartItemMap() + { + this.ToTable("ShoppingCartItem"); + this.HasKey(sci => sci.Id); + + this.Property(sci => sci.CustomerEnteredPrice).HasPrecision(18, 4); + + this.Ignore(sci => sci.ShoppingCartType); + this.Ignore(sci => sci.IsFreeShipping); + this.Ignore(sci => sci.IsShipEnabled); + this.Ignore(sci => sci.AdditionalShippingCharge); + this.Ignore(sci => sci.IsTaxExempt); + this.Ignore(sci => sci.TaxRate); + this.Ignore(sci => sci.SubTotalExclTax); + this.Ignore(sci => sci.SubTotalInclTax); + + this.HasRequired(sci => sci.Customer) + .WithMany(c => c.ShoppingCartItems) + .HasForeignKey(sci => sci.CustomerId); + + this.HasRequired(sci => sci.Product) + .WithMany() + .HasForeignKey(sci => sci.ProductId); + } + } +} diff --git a/src/Libraries/Nop.Services/Catalog/CopyProductService.cs b/src/Libraries/Nop.Services/Catalog/CopyProductService.cs index 2d9aefa3470..0a2bb4d12a4 100644 --- a/src/Libraries/Nop.Services/Catalog/CopyProductService.cs +++ b/src/Libraries/Nop.Services/Catalog/CopyProductService.cs @@ -1,635 +1,638 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Nop.Core.Domain.Catalog; -using Nop.Core.Domain.Media; -using Nop.Services.Localization; -using Nop.Services.Media; -using Nop.Services.Seo; -using Nop.Services.Stores; - -namespace Nop.Services.Catalog -{ - /// - /// Copy Product service - /// - public partial class CopyProductService : ICopyProductService - { - #region Fields - - private readonly IProductService _productService; - private readonly IProductAttributeService _productAttributeService; - private readonly ILanguageService _languageService; - private readonly ILocalizedEntityService _localizedEntityService; - private readonly ILocalizationService _localizationService; - private readonly IPictureService _pictureService; - private readonly ICategoryService _categoryService; - private readonly IManufacturerService _manufacturerService; - private readonly ISpecificationAttributeService _specificationAttributeService; - private readonly IDownloadService _downloadService; - private readonly IProductAttributeParser _productAttributeParser; - private readonly IUrlRecordService _urlRecordService; - private readonly IStoreMappingService _storeMappingService; - - #endregion - - #region Ctor - - public CopyProductService(IProductService productService, - IProductAttributeService productAttributeService, - ILanguageService languageService, - ILocalizedEntityService localizedEntityService, - ILocalizationService localizationService, - IPictureService pictureService, - ICategoryService categoryService, - IManufacturerService manufacturerService, - ISpecificationAttributeService specificationAttributeService, - IDownloadService downloadService, - IProductAttributeParser productAttributeParser, - IUrlRecordService urlRecordService, - IStoreMappingService storeMappingService) - { - this._productService = productService; - this._productAttributeService = productAttributeService; - this._languageService = languageService; - this._localizedEntityService = localizedEntityService; - this._localizationService = localizationService; - this._pictureService = pictureService; - this._categoryService = categoryService; - this._manufacturerService = manufacturerService; - this._specificationAttributeService = specificationAttributeService; - this._downloadService = downloadService; - this._productAttributeParser = productAttributeParser; - this._urlRecordService = urlRecordService; - this._storeMappingService = storeMappingService; - } - - #endregion - - #region Methods - - /// - /// Create a copy of product with all depended data - /// - /// The product to copy - /// The name of product duplicate - /// A value indicating whether the product duplicate should be published - /// A value indicating whether the product images should be copied - /// A value indicating whether the copy associated products - /// Product copy - public virtual Product CopyProduct(Product product, string newName, - bool isPublished = true, bool copyImages = true, bool copyAssociatedProducts = true) - { - if (product == null) - throw new ArgumentNullException("product"); - - if (String.IsNullOrEmpty(newName)) - throw new ArgumentException("Product name is required"); - - //product download & sample download - int downloadId = product.DownloadId; - int sampleDownloadId = product.SampleDownloadId; - if (product.IsDownload) - { - var download = _downloadService.GetDownloadById(product.DownloadId); - if (download != null) - { - var downloadCopy = new Download - { - DownloadGuid = Guid.NewGuid(), - UseDownloadUrl = download.UseDownloadUrl, - DownloadUrl = download.DownloadUrl, - DownloadBinary = download.DownloadBinary, - ContentType = download.ContentType, - Filename = download.Filename, - Extension = download.Extension, - IsNew = download.IsNew, - }; - _downloadService.InsertDownload(downloadCopy); - downloadId = downloadCopy.Id; - } - - if (product.HasSampleDownload) - { - var sampleDownload = _downloadService.GetDownloadById(product.SampleDownloadId); - if (sampleDownload != null) - { - var sampleDownloadCopy = new Download - { - DownloadGuid = Guid.NewGuid(), - UseDownloadUrl = sampleDownload.UseDownloadUrl, - DownloadUrl = sampleDownload.DownloadUrl, - DownloadBinary = sampleDownload.DownloadBinary, - ContentType = sampleDownload.ContentType, - Filename = sampleDownload.Filename, - Extension = sampleDownload.Extension, - IsNew = sampleDownload.IsNew - }; - _downloadService.InsertDownload(sampleDownloadCopy); - sampleDownloadId = sampleDownloadCopy.Id; - } - } - } - - var newSku = !String.IsNullOrWhiteSpace(product.Sku) - ? string.Format(_localizationService.GetResource("Admin.Catalog.Products.Copy.SKU.New"), product.Sku) : - product.Sku; - // product - var productCopy = new Product - { - ProductTypeId = product.ProductTypeId, - ParentGroupedProductId = product.ParentGroupedProductId, - VisibleIndividually = product.VisibleIndividually, - Name = newName, - ShortDescription = product.ShortDescription, - FullDescription = product.FullDescription, - VendorId = product.VendorId, - ProductTemplateId = product.ProductTemplateId, - AdminComment = product.AdminComment, - ShowOnHomePage = product.ShowOnHomePage, - MetaKeywords = product.MetaKeywords, - MetaDescription = product.MetaDescription, - MetaTitle = product.MetaTitle, - AllowCustomerReviews = product.AllowCustomerReviews, - LimitedToStores = product.LimitedToStores, - Sku = newSku, - ManufacturerPartNumber = product.ManufacturerPartNumber, - Gtin = product.Gtin, - IsGiftCard = product.IsGiftCard, - GiftCardType = product.GiftCardType, - OverriddenGiftCardAmount = product.OverriddenGiftCardAmount, - RequireOtherProducts = product.RequireOtherProducts, - RequiredProductIds = product.RequiredProductIds, - AutomaticallyAddRequiredProducts = product.AutomaticallyAddRequiredProducts, - IsDownload = product.IsDownload, - DownloadId = downloadId, - UnlimitedDownloads = product.UnlimitedDownloads, - MaxNumberOfDownloads = product.MaxNumberOfDownloads, - DownloadExpirationDays = product.DownloadExpirationDays, - DownloadActivationType = product.DownloadActivationType, - HasSampleDownload = product.HasSampleDownload, - SampleDownloadId = sampleDownloadId, - HasUserAgreement = product.HasUserAgreement, - UserAgreementText = product.UserAgreementText, - IsRecurring = product.IsRecurring, - RecurringCycleLength = product.RecurringCycleLength, - RecurringCyclePeriod = product.RecurringCyclePeriod, - RecurringTotalCycles = product.RecurringTotalCycles, - IsRental = product.IsRental, - RentalPriceLength = product.RentalPriceLength, - RentalPricePeriod = product.RentalPricePeriod, - IsShipEnabled = product.IsShipEnabled, - IsFreeShipping = product.IsFreeShipping, - ShipSeparately = product.ShipSeparately, - AdditionalShippingCharge = product.AdditionalShippingCharge, - DeliveryDateId = product.DeliveryDateId, - IsTaxExempt = product.IsTaxExempt, - TaxCategoryId = product.TaxCategoryId, - IsTelecommunicationsOrBroadcastingOrElectronicServices = product.IsTelecommunicationsOrBroadcastingOrElectronicServices, - ManageInventoryMethod = product.ManageInventoryMethod, - ProductAvailabilityRangeId = product.ProductAvailabilityRangeId, - UseMultipleWarehouses = product.UseMultipleWarehouses, - WarehouseId = product.WarehouseId, - StockQuantity = product.StockQuantity, - DisplayStockAvailability = product.DisplayStockAvailability, - DisplayStockQuantity = product.DisplayStockQuantity, - MinStockQuantity = product.MinStockQuantity, - LowStockActivityId = product.LowStockActivityId, - NotifyAdminForQuantityBelow = product.NotifyAdminForQuantityBelow, - BackorderMode = product.BackorderMode, - AllowBackInStockSubscriptions = product.AllowBackInStockSubscriptions, - OrderMinimumQuantity = product.OrderMinimumQuantity, - OrderMaximumQuantity = product.OrderMaximumQuantity, - AllowedQuantities = product.AllowedQuantities, - AllowAddingOnlyExistingAttributeCombinations = product.AllowAddingOnlyExistingAttributeCombinations, - NotReturnable = product.NotReturnable, - DisableBuyButton = product.DisableBuyButton, - DisableWishlistButton = product.DisableWishlistButton, - AvailableForPreOrder = product.AvailableForPreOrder, - PreOrderAvailabilityStartDateTimeUtc = product.PreOrderAvailabilityStartDateTimeUtc, - CallForPrice = product.CallForPrice, - Price = product.Price, - OldPrice = product.OldPrice, - ProductCost = product.ProductCost, - CustomerEntersPrice = product.CustomerEntersPrice, - MinimumCustomerEnteredPrice = product.MinimumCustomerEnteredPrice, - MaximumCustomerEnteredPrice = product.MaximumCustomerEnteredPrice, - BasepriceEnabled = product.BasepriceEnabled, - BasepriceAmount = product.BasepriceAmount, - BasepriceUnitId = product.BasepriceUnitId, - BasepriceBaseAmount = product.BasepriceBaseAmount, - BasepriceBaseUnitId = product.BasepriceBaseUnitId, - MarkAsNew = product.MarkAsNew, - MarkAsNewStartDateTimeUtc = product.MarkAsNewStartDateTimeUtc, - MarkAsNewEndDateTimeUtc = product.MarkAsNewEndDateTimeUtc, - Weight = product.Weight, - Length = product.Length, - Width = product.Width, - Height = product.Height, - AvailableStartDateTimeUtc = product.AvailableStartDateTimeUtc, - AvailableEndDateTimeUtc = product.AvailableEndDateTimeUtc, - DisplayOrder = product.DisplayOrder, - Published = isPublished, - Deleted = product.Deleted, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow - }; - - //validate search engine name - _productService.InsertProduct(productCopy); - - //search engine name - _urlRecordService.SaveSlug(productCopy, productCopy.ValidateSeName("", productCopy.Name, true), 0); - - var languages = _languageService.GetAllLanguages(true); - - //localization - foreach (var lang in languages) - { - var name = product.GetLocalized(x => x.Name, lang.Id, false, false); - if (!String.IsNullOrEmpty(name)) - _localizedEntityService.SaveLocalizedValue(productCopy, x => x.Name, name, lang.Id); - - var shortDescription = product.GetLocalized(x => x.ShortDescription, lang.Id, false, false); - if (!String.IsNullOrEmpty(shortDescription)) - _localizedEntityService.SaveLocalizedValue(productCopy, x => x.ShortDescription, shortDescription, lang.Id); - - var fullDescription = product.GetLocalized(x => x.FullDescription, lang.Id, false, false); - if (!String.IsNullOrEmpty(fullDescription)) - _localizedEntityService.SaveLocalizedValue(productCopy, x => x.FullDescription, fullDescription, lang.Id); - - var metaKeywords = product.GetLocalized(x => x.MetaKeywords, lang.Id, false, false); - if (!String.IsNullOrEmpty(metaKeywords)) - _localizedEntityService.SaveLocalizedValue(productCopy, x => x.MetaKeywords, metaKeywords, lang.Id); - - var metaDescription = product.GetLocalized(x => x.MetaDescription, lang.Id, false, false); - if (!String.IsNullOrEmpty(metaDescription)) - _localizedEntityService.SaveLocalizedValue(productCopy, x => x.MetaDescription, metaDescription, lang.Id); - - var metaTitle = product.GetLocalized(x => x.MetaTitle, lang.Id, false, false); - if (!String.IsNullOrEmpty(metaTitle)) - _localizedEntityService.SaveLocalizedValue(productCopy, x => x.MetaTitle, metaTitle, lang.Id); - - //search engine name - _urlRecordService.SaveSlug(productCopy, productCopy.ValidateSeName("", name, false), lang.Id); - } - - //product tags - foreach (var productTag in product.ProductTags) - { - productCopy.ProductTags.Add(productTag); - } - _productService.UpdateProduct(productCopy); - - //product pictures - //variable to store original and new picture identifiers - var originalNewPictureIdentifiers = new Dictionary(); - if (copyImages) - { - foreach (var productPicture in product.ProductPictures) - { - var picture = productPicture.Picture; - var pictureCopy = _pictureService.InsertPicture( - _pictureService.LoadPictureBinary(picture), - picture.MimeType, - _pictureService.GetPictureSeName(newName), - picture.AltAttribute, - picture.TitleAttribute); - _productService.InsertProductPicture(new ProductPicture - { - ProductId = productCopy.Id, - PictureId = pictureCopy.Id, - DisplayOrder = productPicture.DisplayOrder - }); - originalNewPictureIdentifiers.Add(picture.Id, pictureCopy.Id); - } - } - - //quantity change history - _productService.AddStockQuantityHistoryEntry(productCopy, product.StockQuantity, product.StockQuantity, product.WarehouseId, - string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.CopyProduct"), product.Id)); - - // product <-> warehouses mappings - foreach (var pwi in product.ProductWarehouseInventory) - { - var pwiCopy = new ProductWarehouseInventory - { - ProductId = productCopy.Id, - WarehouseId = pwi.WarehouseId, - StockQuantity = pwi.StockQuantity, - ReservedQuantity = 0, - }; - - productCopy.ProductWarehouseInventory.Add(pwiCopy); - - //quantity change history - var message = string.Format("{0} {1}", _localizationService.GetResource("Admin.StockQuantityHistory.Messages.MultipleWarehouses"), - string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.CopyProduct"), product.Id)); - _productService.AddStockQuantityHistoryEntry(productCopy, pwi.StockQuantity, pwi.StockQuantity, pwi.WarehouseId, message); - } - _productService.UpdateProduct(productCopy); - - // product <-> categories mappings - foreach (var productCategory in product.ProductCategories) - { - var productCategoryCopy = new ProductCategory - { - ProductId = productCopy.Id, - CategoryId = productCategory.CategoryId, - IsFeaturedProduct = productCategory.IsFeaturedProduct, - DisplayOrder = productCategory.DisplayOrder - }; - - _categoryService.InsertProductCategory(productCategoryCopy); - } - - // product <-> manufacturers mappings - foreach (var productManufacturers in product.ProductManufacturers) - { - var productManufacturerCopy = new ProductManufacturer - { - ProductId = productCopy.Id, - ManufacturerId = productManufacturers.ManufacturerId, - IsFeaturedProduct = productManufacturers.IsFeaturedProduct, - DisplayOrder = productManufacturers.DisplayOrder - }; - - _manufacturerService.InsertProductManufacturer(productManufacturerCopy); - } - - // product <-> releated products mappings - foreach (var relatedProduct in _productService.GetRelatedProductsByProductId1(product.Id, true)) - { - _productService.InsertRelatedProduct( - new RelatedProduct - { - ProductId1 = productCopy.Id, - ProductId2 = relatedProduct.ProductId2, - DisplayOrder = relatedProduct.DisplayOrder - }); - } - - // product <-> cross sells mappings - foreach (var csProduct in _productService.GetCrossSellProductsByProductId1(product.Id, true)) - { - _productService.InsertCrossSellProduct( - new CrossSellProduct - { - ProductId1 = productCopy.Id, - ProductId2 = csProduct.ProductId2, - }); - } - - // product specifications - foreach (var productSpecificationAttribute in product.ProductSpecificationAttributes) - { - var psaCopy = new ProductSpecificationAttribute - { - ProductId = productCopy.Id, - AttributeTypeId = productSpecificationAttribute.AttributeTypeId, - SpecificationAttributeOptionId = productSpecificationAttribute.SpecificationAttributeOptionId, - CustomValue = productSpecificationAttribute.CustomValue, - AllowFiltering = productSpecificationAttribute.AllowFiltering, - ShowOnProductPage = productSpecificationAttribute.ShowOnProductPage, - DisplayOrder = productSpecificationAttribute.DisplayOrder - }; - _specificationAttributeService.InsertProductSpecificationAttribute(psaCopy); - } - - //store mapping - var selectedStoreIds = _storeMappingService.GetStoresIdsWithAccess(product); - foreach (var id in selectedStoreIds) - { - _storeMappingService.InsertStoreMapping(productCopy, id); - } - - //product <-> attributes mappings - var associatedAttributes = new Dictionary(); - var associatedAttributeValues = new Dictionary(); - - //attribute mapping with condition attributes - var oldCopyWithConditionAttributes = new List(); - - //all product attribute mapping copies - var productAttributeMappingCopies = new Dictionary(); - - foreach (var productAttributeMapping in _productAttributeService.GetProductAttributeMappingsByProductId(product.Id)) - { - var productAttributeMappingCopy = new ProductAttributeMapping - { - ProductId = productCopy.Id, - ProductAttributeId = productAttributeMapping.ProductAttributeId, - TextPrompt = productAttributeMapping.TextPrompt, - IsRequired = productAttributeMapping.IsRequired, - AttributeControlTypeId = productAttributeMapping.AttributeControlTypeId, - DisplayOrder = productAttributeMapping.DisplayOrder, - ValidationMinLength = productAttributeMapping.ValidationMinLength, - ValidationMaxLength = productAttributeMapping.ValidationMaxLength, - ValidationFileAllowedExtensions = productAttributeMapping.ValidationFileAllowedExtensions, - ValidationFileMaximumSize = productAttributeMapping.ValidationFileMaximumSize, - DefaultValue = productAttributeMapping.DefaultValue - }; - _productAttributeService.InsertProductAttributeMapping(productAttributeMappingCopy); - - productAttributeMappingCopies.Add(productAttributeMappingCopy.Id, productAttributeMappingCopy); - - if (!string.IsNullOrEmpty(productAttributeMapping.ConditionAttributeXml)) - { - oldCopyWithConditionAttributes.Add(productAttributeMapping); - } - - //save associated value (used for combinations copying) - associatedAttributes.Add(productAttributeMapping.Id, productAttributeMappingCopy.Id); - - // product attribute values - var productAttributeValues = _productAttributeService.GetProductAttributeValues(productAttributeMapping.Id); - foreach (var productAttributeValue in productAttributeValues) - { - int attributeValuePictureId = 0; - if (originalNewPictureIdentifiers.ContainsKey(productAttributeValue.PictureId)) - { - attributeValuePictureId = originalNewPictureIdentifiers[productAttributeValue.PictureId]; - } - var attributeValueCopy = new ProductAttributeValue - { - ProductAttributeMappingId = productAttributeMappingCopy.Id, - AttributeValueTypeId = productAttributeValue.AttributeValueTypeId, - AssociatedProductId = productAttributeValue.AssociatedProductId, - Name = productAttributeValue.Name, - ColorSquaresRgb = productAttributeValue.ColorSquaresRgb, - PriceAdjustment = productAttributeValue.PriceAdjustment, - WeightAdjustment = productAttributeValue.WeightAdjustment, - Cost = productAttributeValue.Cost, - CustomerEntersQty = productAttributeValue.CustomerEntersQty, - Quantity = productAttributeValue.Quantity, - IsPreSelected = productAttributeValue.IsPreSelected, - DisplayOrder = productAttributeValue.DisplayOrder, - PictureId = attributeValuePictureId, - }; - //picture associated to "iamge square" attribute type (if exists) - if (productAttributeValue.ImageSquaresPictureId > 0) - { - var origImageSquaresPicture = _pictureService.GetPictureById(productAttributeValue.ImageSquaresPictureId); - if (origImageSquaresPicture != null) - { - //copy the picture - var imageSquaresPictureCopy = _pictureService.InsertPicture( - _pictureService.LoadPictureBinary(origImageSquaresPicture), - origImageSquaresPicture.MimeType, - origImageSquaresPicture.SeoFilename, - origImageSquaresPicture.AltAttribute, - origImageSquaresPicture.TitleAttribute); - attributeValueCopy.ImageSquaresPictureId = imageSquaresPictureCopy.Id; - } - } - - _productAttributeService.InsertProductAttributeValue(attributeValueCopy); - - //save associated value (used for combinations copying) - associatedAttributeValues.Add(productAttributeValue.Id, attributeValueCopy.Id); - - //localization - foreach (var lang in languages) - { - var name = productAttributeValue.GetLocalized(x => x.Name, lang.Id, false, false); - if (!String.IsNullOrEmpty(name)) - _localizedEntityService.SaveLocalizedValue(attributeValueCopy, x => x.Name, name, lang.Id); - } - } - } - - //copy attribute conditions - foreach (var productAttributeMapping in oldCopyWithConditionAttributes) - { - var oldConditionAttributeMapping = _productAttributeParser.ParseProductAttributeMappings(productAttributeMapping.ConditionAttributeXml).FirstOrDefault(); - - if (oldConditionAttributeMapping == null) - continue; - - var oldConditionValues = _productAttributeParser.ParseProductAttributeValues(productAttributeMapping.ConditionAttributeXml, oldConditionAttributeMapping.Id); - - if (!oldConditionValues.Any()) - continue; - - var newAttributeMappingId = associatedAttributes[oldConditionAttributeMapping.Id]; - var newConditionAttributeMapping = productAttributeMappingCopies[newAttributeMappingId]; - - var newConditionAttributeXml = string.Empty; - - foreach (var oldConditionValue in oldConditionValues) - { - newConditionAttributeXml = _productAttributeParser.AddProductAttribute(newConditionAttributeXml, newConditionAttributeMapping, associatedAttributeValues[oldConditionValue.Id].ToString()); - } - - var attributeMappingId = associatedAttributes[productAttributeMapping.Id]; - var conditionAttribute = productAttributeMappingCopies[attributeMappingId]; - conditionAttribute.ConditionAttributeXml = newConditionAttributeXml; - - _productAttributeService.UpdateProductAttributeMapping(conditionAttribute); - } - - //attribute combinations - foreach (var combination in _productAttributeService.GetAllProductAttributeCombinations(product.Id)) - { - //generate new AttributesXml according to new value IDs - string newAttributesXml = ""; - var parsedProductAttributes = _productAttributeParser.ParseProductAttributeMappings(combination.AttributesXml); - foreach (var oldAttribute in parsedProductAttributes) - { - if (associatedAttributes.ContainsKey(oldAttribute.Id)) - { - var newAttribute = _productAttributeService.GetProductAttributeMappingById(associatedAttributes[oldAttribute.Id]); - if (newAttribute != null) - { - var oldAttributeValuesStr = _productAttributeParser.ParseValues(combination.AttributesXml, oldAttribute.Id); - foreach (var oldAttributeValueStr in oldAttributeValuesStr) - { - if (newAttribute.ShouldHaveValues()) - { - //attribute values - int oldAttributeValue = int.Parse(oldAttributeValueStr); - if (associatedAttributeValues.ContainsKey(oldAttributeValue)) - { - var newAttributeValue = _productAttributeService.GetProductAttributeValueById(associatedAttributeValues[oldAttributeValue]); - if (newAttributeValue != null) - { - newAttributesXml = _productAttributeParser.AddProductAttribute(newAttributesXml, - newAttribute, newAttributeValue.Id.ToString()); - } - } - } - else - { - //just a text - newAttributesXml = _productAttributeParser.AddProductAttribute(newAttributesXml, - newAttribute, oldAttributeValueStr); - } - } - } - } - } - var combinationCopy = new ProductAttributeCombination - { - ProductId = productCopy.Id, - AttributesXml = newAttributesXml, - StockQuantity = combination.StockQuantity, - AllowOutOfStockOrders = combination.AllowOutOfStockOrders, - Sku = combination.Sku, - ManufacturerPartNumber = combination.ManufacturerPartNumber, - Gtin = combination.Gtin, - OverriddenPrice = combination.OverriddenPrice, - NotifyAdminForQuantityBelow = combination.NotifyAdminForQuantityBelow - }; - _productAttributeService.InsertProductAttributeCombination(combinationCopy); - - //quantity change history - _productService.AddStockQuantityHistoryEntry(productCopy, combination.StockQuantity, combination.StockQuantity, - message: string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.CopyProduct"), product.Id), combinationId: combination.Id); - } - - //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, - StartDateTimeUtc = tierPrice.StartDateTimeUtc, - EndDateTimeUtc = tierPrice.EndDateTimeUtc - }); - } - - // product <-> discounts mapping - foreach (var discount in product.AppliedDiscounts) - { - productCopy.AppliedDiscounts.Add(discount); - _productService.UpdateProduct(productCopy); - } - - //update "HasTierPrices" and "HasDiscountsApplied" properties - _productService.UpdateHasTierPricesProperty(productCopy); - _productService.UpdateHasDiscountsApplied(productCopy); - - //associated products - if (copyAssociatedProducts) - { - var associatedProducts = _productService.GetAssociatedProducts(product.Id, showHidden: true); - foreach (var associatedProduct in associatedProducts) - { - var associatedProductCopy = CopyProduct(associatedProduct, string.Format("Copy of {0}", associatedProduct.Name), - isPublished, copyImages, false); - associatedProductCopy.ParentGroupedProductId = productCopy.Id; - _productService.UpdateProduct(productCopy); - } - } - - return productCopy; - } - - #endregion - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Media; +using Nop.Services.Localization; +using Nop.Services.Media; +using Nop.Services.Seo; +using Nop.Services.Stores; + +namespace Nop.Services.Catalog +{ + /// + /// Copy Product service + /// + public partial class CopyProductService : ICopyProductService + { + #region Fields + + private readonly IProductService _productService; + private readonly IProductAttributeService _productAttributeService; + private readonly ILanguageService _languageService; + private readonly ILocalizedEntityService _localizedEntityService; + private readonly ILocalizationService _localizationService; + private readonly IPictureService _pictureService; + private readonly ICategoryService _categoryService; + private readonly IManufacturerService _manufacturerService; + private readonly ISpecificationAttributeService _specificationAttributeService; + private readonly IDownloadService _downloadService; + private readonly IProductAttributeParser _productAttributeParser; + private readonly IUrlRecordService _urlRecordService; + private readonly IStoreMappingService _storeMappingService; + + #endregion + + #region Ctor + + public CopyProductService(IProductService productService, + IProductAttributeService productAttributeService, + ILanguageService languageService, + ILocalizedEntityService localizedEntityService, + ILocalizationService localizationService, + IPictureService pictureService, + ICategoryService categoryService, + IManufacturerService manufacturerService, + ISpecificationAttributeService specificationAttributeService, + IDownloadService downloadService, + IProductAttributeParser productAttributeParser, + IUrlRecordService urlRecordService, + IStoreMappingService storeMappingService) + { + this._productService = productService; + this._productAttributeService = productAttributeService; + this._languageService = languageService; + this._localizedEntityService = localizedEntityService; + this._localizationService = localizationService; + this._pictureService = pictureService; + this._categoryService = categoryService; + this._manufacturerService = manufacturerService; + this._specificationAttributeService = specificationAttributeService; + this._downloadService = downloadService; + this._productAttributeParser = productAttributeParser; + this._urlRecordService = urlRecordService; + this._storeMappingService = storeMappingService; + } + + #endregion + + #region Methods + + /// + /// Create a copy of product with all depended data + /// + /// The product to copy + /// The name of product duplicate + /// A value indicating whether the product duplicate should be published + /// A value indicating whether the product images should be copied + /// A value indicating whether the copy associated products + /// Product copy + public virtual Product CopyProduct(Product product, string newName, + bool isPublished = true, bool copyImages = true, bool copyAssociatedProducts = true) + { + if (product == null) + throw new ArgumentNullException("product"); + + if (String.IsNullOrEmpty(newName)) + throw new ArgumentException("Product name is required"); + + //product download & sample download + int downloadId = product.DownloadId; + int sampleDownloadId = product.SampleDownloadId; + if (product.IsDownload) + { + var download = _downloadService.GetDownloadById(product.DownloadId); + if (download != null) + { + var downloadCopy = new Download + { + DownloadGuid = Guid.NewGuid(), + UseDownloadUrl = download.UseDownloadUrl, + DownloadUrl = download.DownloadUrl, + DownloadBinary = download.DownloadBinary, + ContentType = download.ContentType, + Filename = download.Filename, + Extension = download.Extension, + IsNew = download.IsNew, + }; + _downloadService.InsertDownload(downloadCopy); + downloadId = downloadCopy.Id; + } + + if (product.HasSampleDownload) + { + var sampleDownload = _downloadService.GetDownloadById(product.SampleDownloadId); + if (sampleDownload != null) + { + var sampleDownloadCopy = new Download + { + DownloadGuid = Guid.NewGuid(), + UseDownloadUrl = sampleDownload.UseDownloadUrl, + DownloadUrl = sampleDownload.DownloadUrl, + DownloadBinary = sampleDownload.DownloadBinary, + ContentType = sampleDownload.ContentType, + Filename = sampleDownload.Filename, + Extension = sampleDownload.Extension, + IsNew = sampleDownload.IsNew + }; + _downloadService.InsertDownload(sampleDownloadCopy); + sampleDownloadId = sampleDownloadCopy.Id; + } + } + } + + var newSku = !String.IsNullOrWhiteSpace(product.Sku) + ? string.Format(_localizationService.GetResource("Admin.Catalog.Products.Copy.SKU.New"), product.Sku) : + product.Sku; + // product + var productCopy = new Product + { + ProductTypeId = product.ProductTypeId, + ParentGroupedProductId = product.ParentGroupedProductId, + VisibleIndividually = product.VisibleIndividually, + Name = newName, + ShortDescription = product.ShortDescription, + FullDescription = product.FullDescription, + VendorId = product.VendorId, + ProductTemplateId = product.ProductTemplateId, + AdminComment = product.AdminComment, + ShowOnHomePage = product.ShowOnHomePage, + MetaKeywords = product.MetaKeywords, + MetaDescription = product.MetaDescription, + MetaTitle = product.MetaTitle, + AllowCustomerReviews = product.AllowCustomerReviews, + LimitedToStores = product.LimitedToStores, + Sku = newSku, + ManufacturerPartNumber = product.ManufacturerPartNumber, + Gtin = product.Gtin, + IsGiftCard = product.IsGiftCard, + GiftCardType = product.GiftCardType, + OverriddenGiftCardAmount = product.OverriddenGiftCardAmount, + IsRewardPoints = product.IsRewardPoints, + OverriddenRPExchangeRate = product.OverriddenRPExchangeRate, + ExcludeFromRewardPoints = product.ExcludeFromRewardPoints, + RequireOtherProducts = product.RequireOtherProducts, + RequiredProductIds = product.RequiredProductIds, + AutomaticallyAddRequiredProducts = product.AutomaticallyAddRequiredProducts, + IsDownload = product.IsDownload, + DownloadId = downloadId, + UnlimitedDownloads = product.UnlimitedDownloads, + MaxNumberOfDownloads = product.MaxNumberOfDownloads, + DownloadExpirationDays = product.DownloadExpirationDays, + DownloadActivationType = product.DownloadActivationType, + HasSampleDownload = product.HasSampleDownload, + SampleDownloadId = sampleDownloadId, + HasUserAgreement = product.HasUserAgreement, + UserAgreementText = product.UserAgreementText, + IsRecurring = product.IsRecurring, + RecurringCycleLength = product.RecurringCycleLength, + RecurringCyclePeriod = product.RecurringCyclePeriod, + RecurringTotalCycles = product.RecurringTotalCycles, + IsRental = product.IsRental, + RentalPriceLength = product.RentalPriceLength, + RentalPricePeriod = product.RentalPricePeriod, + IsShipEnabled = product.IsShipEnabled, + IsFreeShipping = product.IsFreeShipping, + ShipSeparately = product.ShipSeparately, + AdditionalShippingCharge = product.AdditionalShippingCharge, + DeliveryDateId = product.DeliveryDateId, + IsTaxExempt = product.IsTaxExempt, + TaxCategoryId = product.TaxCategoryId, + IsTelecommunicationsOrBroadcastingOrElectronicServices = product.IsTelecommunicationsOrBroadcastingOrElectronicServices, + ManageInventoryMethod = product.ManageInventoryMethod, + ProductAvailabilityRangeId = product.ProductAvailabilityRangeId, + UseMultipleWarehouses = product.UseMultipleWarehouses, + WarehouseId = product.WarehouseId, + StockQuantity = product.StockQuantity, + DisplayStockAvailability = product.DisplayStockAvailability, + DisplayStockQuantity = product.DisplayStockQuantity, + MinStockQuantity = product.MinStockQuantity, + LowStockActivityId = product.LowStockActivityId, + NotifyAdminForQuantityBelow = product.NotifyAdminForQuantityBelow, + BackorderMode = product.BackorderMode, + AllowBackInStockSubscriptions = product.AllowBackInStockSubscriptions, + OrderMinimumQuantity = product.OrderMinimumQuantity, + OrderMaximumQuantity = product.OrderMaximumQuantity, + AllowedQuantities = product.AllowedQuantities, + AllowAddingOnlyExistingAttributeCombinations = product.AllowAddingOnlyExistingAttributeCombinations, + NotReturnable = product.NotReturnable, + DisableBuyButton = product.DisableBuyButton, + DisableWishlistButton = product.DisableWishlistButton, + AvailableForPreOrder = product.AvailableForPreOrder, + PreOrderAvailabilityStartDateTimeUtc = product.PreOrderAvailabilityStartDateTimeUtc, + CallForPrice = product.CallForPrice, + Price = product.Price, + OldPrice = product.OldPrice, + ProductCost = product.ProductCost, + CustomerEntersPrice = product.CustomerEntersPrice, + MinimumCustomerEnteredPrice = product.MinimumCustomerEnteredPrice, + MaximumCustomerEnteredPrice = product.MaximumCustomerEnteredPrice, + BasepriceEnabled = product.BasepriceEnabled, + BasepriceAmount = product.BasepriceAmount, + BasepriceUnitId = product.BasepriceUnitId, + BasepriceBaseAmount = product.BasepriceBaseAmount, + BasepriceBaseUnitId = product.BasepriceBaseUnitId, + MarkAsNew = product.MarkAsNew, + MarkAsNewStartDateTimeUtc = product.MarkAsNewStartDateTimeUtc, + MarkAsNewEndDateTimeUtc = product.MarkAsNewEndDateTimeUtc, + Weight = product.Weight, + Length = product.Length, + Width = product.Width, + Height = product.Height, + AvailableStartDateTimeUtc = product.AvailableStartDateTimeUtc, + AvailableEndDateTimeUtc = product.AvailableEndDateTimeUtc, + DisplayOrder = product.DisplayOrder, + Published = isPublished, + Deleted = product.Deleted, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow + }; + + //validate search engine name + _productService.InsertProduct(productCopy); + + //search engine name + _urlRecordService.SaveSlug(productCopy, productCopy.ValidateSeName("", productCopy.Name, true), 0); + + var languages = _languageService.GetAllLanguages(true); + + //localization + foreach (var lang in languages) + { + var name = product.GetLocalized(x => x.Name, lang.Id, false, false); + if (!String.IsNullOrEmpty(name)) + _localizedEntityService.SaveLocalizedValue(productCopy, x => x.Name, name, lang.Id); + + var shortDescription = product.GetLocalized(x => x.ShortDescription, lang.Id, false, false); + if (!String.IsNullOrEmpty(shortDescription)) + _localizedEntityService.SaveLocalizedValue(productCopy, x => x.ShortDescription, shortDescription, lang.Id); + + var fullDescription = product.GetLocalized(x => x.FullDescription, lang.Id, false, false); + if (!String.IsNullOrEmpty(fullDescription)) + _localizedEntityService.SaveLocalizedValue(productCopy, x => x.FullDescription, fullDescription, lang.Id); + + var metaKeywords = product.GetLocalized(x => x.MetaKeywords, lang.Id, false, false); + if (!String.IsNullOrEmpty(metaKeywords)) + _localizedEntityService.SaveLocalizedValue(productCopy, x => x.MetaKeywords, metaKeywords, lang.Id); + + var metaDescription = product.GetLocalized(x => x.MetaDescription, lang.Id, false, false); + if (!String.IsNullOrEmpty(metaDescription)) + _localizedEntityService.SaveLocalizedValue(productCopy, x => x.MetaDescription, metaDescription, lang.Id); + + var metaTitle = product.GetLocalized(x => x.MetaTitle, lang.Id, false, false); + if (!String.IsNullOrEmpty(metaTitle)) + _localizedEntityService.SaveLocalizedValue(productCopy, x => x.MetaTitle, metaTitle, lang.Id); + + //search engine name + _urlRecordService.SaveSlug(productCopy, productCopy.ValidateSeName("", name, false), lang.Id); + } + + //product tags + foreach (var productTag in product.ProductTags) + { + productCopy.ProductTags.Add(productTag); + } + _productService.UpdateProduct(productCopy); + + //product pictures + //variable to store original and new picture identifiers + var originalNewPictureIdentifiers = new Dictionary(); + if (copyImages) + { + foreach (var productPicture in product.ProductPictures) + { + var picture = productPicture.Picture; + var pictureCopy = _pictureService.InsertPicture( + _pictureService.LoadPictureBinary(picture), + picture.MimeType, + _pictureService.GetPictureSeName(newName), + picture.AltAttribute, + picture.TitleAttribute); + _productService.InsertProductPicture(new ProductPicture + { + ProductId = productCopy.Id, + PictureId = pictureCopy.Id, + DisplayOrder = productPicture.DisplayOrder + }); + originalNewPictureIdentifiers.Add(picture.Id, pictureCopy.Id); + } + } + + //quantity change history + _productService.AddStockQuantityHistoryEntry(productCopy, product.StockQuantity, product.StockQuantity, product.WarehouseId, + string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.CopyProduct"), product.Id)); + + // product <-> warehouses mappings + foreach (var pwi in product.ProductWarehouseInventory) + { + var pwiCopy = new ProductWarehouseInventory + { + ProductId = productCopy.Id, + WarehouseId = pwi.WarehouseId, + StockQuantity = pwi.StockQuantity, + ReservedQuantity = 0, + }; + + productCopy.ProductWarehouseInventory.Add(pwiCopy); + + //quantity change history + var message = string.Format("{0} {1}", _localizationService.GetResource("Admin.StockQuantityHistory.Messages.MultipleWarehouses"), + string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.CopyProduct"), product.Id)); + _productService.AddStockQuantityHistoryEntry(productCopy, pwi.StockQuantity, pwi.StockQuantity, pwi.WarehouseId, message); + } + _productService.UpdateProduct(productCopy); + + // product <-> categories mappings + foreach (var productCategory in product.ProductCategories) + { + var productCategoryCopy = new ProductCategory + { + ProductId = productCopy.Id, + CategoryId = productCategory.CategoryId, + IsFeaturedProduct = productCategory.IsFeaturedProduct, + DisplayOrder = productCategory.DisplayOrder + }; + + _categoryService.InsertProductCategory(productCategoryCopy); + } + + // product <-> manufacturers mappings + foreach (var productManufacturers in product.ProductManufacturers) + { + var productManufacturerCopy = new ProductManufacturer + { + ProductId = productCopy.Id, + ManufacturerId = productManufacturers.ManufacturerId, + IsFeaturedProduct = productManufacturers.IsFeaturedProduct, + DisplayOrder = productManufacturers.DisplayOrder + }; + + _manufacturerService.InsertProductManufacturer(productManufacturerCopy); + } + + // product <-> releated products mappings + foreach (var relatedProduct in _productService.GetRelatedProductsByProductId1(product.Id, true)) + { + _productService.InsertRelatedProduct( + new RelatedProduct + { + ProductId1 = productCopy.Id, + ProductId2 = relatedProduct.ProductId2, + DisplayOrder = relatedProduct.DisplayOrder + }); + } + + // product <-> cross sells mappings + foreach (var csProduct in _productService.GetCrossSellProductsByProductId1(product.Id, true)) + { + _productService.InsertCrossSellProduct( + new CrossSellProduct + { + ProductId1 = productCopy.Id, + ProductId2 = csProduct.ProductId2, + }); + } + + // product specifications + foreach (var productSpecificationAttribute in product.ProductSpecificationAttributes) + { + var psaCopy = new ProductSpecificationAttribute + { + ProductId = productCopy.Id, + AttributeTypeId = productSpecificationAttribute.AttributeTypeId, + SpecificationAttributeOptionId = productSpecificationAttribute.SpecificationAttributeOptionId, + CustomValue = productSpecificationAttribute.CustomValue, + AllowFiltering = productSpecificationAttribute.AllowFiltering, + ShowOnProductPage = productSpecificationAttribute.ShowOnProductPage, + DisplayOrder = productSpecificationAttribute.DisplayOrder + }; + _specificationAttributeService.InsertProductSpecificationAttribute(psaCopy); + } + + //store mapping + var selectedStoreIds = _storeMappingService.GetStoresIdsWithAccess(product); + foreach (var id in selectedStoreIds) + { + _storeMappingService.InsertStoreMapping(productCopy, id); + } + + //product <-> attributes mappings + var associatedAttributes = new Dictionary(); + var associatedAttributeValues = new Dictionary(); + + //attribute mapping with condition attributes + var oldCopyWithConditionAttributes = new List(); + + //all product attribute mapping copies + var productAttributeMappingCopies = new Dictionary(); + + foreach (var productAttributeMapping in _productAttributeService.GetProductAttributeMappingsByProductId(product.Id)) + { + var productAttributeMappingCopy = new ProductAttributeMapping + { + ProductId = productCopy.Id, + ProductAttributeId = productAttributeMapping.ProductAttributeId, + TextPrompt = productAttributeMapping.TextPrompt, + IsRequired = productAttributeMapping.IsRequired, + AttributeControlTypeId = productAttributeMapping.AttributeControlTypeId, + DisplayOrder = productAttributeMapping.DisplayOrder, + ValidationMinLength = productAttributeMapping.ValidationMinLength, + ValidationMaxLength = productAttributeMapping.ValidationMaxLength, + ValidationFileAllowedExtensions = productAttributeMapping.ValidationFileAllowedExtensions, + ValidationFileMaximumSize = productAttributeMapping.ValidationFileMaximumSize, + DefaultValue = productAttributeMapping.DefaultValue + }; + _productAttributeService.InsertProductAttributeMapping(productAttributeMappingCopy); + + productAttributeMappingCopies.Add(productAttributeMappingCopy.Id, productAttributeMappingCopy); + + if (!string.IsNullOrEmpty(productAttributeMapping.ConditionAttributeXml)) + { + oldCopyWithConditionAttributes.Add(productAttributeMapping); + } + + //save associated value (used for combinations copying) + associatedAttributes.Add(productAttributeMapping.Id, productAttributeMappingCopy.Id); + + // product attribute values + var productAttributeValues = _productAttributeService.GetProductAttributeValues(productAttributeMapping.Id); + foreach (var productAttributeValue in productAttributeValues) + { + int attributeValuePictureId = 0; + if (originalNewPictureIdentifiers.ContainsKey(productAttributeValue.PictureId)) + { + attributeValuePictureId = originalNewPictureIdentifiers[productAttributeValue.PictureId]; + } + var attributeValueCopy = new ProductAttributeValue + { + ProductAttributeMappingId = productAttributeMappingCopy.Id, + AttributeValueTypeId = productAttributeValue.AttributeValueTypeId, + AssociatedProductId = productAttributeValue.AssociatedProductId, + Name = productAttributeValue.Name, + ColorSquaresRgb = productAttributeValue.ColorSquaresRgb, + PriceAdjustment = productAttributeValue.PriceAdjustment, + WeightAdjustment = productAttributeValue.WeightAdjustment, + Cost = productAttributeValue.Cost, + CustomerEntersQty = productAttributeValue.CustomerEntersQty, + Quantity = productAttributeValue.Quantity, + IsPreSelected = productAttributeValue.IsPreSelected, + DisplayOrder = productAttributeValue.DisplayOrder, + PictureId = attributeValuePictureId, + }; + //picture associated to "iamge square" attribute type (if exists) + if (productAttributeValue.ImageSquaresPictureId > 0) + { + var origImageSquaresPicture = _pictureService.GetPictureById(productAttributeValue.ImageSquaresPictureId); + if (origImageSquaresPicture != null) + { + //copy the picture + var imageSquaresPictureCopy = _pictureService.InsertPicture( + _pictureService.LoadPictureBinary(origImageSquaresPicture), + origImageSquaresPicture.MimeType, + origImageSquaresPicture.SeoFilename, + origImageSquaresPicture.AltAttribute, + origImageSquaresPicture.TitleAttribute); + attributeValueCopy.ImageSquaresPictureId = imageSquaresPictureCopy.Id; + } + } + + _productAttributeService.InsertProductAttributeValue(attributeValueCopy); + + //save associated value (used for combinations copying) + associatedAttributeValues.Add(productAttributeValue.Id, attributeValueCopy.Id); + + //localization + foreach (var lang in languages) + { + var name = productAttributeValue.GetLocalized(x => x.Name, lang.Id, false, false); + if (!String.IsNullOrEmpty(name)) + _localizedEntityService.SaveLocalizedValue(attributeValueCopy, x => x.Name, name, lang.Id); + } + } + } + + //copy attribute conditions + foreach (var productAttributeMapping in oldCopyWithConditionAttributes) + { + var oldConditionAttributeMapping = _productAttributeParser.ParseProductAttributeMappings(productAttributeMapping.ConditionAttributeXml).FirstOrDefault(); + + if (oldConditionAttributeMapping == null) + continue; + + var oldConditionValues = _productAttributeParser.ParseProductAttributeValues(productAttributeMapping.ConditionAttributeXml, oldConditionAttributeMapping.Id); + + if (!oldConditionValues.Any()) + continue; + + var newAttributeMappingId = associatedAttributes[oldConditionAttributeMapping.Id]; + var newConditionAttributeMapping = productAttributeMappingCopies[newAttributeMappingId]; + + var newConditionAttributeXml = string.Empty; + + foreach (var oldConditionValue in oldConditionValues) + { + newConditionAttributeXml = _productAttributeParser.AddProductAttribute(newConditionAttributeXml, newConditionAttributeMapping, associatedAttributeValues[oldConditionValue.Id].ToString()); + } + + var attributeMappingId = associatedAttributes[productAttributeMapping.Id]; + var conditionAttribute = productAttributeMappingCopies[attributeMappingId]; + conditionAttribute.ConditionAttributeXml = newConditionAttributeXml; + + _productAttributeService.UpdateProductAttributeMapping(conditionAttribute); + } + + //attribute combinations + foreach (var combination in _productAttributeService.GetAllProductAttributeCombinations(product.Id)) + { + //generate new AttributesXml according to new value IDs + string newAttributesXml = ""; + var parsedProductAttributes = _productAttributeParser.ParseProductAttributeMappings(combination.AttributesXml); + foreach (var oldAttribute in parsedProductAttributes) + { + if (associatedAttributes.ContainsKey(oldAttribute.Id)) + { + var newAttribute = _productAttributeService.GetProductAttributeMappingById(associatedAttributes[oldAttribute.Id]); + if (newAttribute != null) + { + var oldAttributeValuesStr = _productAttributeParser.ParseValues(combination.AttributesXml, oldAttribute.Id); + foreach (var oldAttributeValueStr in oldAttributeValuesStr) + { + if (newAttribute.ShouldHaveValues()) + { + //attribute values + int oldAttributeValue = int.Parse(oldAttributeValueStr); + if (associatedAttributeValues.ContainsKey(oldAttributeValue)) + { + var newAttributeValue = _productAttributeService.GetProductAttributeValueById(associatedAttributeValues[oldAttributeValue]); + if (newAttributeValue != null) + { + newAttributesXml = _productAttributeParser.AddProductAttribute(newAttributesXml, + newAttribute, newAttributeValue.Id.ToString()); + } + } + } + else + { + //just a text + newAttributesXml = _productAttributeParser.AddProductAttribute(newAttributesXml, + newAttribute, oldAttributeValueStr); + } + } + } + } + } + var combinationCopy = new ProductAttributeCombination + { + ProductId = productCopy.Id, + AttributesXml = newAttributesXml, + StockQuantity = combination.StockQuantity, + AllowOutOfStockOrders = combination.AllowOutOfStockOrders, + Sku = combination.Sku, + ManufacturerPartNumber = combination.ManufacturerPartNumber, + Gtin = combination.Gtin, + OverriddenPrice = combination.OverriddenPrice, + NotifyAdminForQuantityBelow = combination.NotifyAdminForQuantityBelow + }; + _productAttributeService.InsertProductAttributeCombination(combinationCopy); + + //quantity change history + _productService.AddStockQuantityHistoryEntry(productCopy, combination.StockQuantity, combination.StockQuantity, + message: string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.CopyProduct"), product.Id), combinationId: combination.Id); + } + + //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, + StartDateTimeUtc = tierPrice.StartDateTimeUtc, + EndDateTimeUtc = tierPrice.EndDateTimeUtc + }); + } + + // product <-> discounts mapping + foreach (var discount in product.AppliedDiscounts) + { + productCopy.AppliedDiscounts.Add(discount); + _productService.UpdateProduct(productCopy); + } + + //update "HasTierPrices" and "HasDiscountsApplied" properties + _productService.UpdateHasTierPricesProperty(productCopy); + _productService.UpdateHasDiscountsApplied(productCopy); + + //associated products + if (copyAssociatedProducts) + { + var associatedProducts = _productService.GetAssociatedProducts(product.Id, showHidden: true); + foreach (var associatedProduct in associatedProducts) + { + var associatedProductCopy = CopyProduct(associatedProduct, string.Format("Copy of {0}", associatedProduct.Name), + isPublished, copyImages, false); + associatedProductCopy.ParentGroupedProductId = productCopy.Id; + _productService.UpdateProduct(productCopy); + } + } + + return productCopy; + } + + #endregion + } +} diff --git a/src/Libraries/Nop.Services/Catalog/IPriceCalculationService.cs b/src/Libraries/Nop.Services/Catalog/IPriceCalculationService.cs index 6e03c43f27d..0a83e1b11ce 100644 --- a/src/Libraries/Nop.Services/Catalog/IPriceCalculationService.cs +++ b/src/Libraries/Nop.Services/Catalog/IPriceCalculationService.cs @@ -67,7 +67,30 @@ decimal GetFinalPrice(Product product, out decimal discountAmount, out List appliedDiscounts); - + /// + /// 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 + /// Rental period start date (for rental products) + /// Rental period end date (for rental products) + /// Applied discount amount + /// Applied discounts + /// Overridden product price. If specified, then it'll be used instead of a product price. For example, used with product attribute combinations + /// Final price + decimal GetFinalPrice(Product product, + Customer customer, + decimal additionalCharge, + bool includeDiscounts, + int quantity, + DateTime? rentalStartDate, + DateTime? rentalEndDate, + out decimal discountAmount, + out List appliedDiscounts, + decimal? overriddenProductPrice); /// /// Gets the shopping cart unit price (one item) diff --git a/src/Libraries/Nop.Services/Catalog/IProductAttributeFormatter.cs b/src/Libraries/Nop.Services/Catalog/IProductAttributeFormatter.cs index 0848706934d..86839fbf5aa 100644 --- a/src/Libraries/Nop.Services/Catalog/IProductAttributeFormatter.cs +++ b/src/Libraries/Nop.Services/Catalog/IProductAttributeFormatter.cs @@ -28,12 +28,12 @@ public partial interface IProductAttributeFormatter /// A value indicating whether to render product attributes /// A value indicating whether to render gift card attributes /// A value indicating whether to HTML hyperink tags could be rendered (if required) - /// A value indicating if attribute VAT should be rendered with price. Decimal.MinusOne is default, i.e. don't render + /// A value indicating if attribute VAT should be rendered with price. Null is default, i.e. don't render /// Attributes string FormatAttributes(Product product, string attributesXml, Customer customer, string serapator = "
    ", bool htmlEncode = true, bool renderPrices = true, bool renderProductAttributes = true, bool renderGiftCardAttributes = true, bool allowHyperlinks = true, - decimal subTotal = decimal.MinusOne); + decimal? subTotal = null); } } diff --git a/src/Libraries/Nop.Services/Catalog/IProductAttributeParser.cs b/src/Libraries/Nop.Services/Catalog/IProductAttributeParser.cs index fe2ef5d3007..037409e56c8 100644 --- a/src/Libraries/Nop.Services/Catalog/IProductAttributeParser.cs +++ b/src/Libraries/Nop.Services/Catalog/IProductAttributeParser.cs @@ -89,6 +89,7 @@ ProductAttributeCombination FindProductAttributeCombination(Product product, IList GenerateAllCombinations(Product product, bool ignoreNonCombinableAttributes = false); #endregion + #region taxAttribute /// /// Adds tax subdivision to existing attributesXml @@ -102,8 +103,15 @@ ProductAttributeCombination FindProductAttributeCombination(Product product, /// Parse ProductAttributesTax /// /// Attributes in XML format - /// SortedDictionary with vatRate and vatRateWeight + /// SortedDictionary with taxRate and taxRateWeight SortedDictionary ParseTaxAttribute(string attributesXml); + + /// + /// Check if attributesXml contains tax attribute information + /// + /// Attributes in XML format + /// A boolean value indicating if tax attribute info is present + bool hasTaxInfoInAttributeXML (string attributesXml); #endregion #region Gift card attributes diff --git a/src/Libraries/Nop.Services/Catalog/PriceCalculationService.cs b/src/Libraries/Nop.Services/Catalog/PriceCalculationService.cs index 654586c05b4..87dc2e54fa5 100644 --- a/src/Libraries/Nop.Services/Catalog/PriceCalculationService.cs +++ b/src/Libraries/Nop.Services/Catalog/PriceCalculationService.cs @@ -1,792 +1,806 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using Nop.Core; -using Nop.Core.Caching; -using Nop.Core.Domain.Catalog; -using Nop.Core.Domain.Customers; -using Nop.Core.Domain.Discounts; -using Nop.Core.Domain.Orders; -using Nop.Services.Catalog.Cache; -using Nop.Services.Customers; -using Nop.Services.Discounts; -using Nop.Services.Tax; -using Nop.Core.Domain.Tax; - -namespace Nop.Services.Catalog -{ - /// - /// Price calculation service - /// - public partial class PriceCalculationService : IPriceCalculationService - { - #region Fields - - private readonly IWorkContext _workContext; - private readonly IStoreContext _storeContext; - private readonly IDiscountService _discountService; - private readonly ICategoryService _categoryService; - private readonly IManufacturerService _manufacturerService; - private readonly IProductAttributeParser _productAttributeParser; - private readonly IProductService _productService; - private readonly ICacheManager _cacheManager; - private readonly ShoppingCartSettings _shoppingCartSettings; - private readonly CatalogSettings _catalogSettings; - private readonly ITaxService _taxService; - - #endregion - - #region Ctor - - public PriceCalculationService(IWorkContext workContext, - IStoreContext storeContext, - IDiscountService discountService, - ICategoryService categoryService, - IManufacturerService manufacturerService, - IProductAttributeParser productAttributeParser, - IProductService productService, - ICacheManager cacheManager, - ShoppingCartSettings shoppingCartSettings, - CatalogSettings catalogSettings, - ITaxService taxService) - { - this._workContext = workContext; - this._storeContext = storeContext; - this._discountService = discountService; - this._categoryService = categoryService; - this._manufacturerService = manufacturerService; - this._productAttributeParser = productAttributeParser; - this._productService = productService; - this._cacheManager = cacheManager; - this._shoppingCartSettings = shoppingCartSettings; - this._catalogSettings = catalogSettings; - this._taxService = taxService; - } - - #endregion - - #region Nested classes - - [Serializable] - protected class ProductPriceForCaching - { - public ProductPriceForCaching() - { - this.AppliedDiscounts = new List(); - } - - public decimal Price { get; set; } - public decimal AppliedDiscountAmount { get; set; } - public List AppliedDiscounts { get; set; } - } - #endregion - - #region Utilities - - /// - /// Gets allowed discounts applied to product - /// - /// Product - /// Customer - /// Discounts - protected virtual IList GetAllowedDiscountsAppliedToProduct(Product product, Customer customer) - { - var allowedDiscounts = new List(); - if (_catalogSettings.IgnoreDiscounts) - return allowedDiscounts; - - if (product.HasDiscountsApplied) - { - //we use this property ("HasDiscountsApplied") for performance optimization to avoid unnecessary database calls - foreach (var discount in product.AppliedDiscounts) - { - if (_discountService.ValidateDiscount(discount, customer).IsValid && - discount.DiscountType == DiscountType.AssignedToSkus) - allowedDiscounts.Add(discount.MapDiscount()); - } - } - - return allowedDiscounts; - } - - /// - /// Gets allowed discounts applied to categories - /// - /// Product - /// Customer - /// Discounts - protected virtual IList GetAllowedDiscountsAppliedToCategories(Product product, Customer customer) - { - var allowedDiscounts = new List(); - if (_catalogSettings.IgnoreDiscounts) - return allowedDiscounts; - - //load cached discount models (performance optimization) - foreach (var discount in _discountService.GetAllDiscountsForCaching(DiscountType.AssignedToCategories)) - { - //load identifier of categories with this discount applied to - var discountCategoryIds = _discountService.GetAppliedCategoryIds(discount, customer); - - //compare with categories of this product - var productCategoryIds = new List(); - if (discountCategoryIds.Any()) - { - //load identifier of categories of this product - var cacheKey = string.Format(PriceCacheEventConsumer.PRODUCT_CATEGORY_IDS_MODEL_KEY, - product.Id, - string.Join(",", customer.GetCustomerRoleIds()), - _storeContext.CurrentStore.Id); - productCategoryIds = _cacheManager.Get(cacheKey, () => - _categoryService - .GetProductCategoriesByProductId(product.Id) - .Select(x => x.CategoryId) - .ToList()); - } - - foreach (var categoryId in productCategoryIds) - { - if (discountCategoryIds.Contains(categoryId)) - { - if (_discountService.ValidateDiscount(discount, customer).IsValid && - !allowedDiscounts.ContainsDiscount(discount)) - allowedDiscounts.Add(discount); - } - } - } - - return allowedDiscounts; - } - - /// - /// Gets allowed discounts applied to manufacturers - /// - /// Product - /// Customer - /// Discounts - protected virtual IList GetAllowedDiscountsAppliedToManufacturers(Product product, Customer customer) - { - var allowedDiscounts = new List(); - if (_catalogSettings.IgnoreDiscounts) - return allowedDiscounts; - - foreach (var discount in _discountService.GetAllDiscountsForCaching(DiscountType.AssignedToManufacturers)) - { - //load identifier of manufacturers with this discount applied to - var discountManufacturerIds = _discountService.GetAppliedManufacturerIds(discount, customer); - - //compare with manufacturers of this product - var productManufacturerIds = new List(); - if (discountManufacturerIds.Any()) - { - //load identifier of manufacturers of this product - var cacheKey = string.Format(PriceCacheEventConsumer.PRODUCT_MANUFACTURER_IDS_MODEL_KEY, - product.Id, - string.Join(",", customer.GetCustomerRoleIds()), - _storeContext.CurrentStore.Id); - productManufacturerIds = _cacheManager.Get(cacheKey, () => - _manufacturerService - .GetProductManufacturersByProductId(product.Id) - .Select(x => x.ManufacturerId) - .ToList()); - } - - foreach (var manufacturerId in productManufacturerIds) - { - if (discountManufacturerIds.Contains(manufacturerId)) - { - if (_discountService.ValidateDiscount(discount, customer).IsValid && - !allowedDiscounts.ContainsDiscount(discount)) - allowedDiscounts.Add(discount); - } - } - } - - return allowedDiscounts; - } - - /// - /// Gets allowed discounts - /// - /// Product - /// Customer - /// Discounts - protected virtual IList GetAllowedDiscounts(Product product, Customer customer) - { - var allowedDiscounts = new List(); - if (_catalogSettings.IgnoreDiscounts) - return allowedDiscounts; - - //discounts applied to products - foreach (var discount in GetAllowedDiscountsAppliedToProduct(product, customer)) - if (!allowedDiscounts.ContainsDiscount(discount)) - allowedDiscounts.Add(discount); - - //discounts applied to categories - foreach (var discount in GetAllowedDiscountsAppliedToCategories(product, customer)) - if (!allowedDiscounts.ContainsDiscount(discount)) - allowedDiscounts.Add(discount); - - //discounts applied to manufacturers - foreach (var discount in GetAllowedDiscountsAppliedToManufacturers(product, customer)) - if (!allowedDiscounts.ContainsDiscount(discount)) - allowedDiscounts.Add(discount); - - return allowedDiscounts; - } - - /// - /// Gets discount amount - /// - /// Product - /// The customer - /// Already calculated product price without discount - /// Applied discounts - /// Discount amount - protected virtual decimal GetDiscountAmount(Product product, - Customer customer, - decimal productPriceWithoutDiscount, - out List appliedDiscounts) - { - if (product == null) - throw new ArgumentNullException("product"); - - appliedDiscounts = null; - decimal appliedDiscountAmount = decimal.Zero; - - //we don't apply discounts to products with price entered by a customer - if (product.CustomerEntersPrice) - return appliedDiscountAmount; - - //discounts are disabled - if (_catalogSettings.IgnoreDiscounts) - return appliedDiscountAmount; - - var allowedDiscounts = GetAllowedDiscounts(product, customer); - - //no discounts - if (!allowedDiscounts.Any()) - return appliedDiscountAmount; - - appliedDiscounts = allowedDiscounts.GetPreferredDiscount(productPriceWithoutDiscount, out appliedDiscountAmount); - return appliedDiscountAmount; - } - - #endregion - - #region Methods - - /// - /// 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 - /// Final price - public virtual decimal GetFinalPrice(Product product, - Customer customer, - decimal additionalCharge = decimal.Zero, - bool includeDiscounts = true, - int quantity = 1) - { - decimal discountAmount; - List appliedDiscounts; - return GetFinalPrice(product, customer, additionalCharge, includeDiscounts, - quantity, out discountAmount, out appliedDiscounts); - } - /// - /// 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 - /// Applied discount amount - /// Applied discounts - /// Final price - public virtual decimal GetFinalPrice(Product product, - Customer customer, - decimal additionalCharge, - bool includeDiscounts, - int quantity, - out decimal discountAmount, - out List appliedDiscounts) - { - return GetFinalPrice(product, customer, - additionalCharge, includeDiscounts, quantity, - null, null, - out discountAmount, out appliedDiscounts); - } - /// - /// 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 - /// Rental period start date (for rental products) - /// Rental period end date (for rental products) - /// Applied discount amount - /// Applied discounts - /// Final price - public virtual decimal GetFinalPrice(Product product, - Customer customer, - decimal additionalCharge, - bool includeDiscounts, - int quantity, - DateTime? rentalStartDate, - DateTime? rentalEndDate, - out decimal discountAmount, - out List appliedDiscounts) - { - return GetFinalPrice(product, customer, null, additionalCharge, includeDiscounts, quantity, - rentalStartDate, rentalEndDate, out discountAmount, out appliedDiscounts); - } - /// - /// Gets the final price - /// - /// Product - /// The customer - /// Overridden product price. If specified, then it'll be used instead of a product price. For example, used with product attribute combinations - /// Additional charge - /// A value indicating whether include discounts or not for final price computation - /// Shopping cart item quantity - /// Rental period start date (for rental products) - /// Rental period end date (for rental products) - /// Applied discount amount - /// Applied discounts - /// Final price - public virtual decimal GetFinalPrice(Product product, - Customer customer, - decimal? overriddenProductPrice, - decimal additionalCharge, - bool includeDiscounts, - int quantity, - DateTime? rentalStartDate, - DateTime? rentalEndDate, - out decimal discountAmount, - out List appliedDiscounts) - { - if (product == null) - throw new ArgumentNullException("product"); - - discountAmount = decimal.Zero; - appliedDiscounts = new List(); - - var cacheKey = string.Format(PriceCacheEventConsumer.PRODUCT_PRICE_MODEL_KEY, - product.Id, - overriddenProductPrice.HasValue ? overriddenProductPrice.Value.ToString(CultureInfo.InvariantCulture) : null, - additionalCharge.ToString(CultureInfo.InvariantCulture), - includeDiscounts, - quantity, - string.Join(",", customer.GetCustomerRoleIds()), - _storeContext.CurrentStore.Id); - var cacheTime = _catalogSettings.CacheProductPrices ? 60 : 0; - //we do not cache price for rental products - //otherwise, it can cause memory leaks (to store all possible date period combinations) - if (product.IsRental) - cacheTime = 0; - var cachedPrice = _cacheManager.Get(cacheKey, cacheTime, () => - { - var result = new ProductPriceForCaching(); - - //initial price - decimal price = overriddenProductPrice.HasValue ? overriddenProductPrice.Value : product.Price; - - //tier prices - var tierPrice = product.GetPreferredTierPrice(customer, _storeContext.CurrentStore.Id, quantity); - if (tierPrice != null) - price = tierPrice.Price; - - //additional charge - price = price + additionalCharge; - - //rental products - if (product.IsRental) - if (rentalStartDate.HasValue && rentalEndDate.HasValue) - price = price * product.GetRentalPeriods(rentalStartDate.Value, rentalEndDate.Value); - - if (includeDiscounts) - { - //discount - List tmpAppliedDiscounts; - decimal tmpDiscountAmount = GetDiscountAmount(product, customer, price, out tmpAppliedDiscounts); - price = price - tmpDiscountAmount; - - if (tmpAppliedDiscounts != null) - { - result.AppliedDiscounts = tmpAppliedDiscounts; - result.AppliedDiscountAmount = tmpDiscountAmount; - } - } - - if (price < decimal.Zero) - price = decimal.Zero; - - result.Price = price; - return result; - }); - - if (includeDiscounts) - { - if (cachedPrice.AppliedDiscounts.Any()) - { - appliedDiscounts.AddRange(cachedPrice.AppliedDiscounts); - discountAmount = cachedPrice.AppliedDiscountAmount; - } - } - - return cachedPrice.Price; - } - - - - /// - /// Gets the shopping cart unit price (one item) - /// - /// The shopping cart item - /// A value indicating whether include discounts or not for price computation - /// Shopping cart unit price (one item) - public virtual decimal GetUnitPrice(ShoppingCartItem shoppingCartItem, - bool includeDiscounts = true) - { - decimal discountAmount; - List appliedDiscounts; - return GetUnitPrice(shoppingCartItem, includeDiscounts, - out discountAmount, out appliedDiscounts); - } - /// - /// Gets the shopping cart unit price (one item) - /// - /// The shopping cart item - /// A value indicating whether include discounts or not for price computation - /// Applied discount amount - /// Applied discounts - /// Shopping cart unit price (one item) - public virtual decimal GetUnitPrice(ShoppingCartItem shoppingCartItem, - bool includeDiscounts, - out decimal discountAmount, - out List appliedDiscounts) - { - if (shoppingCartItem == null) - throw new ArgumentNullException("shoppingCartItem"); - var attributesXml = shoppingCartItem.AttributesXml; - var unitPrice = GetUnitPrice(shoppingCartItem.Product, - shoppingCartItem.Customer, - shoppingCartItem.ShoppingCartType, - shoppingCartItem.Quantity, - ref attributesXml, - shoppingCartItem.CustomerEnteredPrice, - shoppingCartItem.RentalStartDateUtc, - shoppingCartItem.RentalEndDateUtc, - includeDiscounts, - out discountAmount, - out appliedDiscounts); - shoppingCartItem.AttributesXml = attributesXml; - return unitPrice; - } - /// - /// Gets the shopping cart unit price (one item) - /// - /// Product - /// Customer - /// Shopping cart type - /// Quantity - /// Product atrributes (XML format) - /// Customer entered price (if specified) - /// Rental start date (null for not rental products) - /// Rental end date (null for not rental products) - /// A value indicating whether include discounts or not for price computation - /// Applied discount amount - /// Applied discounts - /// Shopping cart unit price (one item) - public virtual decimal GetUnitPrice(Product product, - Customer customer, - ShoppingCartType shoppingCartType, - int quantity, - ref string attributesXml, - decimal customerEnteredPrice, - DateTime? rentalStartDate, DateTime? rentalEndDate, - bool includeDiscounts, - out decimal discountAmount, - out List appliedDiscounts) - { - if (product == null) - throw new ArgumentNullException("product"); - - if (customer == null) - throw new ArgumentNullException("customer"); - - discountAmount = decimal.Zero; - appliedDiscounts = new List(); - - var taxWeightSummary = new TaxSummary(_workContext.TaxDisplayType == TaxDisplayType.IncludingTax); - - decimal finalPrice = decimal.Zero; - - var combination = _productAttributeParser.FindProductAttributeCombination(product, attributesXml); - if (combination != null && combination.OverriddenPrice.HasValue) - { - finalPrice = GetFinalPrice(product, - customer, - combination.OverriddenPrice.Value, - decimal.Zero, - includeDiscounts, - quantity, - product.IsRental ? rentalStartDate : null, - product.IsRental ? rentalEndDate : null, - out discountAmount, out appliedDiscounts); - } - - //summarize price of all attributes and build up taxSummary - //needed for VAT calculation of product bundles - decimal attributesTotalPrice = decimal.Zero; - var attributeValues = _productAttributeParser.ParseProductAttributeValues(attributesXml); - if (attributeValues != null) - { - decimal taxRate; - foreach (var attributeValue in attributeValues) - { - var attributePrice = GetProductAttributeValuePriceAdjustment(attributeValue); - attributesTotalPrice += attributePrice; - - if (attributeValue.AttributeValueType == AttributeValueType.AssociatedToProduct) - { - //bundled product - var associatedProduct = _productService.GetProductById(attributeValue.AssociatedProductId); - if (associatedProduct != null) - { - //build taxSummary for tax subdivision - var attributePriceTax = _taxService.GetProductPrice(associatedProduct, attributePrice, customer, out taxRate); - taxWeightSummary.AddRate(taxRate, attributePrice); - } - } - } - } - - if (combination == null || !combination.OverriddenPrice.HasValue) - { - //get price of a product (with previously calculated price of all attributes) - if (product.CustomerEntersPrice) - { - finalPrice = customerEnteredPrice; - } - else - { - int qty; - if (_shoppingCartSettings.GroupTierPricesForDistinctShoppingCartItems) - { - //the same products with distinct product attributes could be stored as distinct "ShoppingCartItem" records - //so let's find how many of the current products are in the cart - qty = customer.ShoppingCartItems - .Where(x => x.ProductId == product.Id) - .Where(x => x.ShoppingCartType == shoppingCartType) - .Sum(x => x.Quantity); - if (qty == 0) - { - qty = quantity; - } - } - else - { - qty = quantity; - } - finalPrice = GetFinalPrice(product, - customer, - attributesTotalPrice, - includeDiscounts, - qty, - product.IsRental ? rentalStartDate : null, - product.IsRental ? rentalEndDate : null, - out discountAmount, out appliedDiscounts); - } - } - - //rounding - if (_shoppingCartSettings.RoundPricesDuringCalculation) - finalPrice = RoundingHelper.RoundPrice(finalPrice); - - //add tax attributes - if (taxWeightSummary != null && taxWeightSummary.TaxRates.Any()) - { - decimal taxRate; - var itemAmount = _taxService.GetProductPrice(product, 0, customer, out taxRate); - var baseProductPrice = finalPrice - attributesTotalPrice; - if (baseProductPrice > decimal.Zero) - taxWeightSummary.AddRate(taxRate, baseProductPrice); - - if (taxWeightSummary.TaxRates.Count() > 1 || - (taxWeightSummary.TaxRates.FirstOrDefault().Key != taxRate && baseProductPrice != finalPrice) //use associated product taxRate when different - ) - { - taxWeightSummary.CalculateAmounts(); - attributesXml = _productAttributeParser.AddTaxAttribute(attributesXml, taxWeightSummary); - } - } - - return finalPrice; - } - /// - /// Gets the shopping cart item sub total - /// - /// The shopping cart item - /// A value indicating whether include discounts or not for price computation - /// Shopping cart item sub total - public virtual decimal GetSubTotal(ShoppingCartItem shoppingCartItem, - bool includeDiscounts = true) - { - decimal discountAmount; - List appliedDiscounts; - int? maximumDiscountQty; - return GetSubTotal(shoppingCartItem, includeDiscounts, out discountAmount, out appliedDiscounts, out maximumDiscountQty); - } - /// - /// Gets the shopping cart item sub total - /// - /// The shopping cart item - /// A value indicating whether include discounts or not for price computation - /// Applied discount amount - /// Applied discounts - /// Maximum discounted qty. Return not nullable value if discount cannot be applied to ALL items - /// Shopping cart item sub total - public virtual decimal GetSubTotal(ShoppingCartItem shoppingCartItem, - bool includeDiscounts, - out decimal discountAmount, - out List appliedDiscounts, - out int? maximumDiscountQty) - { - if (shoppingCartItem == null) - throw new ArgumentNullException("shoppingCartItem"); - - decimal subTotal; - maximumDiscountQty = null; - - //unit price - var unitPrice = GetUnitPrice(shoppingCartItem, includeDiscounts, - out discountAmount, out appliedDiscounts); - - //discount - if (appliedDiscounts.Any()) - { - //we can properly use "MaximumDiscountedQuantity" property only for one discount (not cumulative ones) - DiscountForCaching oneAndOnlyDiscount = null; - if (appliedDiscounts.Count == 1) - oneAndOnlyDiscount = appliedDiscounts.First(); - - if (oneAndOnlyDiscount != null && - oneAndOnlyDiscount.MaximumDiscountedQuantity.HasValue && - shoppingCartItem.Quantity > oneAndOnlyDiscount.MaximumDiscountedQuantity.Value) - { - maximumDiscountQty = oneAndOnlyDiscount.MaximumDiscountedQuantity.Value; - //we cannot apply discount for all shopping cart items - var discountedQuantity = oneAndOnlyDiscount.MaximumDiscountedQuantity.Value; - var discountedSubTotal = unitPrice * discountedQuantity; - discountAmount = discountAmount * discountedQuantity; - - var notDiscountedQuantity = shoppingCartItem.Quantity - discountedQuantity; - var notDiscountedUnitPrice = GetUnitPrice(shoppingCartItem, false); - var notDiscountedSubTotal = notDiscountedUnitPrice*notDiscountedQuantity; - - subTotal = discountedSubTotal + notDiscountedSubTotal; - } - else - { - //discount is applied to all items (quantity) - //calculate discount amount for all items - discountAmount = discountAmount * shoppingCartItem.Quantity; - - subTotal = unitPrice * shoppingCartItem.Quantity; - } - } - else - { - subTotal = unitPrice * shoppingCartItem.Quantity; - } - return subTotal; - } - - - /// - /// Gets the product cost (one item) - /// - /// Product - /// Shopping cart item attributes in XML - /// Product cost (one item) - public virtual decimal GetProductCost(Product product, string attributesXml) - { - if (product == null) - throw new ArgumentNullException("product"); - - decimal cost = product.ProductCost; - var attributeValues = _productAttributeParser.ParseProductAttributeValues(attributesXml); - foreach (var attributeValue in attributeValues) - { - switch (attributeValue.AttributeValueType) - { - case AttributeValueType.Simple: - { - //simple attribute - cost += attributeValue.Cost; - } - break; - case AttributeValueType.AssociatedToProduct: - { - //bundled product - var associatedProduct = _productService.GetProductById(attributeValue.AssociatedProductId); - if (associatedProduct != null) - cost += associatedProduct.ProductCost * attributeValue.Quantity; - } - break; - default: - break; - } - } - - return cost; - } - - - - /// - /// Get a price adjustment of a product attribute value - /// - /// Product attribute value - /// Price adjustment - public virtual decimal GetProductAttributeValuePriceAdjustment(ProductAttributeValue value) - { - if (value == null) - throw new ArgumentNullException("value"); - - var adjustment = decimal.Zero; - switch (value.AttributeValueType) - { - case AttributeValueType.Simple: - { - //simple attribute - adjustment = value.PriceAdjustment; - } - break; - case AttributeValueType.AssociatedToProduct: - { - //bundled product - var associatedProduct = _productService.GetProductById(value.AssociatedProductId); - if (associatedProduct != null) - { - adjustment = GetFinalPrice(associatedProduct, _workContext.CurrentCustomer, includeDiscounts: true) * value.Quantity; - } - } - break; - default: - break; - } - - return adjustment; - } - - #endregion - } -} +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Nop.Core; +using Nop.Core.Caching; +using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Customers; +using Nop.Core.Domain.Discounts; +using Nop.Core.Domain.Orders; +using Nop.Services.Catalog.Cache; +using Nop.Services.Customers; +using Nop.Services.Discounts; +using Nop.Services.Tax; +using Nop.Core.Domain.Tax; + +namespace Nop.Services.Catalog +{ + /// + /// Price calculation service + /// + public partial class PriceCalculationService : IPriceCalculationService + { + #region Fields + + private readonly IWorkContext _workContext; + private readonly IStoreContext _storeContext; + private readonly IDiscountService _discountService; + private readonly ICategoryService _categoryService; + private readonly IManufacturerService _manufacturerService; + private readonly IProductAttributeParser _productAttributeParser; + private readonly IProductService _productService; + private readonly ICacheManager _cacheManager; + private readonly ShoppingCartSettings _shoppingCartSettings; + private readonly CatalogSettings _catalogSettings; + private readonly ITaxService _taxService; + + #endregion + + #region Ctor + + public PriceCalculationService(IWorkContext workContext, + IStoreContext storeContext, + IDiscountService discountService, + ICategoryService categoryService, + IManufacturerService manufacturerService, + IProductAttributeParser productAttributeParser, + IProductService productService, + ICacheManager cacheManager, + ShoppingCartSettings shoppingCartSettings, + CatalogSettings catalogSettings, + ITaxService taxService) + { + this._workContext = workContext; + this._storeContext = storeContext; + this._discountService = discountService; + this._categoryService = categoryService; + this._manufacturerService = manufacturerService; + this._productAttributeParser = productAttributeParser; + this._productService = productService; + this._cacheManager = cacheManager; + this._shoppingCartSettings = shoppingCartSettings; + this._catalogSettings = catalogSettings; + this._taxService = taxService; + } + + #endregion + + #region Nested classes + + [Serializable] + protected class ProductPriceForCaching + { + public ProductPriceForCaching() + { + this.AppliedDiscounts = new List(); + } + + public decimal Price { get; set; } + public decimal AppliedDiscountAmount { get; set; } + public List AppliedDiscounts { get; set; } + } + #endregion + + #region Utilities + + /// + /// Gets allowed discounts applied to product + /// + /// Product + /// Customer + /// Discounts + protected virtual IList GetAllowedDiscountsAppliedToProduct(Product product, Customer customer) + { + var allowedDiscounts = new List(); + if (_catalogSettings.IgnoreDiscounts) + return allowedDiscounts; + + if (product.HasDiscountsApplied) + { + //we use this property ("HasDiscountsApplied") for performance optimization to avoid unnecessary database calls + foreach (var discount in product.AppliedDiscounts) + { + if (_discountService.ValidateDiscount(discount, customer).IsValid && + discount.DiscountType == DiscountType.AssignedToSkus) + allowedDiscounts.Add(discount.MapDiscount()); + } + } + + return allowedDiscounts; + } + + /// + /// Gets allowed discounts applied to categories + /// + /// Product + /// Customer + /// Discounts + protected virtual IList GetAllowedDiscountsAppliedToCategories(Product product, Customer customer) + { + var allowedDiscounts = new List(); + if (_catalogSettings.IgnoreDiscounts) + return allowedDiscounts; + + //load cached discount models (performance optimization) + foreach (var discount in _discountService.GetAllDiscountsForCaching(DiscountType.AssignedToCategories)) + { + //load identifier of categories with this discount applied to + var discountCategoryIds = _discountService.GetAppliedCategoryIds(discount, customer); + + //compare with categories of this product + var productCategoryIds = new List(); + if (discountCategoryIds.Any()) + { + //load identifier of categories of this product + var cacheKey = string.Format(PriceCacheEventConsumer.PRODUCT_CATEGORY_IDS_MODEL_KEY, + product.Id, + string.Join(",", customer.GetCustomerRoleIds()), + _storeContext.CurrentStore.Id); + productCategoryIds = _cacheManager.Get(cacheKey, () => + _categoryService + .GetProductCategoriesByProductId(product.Id) + .Select(x => x.CategoryId) + .ToList()); + } + + foreach (var categoryId in productCategoryIds) + { + if (discountCategoryIds.Contains(categoryId)) + { + if (_discountService.ValidateDiscount(discount, customer).IsValid && + !allowedDiscounts.ContainsDiscount(discount)) + allowedDiscounts.Add(discount); + } + } + } + + return allowedDiscounts; + } + + /// + /// Gets allowed discounts applied to manufacturers + /// + /// Product + /// Customer + /// Discounts + protected virtual IList GetAllowedDiscountsAppliedToManufacturers(Product product, Customer customer) + { + var allowedDiscounts = new List(); + if (_catalogSettings.IgnoreDiscounts) + return allowedDiscounts; + + foreach (var discount in _discountService.GetAllDiscountsForCaching(DiscountType.AssignedToManufacturers)) + { + //load identifier of manufacturers with this discount applied to + var discountManufacturerIds = _discountService.GetAppliedManufacturerIds(discount, customer); + + //compare with manufacturers of this product + var productManufacturerIds = new List(); + if (discountManufacturerIds.Any()) + { + //load identifier of manufacturers of this product + var cacheKey = string.Format(PriceCacheEventConsumer.PRODUCT_MANUFACTURER_IDS_MODEL_KEY, + product.Id, + string.Join(",", customer.GetCustomerRoleIds()), + _storeContext.CurrentStore.Id); + productManufacturerIds = _cacheManager.Get(cacheKey, () => + _manufacturerService + .GetProductManufacturersByProductId(product.Id) + .Select(x => x.ManufacturerId) + .ToList()); + } + + foreach (var manufacturerId in productManufacturerIds) + { + if (discountManufacturerIds.Contains(manufacturerId)) + { + if (_discountService.ValidateDiscount(discount, customer).IsValid && + !allowedDiscounts.ContainsDiscount(discount)) + allowedDiscounts.Add(discount); + } + } + } + + return allowedDiscounts; + } + + /// + /// Gets allowed discounts + /// + /// Product + /// Customer + /// Discounts + protected virtual IList GetAllowedDiscounts(Product product, Customer customer) + { + var allowedDiscounts = new List(); + if (_catalogSettings.IgnoreDiscounts) + return allowedDiscounts; + + //discounts applied to products + foreach (var discount in GetAllowedDiscountsAppliedToProduct(product, customer)) + if (!allowedDiscounts.ContainsDiscount(discount)) + allowedDiscounts.Add(discount); + + //discounts applied to categories + foreach (var discount in GetAllowedDiscountsAppliedToCategories(product, customer)) + if (!allowedDiscounts.ContainsDiscount(discount)) + allowedDiscounts.Add(discount); + + //discounts applied to manufacturers + foreach (var discount in GetAllowedDiscountsAppliedToManufacturers(product, customer)) + if (!allowedDiscounts.ContainsDiscount(discount)) + allowedDiscounts.Add(discount); + + return allowedDiscounts; + } + + /// + /// Gets discount amount + /// + /// Product + /// The customer + /// Already calculated product price without discount + /// Applied discounts + /// Discount amount + protected virtual decimal GetDiscountAmount(Product product, + Customer customer, + decimal productPriceWithoutDiscount, + out List appliedDiscounts) + { + if (product == null) + throw new ArgumentNullException("product"); + + appliedDiscounts = null; + decimal appliedDiscountAmount = decimal.Zero; + + //we don't apply discounts to products with price entered by a customer + if (product.CustomerEntersPrice) + return appliedDiscountAmount; + + //discounts are disabled + if (_catalogSettings.IgnoreDiscounts) + return appliedDiscountAmount; + + var allowedDiscounts = GetAllowedDiscounts(product, customer); + + //no discounts + if (!allowedDiscounts.Any()) + return appliedDiscountAmount; + + appliedDiscounts = allowedDiscounts.GetPreferredDiscount(productPriceWithoutDiscount, out appliedDiscountAmount); + return appliedDiscountAmount; + } + + #endregion + + #region Methods + + /// + /// 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 + /// Final price + public virtual decimal GetFinalPrice(Product product, + Customer customer, + decimal additionalCharge = decimal.Zero, + bool includeDiscounts = true, + int quantity = 1) + { + decimal discountAmount; + List appliedDiscounts; + return GetFinalPrice(product, customer, additionalCharge, includeDiscounts, + quantity, out discountAmount, out appliedDiscounts); + } + /// + /// 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 + /// Applied discount amount + /// Applied discounts + /// Final price + public virtual decimal GetFinalPrice(Product product, + Customer customer, + decimal additionalCharge, + bool includeDiscounts, + int quantity, + out decimal discountAmount, + out List appliedDiscounts) + { + return GetFinalPrice(product, customer, + additionalCharge, includeDiscounts, quantity, + null, null, + out discountAmount, out appliedDiscounts); + } + /// + /// 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 + /// Rental period start date (for rental products) + /// Rental period end date (for rental products) + /// Applied discount amount + /// Applied discounts + /// Final price + public virtual decimal GetFinalPrice(Product product, + Customer customer, + decimal additionalCharge, + bool includeDiscounts, + int quantity, + DateTime? rentalStartDate, + DateTime? rentalEndDate, + out decimal discountAmount, + out List appliedDiscounts) + { + return GetFinalPrice(product, customer, additionalCharge, includeDiscounts, quantity, + rentalStartDate, rentalEndDate, out discountAmount, out appliedDiscounts, null); + } + /// + /// 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 + /// Rental period start date (for rental products) + /// Rental period end date (for rental products) + /// Applied discount amount + /// Applied discounts + /// Overridden product price. If specified, then it'll be used instead of a product price. For example, used with product attribute combinations + /// Final price + public virtual decimal GetFinalPrice(Product product, + Customer customer, + decimal additionalCharge, + bool includeDiscounts, + int quantity, + DateTime? rentalStartDate, + DateTime? rentalEndDate, + out decimal discountAmount, + out List appliedDiscounts, + decimal? overriddenProductPrice) + { + if (product == null) + throw new ArgumentNullException("product"); + + discountAmount = decimal.Zero; + appliedDiscounts = new List(); + + var cacheKey = string.Format(PriceCacheEventConsumer.PRODUCT_PRICE_MODEL_KEY, + product.Id, + overriddenProductPrice.HasValue ? overriddenProductPrice.Value.ToString(CultureInfo.InvariantCulture) : null, + additionalCharge.ToString(CultureInfo.InvariantCulture), + includeDiscounts, + quantity, + string.Join(",", customer.GetCustomerRoleIds()), + _storeContext.CurrentStore.Id); + var cacheTime = _catalogSettings.CacheProductPrices ? 60 : 0; + //we do not cache price for rental products + //otherwise, it can cause memory leaks (to store all possible date period combinations) + if (product.IsRental) + cacheTime = 0; + var cachedPrice = _cacheManager.Get(cacheKey, cacheTime, () => + { + var result = new ProductPriceForCaching(); + + //initial price + decimal price = overriddenProductPrice.HasValue ? overriddenProductPrice.Value : product.Price; + + //tier prices + var tierPrice = product.GetPreferredTierPrice(customer, _storeContext.CurrentStore.Id, quantity); + if (tierPrice != null) + price = tierPrice.Price; + + //additional charge + price = price + additionalCharge; + + //rental products + if (product.IsRental) + if (rentalStartDate.HasValue && rentalEndDate.HasValue) + price = price * product.GetRentalPeriods(rentalStartDate.Value, rentalEndDate.Value); + + if (includeDiscounts) + { + //discount + List tmpAppliedDiscounts; + decimal tmpDiscountAmount = GetDiscountAmount(product, customer, price, out tmpAppliedDiscounts); + price = price - tmpDiscountAmount; + + if (tmpAppliedDiscounts != null) + { + result.AppliedDiscounts = tmpAppliedDiscounts; + result.AppliedDiscountAmount = tmpDiscountAmount; + } + } + + if (price < decimal.Zero) + price = decimal.Zero; + + result.Price = price; + return result; + }); + + if (includeDiscounts) + { + if (cachedPrice.AppliedDiscounts.Any()) + { + appliedDiscounts.AddRange(cachedPrice.AppliedDiscounts); + discountAmount = cachedPrice.AppliedDiscountAmount; + } + } + + return cachedPrice.Price; + } + + + + /// + /// Gets the shopping cart unit price (one item) + /// + /// The shopping cart item + /// A value indicating whether include discounts or not for price computation + /// Shopping cart unit price (one item) + public virtual decimal GetUnitPrice(ShoppingCartItem shoppingCartItem, + bool includeDiscounts = true) + { + decimal discountAmount; + List appliedDiscounts; + return GetUnitPrice(shoppingCartItem, includeDiscounts, + out discountAmount, out appliedDiscounts); + } + /// + /// Gets the shopping cart unit price (one item) + /// + /// The shopping cart item + /// A value indicating whether include discounts or not for price computation + /// Applied discount amount + /// Applied discounts + /// Shopping cart unit price (one item) + public virtual decimal GetUnitPrice(ShoppingCartItem shoppingCartItem, + bool includeDiscounts, + out decimal discountAmount, + out List appliedDiscounts) + { + if (shoppingCartItem == null) + throw new ArgumentNullException("shoppingCartItem"); + var attributesXml = shoppingCartItem.AttributesXml; + bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; + var unitPrice = GetUnitPrice(shoppingCartItem.Product, + shoppingCartItem.Customer, + shoppingCartItem.ShoppingCartType, + shoppingCartItem.Quantity, + ref attributesXml, + shoppingCartItem.CustomerEnteredPrice, + shoppingCartItem.RentalStartDateUtc, + shoppingCartItem.RentalEndDateUtc, + includeDiscounts, + out discountAmount, + out appliedDiscounts); + //update modified attributesXml with attributes taxinfo + shoppingCartItem.AttributesXml = attributesXml; + return unitPrice; + } + /// + /// Gets the shopping cart unit price (one item) + /// + /// Product + /// Customer + /// Shopping cart type + /// Quantity + /// Product atrributes (XML format) + /// Customer entered price (if specified) + /// Rental start date (null for not rental products) + /// Rental end date (null for not rental products) + /// A value indicating whether include discounts or not for price computation + /// Applied discount amount + /// Applied discounts + /// Shopping cart unit price (one item) + public virtual decimal GetUnitPrice(Product product, + Customer customer, + ShoppingCartType shoppingCartType, + int quantity, + ref string attributesXml, + decimal customerEnteredPrice, + DateTime? rentalStartDate, DateTime? rentalEndDate, + bool includeDiscounts, + out decimal discountAmount, + out List appliedDiscounts) + { + if (product == null) + throw new ArgumentNullException("product"); + + if (customer == null) + throw new ArgumentNullException("customer"); + + discountAmount = decimal.Zero; + appliedDiscounts = new List(); + + //taxWeights will use price tax settings + var taxWeightSummary = new TaxSummary(_taxService.PricesIncludeTax()); + + decimal finalPrice = decimal.Zero; + + var combination = _productAttributeParser.FindProductAttributeCombination(product, attributesXml); + + //get taxrate and summarize price of all attributes to build up taxWeightSummary + //needed for VAT calculation of product bundles + //taxrate comes from associated product and not from product + decimal attributesTotalPrice = decimal.Zero; + if (combination != null) + { + var attributeValues = _productAttributeParser.ParseProductAttributeValues(attributesXml); + if (attributeValues != null) + { + decimal taxRate; + foreach (var attributeValue in attributeValues) + { + var attributePrice = GetProductAttributeValuePriceAdjustment(attributeValue); + attributesTotalPrice += attributePrice; + + if (attributeValue.AttributeValueType == AttributeValueType.AssociatedToProduct) + { + //bundled product + var associatedProduct = _productService.GetProductById(attributeValue.AssociatedProductId); + if (associatedProduct != null) + { + //get only the taxrate of associate product, price is not needed + var attributePriceTax = _taxService.GetProductPrice(associatedProduct, 0, customer, out taxRate); + //build taxSummary for tax subdivision + taxWeightSummary.AddRate(taxRate, attributePrice); + } + } + } + } + } + + if (combination != null && combination.OverriddenPrice.HasValue) + { + finalPrice = GetFinalPrice(product, + customer, + decimal.Zero, + includeDiscounts, + quantity, + product.IsRental ? rentalStartDate : null, + product.IsRental ? rentalEndDate : null, + out discountAmount, out appliedDiscounts, + combination.OverriddenPrice.Value); + } + else + { + //get price of a product (with previously calculated price of all attributes) + if (product.CustomerEntersPrice) + { + finalPrice = customerEnteredPrice; + } + else + { + int qty; + if (_shoppingCartSettings.GroupTierPricesForDistinctShoppingCartItems) + { + //the same products with distinct product attributes could be stored as distinct "ShoppingCartItem" records + //so let's find how many of the current products are in the cart + qty = customer.ShoppingCartItems + .Where(x => x.ProductId == product.Id) + .Where(x => x.ShoppingCartType == shoppingCartType) + .Sum(x => x.Quantity); + if (qty == 0) + { + qty = quantity; + } + } + else + { + qty = quantity; + } + finalPrice = GetFinalPrice(product, + customer, + attributesTotalPrice, + includeDiscounts, + qty, + product.IsRental ? rentalStartDate : null, + product.IsRental ? rentalEndDate : null, + out discountAmount, out appliedDiscounts); + } + } + + //rounding + if (_shoppingCartSettings.RoundPricesDuringCalculation) + finalPrice = RoundingHelper.RoundPrice(finalPrice); + + //add tax attributes + if (taxWeightSummary != null && taxWeightSummary.TaxRates.Any()) + { + decimal prodcutTaxRate; + //get taxrate, no price needed + var itemAmount = _taxService.GetProductPrice(product, 0, customer, out prodcutTaxRate); + + //price is overridden or it is product price + attributes price + if (combination != null && !combination.OverriddenPrice.HasValue) + { + var baseProductPrice = finalPrice - attributesTotalPrice; + //add product price if exists + if (baseProductPrice > decimal.Zero) + taxWeightSummary.AddRate(prodcutTaxRate, baseProductPrice); + } + if (taxWeightSummary.TaxRates.Count() > 1 || + (taxWeightSummary.TaxRates.FirstOrDefault().Key != prodcutTaxRate) //use associated product taxRate when different + ) + { + taxWeightSummary.CalculateWeights(); + attributesXml = _productAttributeParser.AddTaxAttribute(attributesXml, taxWeightSummary); + } + } + + return finalPrice; + } + /// + /// Gets the shopping cart item sub total + /// + /// The shopping cart item + /// A value indicating whether include discounts or not for price computation + /// Shopping cart item sub total + public virtual decimal GetSubTotal(ShoppingCartItem shoppingCartItem, + bool includeDiscounts = true) + { + decimal discountAmount; + List appliedDiscounts; + int? maximumDiscountQty; + return GetSubTotal(shoppingCartItem, includeDiscounts, out discountAmount, out appliedDiscounts, out maximumDiscountQty); + } + /// + /// Gets the shopping cart item sub total + /// + /// The shopping cart item + /// A value indicating whether include discounts or not for price computation + /// Applied discount amount + /// Applied discounts + /// Maximum discounted qty. Return not nullable value if discount cannot be applied to ALL items + /// Shopping cart item sub total + public virtual decimal GetSubTotal(ShoppingCartItem shoppingCartItem, + bool includeDiscounts, + out decimal discountAmount, + out List appliedDiscounts, + out int? maximumDiscountQty) + { + if (shoppingCartItem == null) + throw new ArgumentNullException("shoppingCartItem"); + + decimal subTotal; + maximumDiscountQty = null; + + //unit price + var unitPrice = GetUnitPrice(shoppingCartItem, includeDiscounts, + out discountAmount, out appliedDiscounts); + + //discount + if (appliedDiscounts.Any()) + { + //we can properly use "MaximumDiscountedQuantity" property only for one discount (not cumulative ones) + DiscountForCaching oneAndOnlyDiscount = null; + if (appliedDiscounts.Count == 1) + oneAndOnlyDiscount = appliedDiscounts.First(); + + if (oneAndOnlyDiscount != null && + oneAndOnlyDiscount.MaximumDiscountedQuantity.HasValue && + shoppingCartItem.Quantity > oneAndOnlyDiscount.MaximumDiscountedQuantity.Value) + { + maximumDiscountQty = oneAndOnlyDiscount.MaximumDiscountedQuantity.Value; + //we cannot apply discount for all shopping cart items + var discountedQuantity = oneAndOnlyDiscount.MaximumDiscountedQuantity.Value; + var discountedSubTotal = unitPrice * discountedQuantity; + discountAmount = discountAmount * discountedQuantity; + + var notDiscountedQuantity = shoppingCartItem.Quantity - discountedQuantity; + var notDiscountedUnitPrice = GetUnitPrice(shoppingCartItem, false); + var notDiscountedSubTotal = notDiscountedUnitPrice*notDiscountedQuantity; + + subTotal = discountedSubTotal + notDiscountedSubTotal; + } + else + { + //discount is applied to all items (quantity) + //calculate discount amount for all items + discountAmount = discountAmount * shoppingCartItem.Quantity; + + subTotal = unitPrice * shoppingCartItem.Quantity; + } + } + else + { + subTotal = unitPrice * shoppingCartItem.Quantity; + } + return subTotal; + } + + + /// + /// Gets the product cost (one item) + /// + /// Product + /// Shopping cart item attributes in XML + /// Product cost (one item) + public virtual decimal GetProductCost(Product product, string attributesXml) + { + if (product == null) + throw new ArgumentNullException("product"); + + decimal cost = product.ProductCost; + var attributeValues = _productAttributeParser.ParseProductAttributeValues(attributesXml); + foreach (var attributeValue in attributeValues) + { + switch (attributeValue.AttributeValueType) + { + case AttributeValueType.Simple: + { + //simple attribute + cost += attributeValue.Cost; + } + break; + case AttributeValueType.AssociatedToProduct: + { + //bundled product + var associatedProduct = _productService.GetProductById(attributeValue.AssociatedProductId); + if (associatedProduct != null) + cost += associatedProduct.ProductCost * attributeValue.Quantity; + } + break; + default: + break; + } + } + + return cost; + } + + + + /// + /// Get a price adjustment of a product attribute value + /// + /// Product attribute value + /// Price adjustment + public virtual decimal GetProductAttributeValuePriceAdjustment(ProductAttributeValue value) + { + if (value == null) + throw new ArgumentNullException("value"); + + var adjustment = decimal.Zero; + switch (value.AttributeValueType) + { + case AttributeValueType.Simple: + { + //simple attribute + adjustment = value.PriceAdjustment; + } + break; + case AttributeValueType.AssociatedToProduct: + { + //bundled product + var associatedProduct = _productService.GetProductById(value.AssociatedProductId); + if (associatedProduct != null) + { + adjustment = GetFinalPrice(associatedProduct, _workContext.CurrentCustomer, includeDiscounts: true) * value.Quantity; + } + } + break; + default: + break; + } + + return adjustment; + } + + #endregion + } +} diff --git a/src/Libraries/Nop.Services/Catalog/ProductAttributeFormatter.cs b/src/Libraries/Nop.Services/Catalog/ProductAttributeFormatter.cs index 7ec66ba7c1c..9f3e6101b09 100644 --- a/src/Libraries/Nop.Services/Catalog/ProductAttributeFormatter.cs +++ b/src/Libraries/Nop.Services/Catalog/ProductAttributeFormatter.cs @@ -81,13 +81,13 @@ public virtual string FormatAttributes(Product product, string attributesXml) /// A value indicating whether to render product attributes /// A value indicating whether to render gift card attributes /// A value indicating whether to HTML hyperink tags could be rendered (if required) - /// /// A value indicating if attribute VAT should be rendered with price. Decimal.MinusOne is default, i.e. don't render + /// /// A value indicating if attribute VAT should be rendered with price. Null is default, i.e. don't render /// Attributes public virtual string FormatAttributes(Product product, string attributesXml, Customer customer, string serapator = "
    ", bool htmlEncode = true, bool renderPrices = true, bool renderProductAttributes = true, bool renderGiftCardAttributes = true, bool allowHyperlinks = true, - decimal subTotal = decimal.MinusOne) + decimal? subTotal = null) { var result = new StringBuilder(); @@ -168,7 +168,7 @@ public virtual string FormatAttributes(Product product, string attributesXml, attribute.ProductAttribute.GetLocalized(a => a.Name, _workContext.WorkingLanguage.Id), attributeValue.GetLocalized(a => a.Name, _workContext.WorkingLanguage.Id)); - if (renderPrices) + if (renderPrices && _shoppingCartSettings.RenderProductAttributePrices && attribute.AttributeControlType != AttributeControlType.ReadonlyCheckboxes) //prices are off for readonly attributes { decimal taxRate; var attributeValuePriceAdjustment = _priceCalculationService.GetProductAttributeValuePriceAdjustment(attributeValue); @@ -243,31 +243,31 @@ public virtual string FormatAttributes(Product product, string attributesXml, } //attribute tax - if (subTotal != decimal.MinusOne) + if (subTotal != null & subTotal != decimal.Zero) { var taxAttributes = _productAttributeParser.ParseTaxAttribute(attributesXml); - var attribTaxSummary = new TaxSummary(_workContext.TaxDisplayType == TaxDisplayType.IncludingTax); - var attribTax = attribTaxSummary.ParseAttributeRate(subTotal, taxAttributes); - string formattedAttribute = ""; - if (result.Length > 0) - result.Append(serapator); - result.Append(String.Format("{0} {1}
    ", - _localizationService.GetResource("ShoppingCart.VatRate"), - _localizationService.GetResource("Shoppingcart.Totals.OrderAmount"))); - - foreach (KeyValuePair kvp in attribTax) + if (taxAttributes.Count != 0) { - decimal vatpercentage = kvp.Key; - decimal rateAmount = kvp.Value; - decimal taxRate; - var priceAdjustmentBase = _taxService.GetProductPrice(product, rateAmount, customer, out taxRate); - var priceAdjustment = _currencyService.ConvertFromPrimaryStoreCurrency(priceAdjustmentBase, _workContext.WorkingCurrency); - var price = _priceFormatter.FormatPrice(priceAdjustment, false, false); - price = htmlEncode ? HttpUtility.HtmlEncode(price) : price; - formattedAttribute = String.Format("{0} {1}
    ", kvp.Key.ToString(), price); - result.Append(formattedAttribute); - } + var attribTaxSummary = new TaxSummary(_workContext.TaxDisplayType == TaxDisplayType.IncludingTax); + var attribTax = attribTaxSummary.ApplyAttributeRate(subTotal ?? decimal.Zero, taxAttributes); + string formattedAttribute = ""; + if (result.Length > 0) + result.Append(serapator); + result.Append(String.Format("{0} {1}
    ", + _localizationService.GetResource("ShoppingCart.TaxRate"), + _localizationService.GetResource("Shoppingcart.Totals.OrderAmount"))); + foreach (KeyValuePair kvp in attribTax) + { + decimal vatpercentage = kvp.Key; + decimal rateAmount = kvp.Value; + var priceBase = _currencyService.ConvertFromPrimaryStoreCurrency(rateAmount, _workContext.WorkingCurrency); + var price = _priceFormatter.FormatPrice(priceBase, false, false); + price = htmlEncode ? HttpUtility.HtmlEncode(price) : price; + formattedAttribute = String.Format("{0} {1}
    ", kvp.Key.ToString(), price); + result.Append(formattedAttribute); + } + } } return result.ToString(); diff --git a/src/Libraries/Nop.Services/Catalog/ProductAttributeParser.cs b/src/Libraries/Nop.Services/Catalog/ProductAttributeParser.cs index 919e09b352f..42ab1c3dbdd 100644 --- a/src/Libraries/Nop.Services/Catalog/ProductAttributeParser.cs +++ b/src/Libraries/Nop.Services/Catalog/ProductAttributeParser.cs @@ -660,6 +660,7 @@ public virtual IList GenerateAllCombinations(Product product, bool ignor } #endregion + #region taxAttribute /// /// Adds tax subdivision to existing attributesXml @@ -711,8 +712,7 @@ public virtual string AddTaxAttribute(string attributesXml, TaxSummary taxSummar attributeTaxRateElement.SetAttribute("Rate", strVatPer); attributeTaxElement.AppendChild(attributeTaxRateElement); } - attributeTaxRateElement.SetAttribute("RateWeight", taxrate.VatRateWeight.ToString(CultureInfo.InvariantCulture)); - + attributeTaxRateElement.SetAttribute("RateWeight", taxrate.TaxRateWeight.ToString(CultureInfo.InvariantCulture)); } } else @@ -735,7 +735,7 @@ public virtual string AddTaxAttribute(string attributesXml, TaxSummary taxSummar /// Parse ProductAttributesTax /// /// Attributes in XML format - /// SortedDictionary with vatRate and vatRateWeight + /// SortedDictionary with taxRate and taxRateWeight public SortedDictionary ParseTaxAttribute(string attributesXml) { var taxDict = new SortedDictionary(); @@ -754,6 +754,16 @@ public SortedDictionary ParseTaxAttribute(string attributesXml } return taxDict; } + + /// + /// Check if attributesXml contains tax attribute info + /// + /// Attributes in XML format + /// A boolean value indicating if tax attribute info is present + public bool hasTaxInfoInAttributeXML(string attributesXml) + { + return attributesXml.Contains("Font file name - /// Font - protected virtual Font GetFont(string fontFileName) - { - if (fontFileName == null) - throw new ArgumentNullException("fontFileName"); - - string fontPath = Path.Combine(CommonHelper.MapPath("~/App_Data/Pdf/"), fontFileName); - var baseFont = BaseFont.CreateFont(fontPath, BaseFont.IDENTITY_H, BaseFont.EMBEDDED); - var font = new Font(baseFont, 10, Font.NORMAL); - return font; - } - - /// - /// Get font direction - /// - /// Language - /// Font direction - protected virtual int GetDirection(Language lang) - { - return lang.Rtl ? PdfWriter.RUN_DIRECTION_RTL : PdfWriter.RUN_DIRECTION_LTR; - } - - /// - /// Get element alignment - /// - /// Language - /// Is opposite? - /// Element alignment - protected virtual int GetAlignment(Language lang, bool isOpposite = false) - { - //if we need the element to be opposite, like logo etc`. - if (!isOpposite) - return lang.Rtl ? Element.ALIGN_RIGHT : Element.ALIGN_LEFT; - - return lang.Rtl ? Element.ALIGN_LEFT : Element.ALIGN_RIGHT; - } - - #endregion - - #region Methods - - /// - /// Print an order to PDF - /// - /// Order - /// Language identifier; 0 to use a language used when placing an order - /// Vendor identifier to limit products; 0 to to print all products. If specified, then totals won't be printed - /// A path of generated file - public virtual string PrintOrderToPdf(Order order, int languageId = 0, int vendorId = 0) - { - if (order == null) - throw new ArgumentNullException("order"); - - string fileName = string.Format("order_{0}_{1}.pdf", order.OrderGuid, CommonHelper.GenerateRandomDigitCode(4)); - string filePath = Path.Combine(CommonHelper.MapPath("~/content/files/ExportImport"), fileName); - using (var fileStream = new FileStream(filePath, FileMode.Create)) - { - var orders = new List(); - orders.Add(order); - PrintOrdersToPdf(fileStream, orders, languageId, vendorId); - } - return filePath; - } - - /// - /// Print orders to PDF - /// - /// Stream - /// Orders - /// Language identifier; 0 to use a language used when placing an order - /// Vendor identifier to limit products; 0 to to print all products. If specified, then totals won't be printed - public virtual void PrintOrdersToPdf(Stream stream, IList orders, int languageId = 0, int vendorId = 0) - { - if (stream == null) - throw new ArgumentNullException("stream"); - - if (orders == null) - throw new ArgumentNullException("orders"); - - var pageSize = PageSize.A4; - - if (_pdfSettings.LetterPageSizeEnabled) - { - pageSize = PageSize.LETTER; - } - - - var doc = new Document(pageSize); - var pdfWriter = PdfWriter.GetInstance(doc, stream); - doc.Open(); - - //fonts - var titleFont = GetFont(); - titleFont.SetStyle(Font.BOLD); - titleFont.Color = BaseColor.BLACK; - var font = GetFont(); - var attributesFont = GetFont(); - attributesFont.SetStyle(Font.ITALIC); - - int ordCount = orders.Count; - int ordNum = 0; - - foreach (var order in orders) - { - //by default _pdfSettings contains settings for the current active store - //and we need PdfSettings for the store which was used to place an order - //so let's load it based on a store of the current order - var pdfSettingsByStore = _settingContext.LoadSetting(order.StoreId); - - - var lang = _languageService.GetLanguageById(languageId == 0 ? order.CustomerLanguageId : languageId); - if (lang == null || !lang.Published) - lang = _workContext.WorkingLanguage; - - #region Header - - //logo - var logoPicture = _pictureService.GetPictureById(pdfSettingsByStore.LogoPictureId); - var logoExists = logoPicture != null; - - //header - var headerTable = new PdfPTable(logoExists ? 2 : 1); - headerTable.RunDirection = GetDirection(lang); - headerTable.DefaultCell.Border = Rectangle.NO_BORDER; - - //store info - var store = _storeService.GetStoreById(order.StoreId) ?? _storeContext.CurrentStore; - var anchor = new Anchor(store.Url.Trim(new [] { '/' }), font); - anchor.Reference = store.Url; - - var cellHeader = new PdfPCell(new Phrase(String.Format(_localizationService.GetResource("PDFInvoice.Order#", lang.Id), order.CustomOrderNumber), titleFont)); - cellHeader.Phrase.Add(new Phrase(Environment.NewLine)); - cellHeader.Phrase.Add(new Phrase(anchor)); - cellHeader.Phrase.Add(new Phrase(Environment.NewLine)); - cellHeader.Phrase.Add(new Phrase(String.Format(_localizationService.GetResource("PDFInvoice.OrderDate", lang.Id), _dateTimeHelper.ConvertToUserTime(order.CreatedOnUtc, DateTimeKind.Utc).ToString("D", new CultureInfo(lang.LanguageCulture))), font)); - cellHeader.Phrase.Add(new Phrase(Environment.NewLine)); - cellHeader.Phrase.Add(new Phrase(Environment.NewLine)); - cellHeader.HorizontalAlignment = Element.ALIGN_LEFT; - cellHeader.Border = Rectangle.NO_BORDER; - - headerTable.AddCell(cellHeader); - - if (logoExists) - if (lang.Rtl) - headerTable.SetWidths(new[] { 0.2f, 0.8f }); - else - headerTable.SetWidths(new[] { 0.8f, 0.2f }); - headerTable.WidthPercentage = 100f; - - //logo - if (logoExists) - { - var logoFilePath = _pictureService.GetThumbLocalPath(logoPicture, 0, false); - var logo = Image.GetInstance(logoFilePath); - logo.Alignment = GetAlignment(lang, true); - logo.ScaleToFit(65f, 65f); - - var cellLogo = new PdfPCell(); - cellLogo.Border = Rectangle.NO_BORDER; - cellLogo.AddElement(logo); - headerTable.AddCell(cellLogo); - } - doc.Add(headerTable); - - #endregion - - #region Addresses - - var addressTable = new PdfPTable(2); - addressTable.RunDirection = GetDirection(lang); - addressTable.DefaultCell.Border = Rectangle.NO_BORDER; - addressTable.WidthPercentage = 100f; - addressTable.SetWidths(new[] { 50, 50 }); - - //billing info - var billingAddress = new PdfPTable(1); - billingAddress.DefaultCell.Border = Rectangle.NO_BORDER; - billingAddress.RunDirection = GetDirection(lang); - - billingAddress.AddCell(new Paragraph(_localizationService.GetResource("PDFInvoice.BillingInformation", lang.Id), titleFont)); - - if (_addressSettings.CompanyEnabled && !String.IsNullOrEmpty(order.BillingAddress.Company)) - billingAddress.AddCell(new Paragraph(" " + String.Format(_localizationService.GetResource("PDFInvoice.Company", lang.Id), order.BillingAddress.Company), font)); - - billingAddress.AddCell(new Paragraph(" " + String.Format(_localizationService.GetResource("PDFInvoice.Name", lang.Id), order.BillingAddress.FirstName + " " + order.BillingAddress.LastName), font)); - if (_addressSettings.PhoneEnabled) - billingAddress.AddCell(new Paragraph(" " + String.Format(_localizationService.GetResource("PDFInvoice.Phone", lang.Id), order.BillingAddress.PhoneNumber), font)); - if (_addressSettings.FaxEnabled && !String.IsNullOrEmpty(order.BillingAddress.FaxNumber)) - billingAddress.AddCell(new Paragraph(" " + String.Format(_localizationService.GetResource("PDFInvoice.Fax", lang.Id), order.BillingAddress.FaxNumber), font)); - if (_addressSettings.StreetAddressEnabled) - billingAddress.AddCell(new Paragraph(" " + String.Format(_localizationService.GetResource("PDFInvoice.Address", lang.Id), order.BillingAddress.Address1), font)); - if (_addressSettings.StreetAddress2Enabled && !String.IsNullOrEmpty(order.BillingAddress.Address2)) - billingAddress.AddCell(new Paragraph(" " + String.Format(_localizationService.GetResource("PDFInvoice.Address2", lang.Id), order.BillingAddress.Address2), font)); - if (_addressSettings.CityEnabled || _addressSettings.StateProvinceEnabled || _addressSettings.ZipPostalCodeEnabled) - billingAddress.AddCell(new Paragraph(" " + String.Format("{0}, {1} {2}", order.BillingAddress.City, order.BillingAddress.StateProvince != null ? order.BillingAddress.StateProvince.GetLocalized(x => x.Name, lang.Id) : "", order.BillingAddress.ZipPostalCode), font)); - if (_addressSettings.CountryEnabled && order.BillingAddress.Country != null) - billingAddress.AddCell(new Paragraph(" " + order.BillingAddress.Country.GetLocalized(x => x.Name, lang.Id), font)); - - //VAT number - if (!String.IsNullOrEmpty(order.VatNumber)) - billingAddress.AddCell(new Paragraph(" " + String.Format(_localizationService.GetResource("PDFInvoice.VATNumber", lang.Id), order.VatNumber), font)); - - //custom attributes - var customBillingAddressAttributes = _addressAttributeFormatter.FormatAttributes( order.BillingAddress.CustomAttributes); - if (!String.IsNullOrEmpty(customBillingAddressAttributes)) - { - //TODO: we should add padding to each line (in case if we have sevaral custom address attributes) - billingAddress.AddCell(new Paragraph(" " + HtmlHelper.ConvertHtmlToPlainText(customBillingAddressAttributes, true, true), font)); - } - - - - //vendors payment details - if (vendorId == 0) - { - //payment method - var paymentMethod = _paymentService.LoadPaymentMethodBySystemName(order.PaymentMethodSystemName); - string paymentMethodStr = paymentMethod != null ? paymentMethod.GetLocalizedFriendlyName(_localizationService, lang.Id) : order.PaymentMethodSystemName; - if (!String.IsNullOrEmpty(paymentMethodStr)) - { - billingAddress.AddCell(new Paragraph(" ")); - billingAddress.AddCell(new Paragraph(" " + String.Format(_localizationService.GetResource("PDFInvoice.PaymentMethod", lang.Id), paymentMethodStr), font)); - billingAddress.AddCell(new Paragraph()); - } - - //custom values - var customValues = order.DeserializeCustomValues(); - if (customValues != null) - { - foreach (var item in customValues) - { - billingAddress.AddCell(new Paragraph(" ")); - billingAddress.AddCell(new Paragraph(" " + item.Key + ": " + item.Value, font)); - billingAddress.AddCell(new Paragraph()); - } - } - } - addressTable.AddCell(billingAddress); - - //shipping info - var shippingAddress = new PdfPTable(1); - shippingAddress.DefaultCell.Border = Rectangle.NO_BORDER; - shippingAddress.RunDirection = GetDirection(lang); - - if (order.ShippingStatus != ShippingStatus.ShippingNotRequired) - { - //cell = new PdfPCell(); - //cell.Border = Rectangle.NO_BORDER; - - if (!order.PickUpInStore) - { - if (order.ShippingAddress == null) - throw new NopException(string.Format("Shipping is required, but address is not available. Order ID = {0}", order.Id)); - - shippingAddress.AddCell(new Paragraph(_localizationService.GetResource("PDFInvoice.ShippingInformation", lang.Id), titleFont)); - if (!String.IsNullOrEmpty(order.ShippingAddress.Company)) - shippingAddress.AddCell(new Paragraph(" " + String.Format(_localizationService.GetResource("PDFInvoice.Company", lang.Id), order.ShippingAddress.Company), font)); - shippingAddress.AddCell(new Paragraph(" " + String.Format(_localizationService.GetResource("PDFInvoice.Name", lang.Id), order.ShippingAddress.FirstName + " " + order.ShippingAddress.LastName), font)); - if (_addressSettings.PhoneEnabled) - shippingAddress.AddCell(new Paragraph(" " + String.Format(_localizationService.GetResource("PDFInvoice.Phone", lang.Id), order.ShippingAddress.PhoneNumber), font)); - if (_addressSettings.FaxEnabled && !String.IsNullOrEmpty(order.ShippingAddress.FaxNumber)) - shippingAddress.AddCell(new Paragraph(" " + String.Format(_localizationService.GetResource("PDFInvoice.Fax", lang.Id), order.ShippingAddress.FaxNumber), font)); - if (_addressSettings.StreetAddressEnabled) - shippingAddress.AddCell(new Paragraph(" " + String.Format(_localizationService.GetResource("PDFInvoice.Address", lang.Id), order.ShippingAddress.Address1), font)); - if (_addressSettings.StreetAddress2Enabled && !String.IsNullOrEmpty(order.ShippingAddress.Address2)) - shippingAddress.AddCell(new Paragraph(" " + String.Format(_localizationService.GetResource("PDFInvoice.Address2", lang.Id), order.ShippingAddress.Address2), font)); - if (_addressSettings.CityEnabled || _addressSettings.StateProvinceEnabled || _addressSettings.ZipPostalCodeEnabled) - shippingAddress.AddCell(new Paragraph(" " + String.Format("{0}, {1} {2}", order.ShippingAddress.City, order.ShippingAddress.StateProvince != null ? order.ShippingAddress.StateProvince.GetLocalized(x => x.Name, lang.Id) : "", order.ShippingAddress.ZipPostalCode), font)); - if (_addressSettings.CountryEnabled && order.ShippingAddress.Country != null) - shippingAddress.AddCell(new Paragraph(" " + order.ShippingAddress.Country.GetLocalized(x => x.Name, lang.Id), font)); - //custom attributes - var customShippingAddressAttributes = _addressAttributeFormatter.FormatAttributes(order.ShippingAddress.CustomAttributes); - if (!String.IsNullOrEmpty(customShippingAddressAttributes)) - { - //TODO: we should add padding to each line (in case if we have sevaral custom address attributes) - shippingAddress.AddCell(new Paragraph(" " + HtmlHelper.ConvertHtmlToPlainText(customShippingAddressAttributes, true, true), font)); - } - shippingAddress.AddCell(new Paragraph(" ")); - } - else - if (order.PickupAddress != null) - { - shippingAddress.AddCell(new Paragraph(_localizationService.GetResource("PDFInvoice.Pickup", lang.Id), titleFont)); - if (!string.IsNullOrEmpty(order.PickupAddress.Address1)) - shippingAddress.AddCell(new Paragraph(string.Format(" {0}", string.Format(_localizationService.GetResource("PDFInvoice.Address", lang.Id), order.PickupAddress.Address1)), font)); - if (!string.IsNullOrEmpty(order.PickupAddress.City)) - shippingAddress.AddCell(new Paragraph(string.Format(" {0}", order.PickupAddress.City), font)); - if (order.PickupAddress.Country != null) - shippingAddress.AddCell(new Paragraph(string.Format(" {0}", order.PickupAddress.Country.GetLocalized(x => x.Name, lang.Id)), font)); - if (!string.IsNullOrEmpty(order.PickupAddress.ZipPostalCode)) - shippingAddress.AddCell(new Paragraph(string.Format(" {0}", order.PickupAddress.ZipPostalCode), font)); - shippingAddress.AddCell(new Paragraph(" ")); - } - shippingAddress.AddCell(new Paragraph(" " + String.Format(_localizationService.GetResource("PDFInvoice.ShippingMethod", lang.Id), order.ShippingMethod), font)); - shippingAddress.AddCell(new Paragraph()); - - addressTable.AddCell(shippingAddress); - } - else - { - shippingAddress.AddCell(new Paragraph()); - addressTable.AddCell(shippingAddress); - } - - doc.Add(addressTable); - doc.Add(new Paragraph(" ")); - - #endregion - - #region Products - - //products - var productsHeader = new PdfPTable(1); - productsHeader.RunDirection = GetDirection(lang); - productsHeader.WidthPercentage = 100f; - var cellProducts = new PdfPCell(new Phrase(_localizationService.GetResource("PDFInvoice.Product(s)", lang.Id), titleFont)); - cellProducts.Border = Rectangle.NO_BORDER; - productsHeader.AddCell(cellProducts); - doc.Add(productsHeader); - doc.Add(new Paragraph(" ")); - - - var orderItems = order.OrderItems; - - var productsTable = new PdfPTable(_catalogSettings.ShowSkuOnProductDetailsPage ? 5 : 4); - productsTable.RunDirection = GetDirection(lang); - productsTable.WidthPercentage = 100f; - if (lang.Rtl) - { - productsTable.SetWidths(_catalogSettings.ShowSkuOnProductDetailsPage - ? new[] {15, 10, 15, 15, 45} - : new[] {20, 10, 20, 50}); - } - else - { - productsTable.SetWidths(_catalogSettings.ShowSkuOnProductDetailsPage - ? new[] {45, 15, 15, 10, 15} - : new[] {50, 20, 10, 20}); - } - - //product name - var cellProductItem = new PdfPCell(new Phrase(_localizationService.GetResource("PDFInvoice.ProductName", lang.Id), font)); - cellProductItem.BackgroundColor = BaseColor.LIGHT_GRAY; - cellProductItem.HorizontalAlignment = Element.ALIGN_CENTER; - productsTable.AddCell(cellProductItem); - - //SKU - if (_catalogSettings.ShowSkuOnProductDetailsPage) - { - cellProductItem = new PdfPCell(new Phrase(_localizationService.GetResource("PDFInvoice.SKU", lang.Id), font)); - cellProductItem.BackgroundColor = BaseColor.LIGHT_GRAY; - cellProductItem.HorizontalAlignment = Element.ALIGN_CENTER; - productsTable.AddCell(cellProductItem); - } - - //price - cellProductItem = new PdfPCell(new Phrase(_localizationService.GetResource("PDFInvoice.ProductPrice", lang.Id), font)); - cellProductItem.BackgroundColor = BaseColor.LIGHT_GRAY; - cellProductItem.HorizontalAlignment = Element.ALIGN_CENTER; - productsTable.AddCell(cellProductItem); - - //qty - cellProductItem = new PdfPCell(new Phrase(_localizationService.GetResource("PDFInvoice.ProductQuantity", lang.Id), font)); - cellProductItem.BackgroundColor = BaseColor.LIGHT_GRAY; - cellProductItem.HorizontalAlignment = Element.ALIGN_CENTER; - productsTable.AddCell(cellProductItem); - - //total - cellProductItem = new PdfPCell(new Phrase(_localizationService.GetResource("PDFInvoice.ProductTotal", lang.Id), font)); - cellProductItem.BackgroundColor = BaseColor.LIGHT_GRAY; - cellProductItem.HorizontalAlignment = Element.ALIGN_CENTER; - productsTable.AddCell(cellProductItem); - - foreach (var orderItem in orderItems) - { - var p = orderItem.Product; - - //a vendor should have access only to his products - if (vendorId > 0 && p.VendorId != vendorId) - continue; - - var pAttribTable = new PdfPTable(1); - pAttribTable.RunDirection = GetDirection(lang); - pAttribTable.DefaultCell.Border = Rectangle.NO_BORDER; - - //product name - string name = p.GetLocalized(x => x.Name, lang.Id); - pAttribTable.AddCell(new Paragraph(name, font)); - cellProductItem.AddElement(new Paragraph(name, font)); - //attributes - if (!String.IsNullOrEmpty(orderItem.AttributeDescription)) - { - var attributesParagraph = new Paragraph(HtmlHelper.ConvertHtmlToPlainText(orderItem.AttributeDescription, true, true), attributesFont); - pAttribTable.AddCell(attributesParagraph); - } - //rental info - if (orderItem.Product.IsRental) - { - var rentalStartDate = orderItem.RentalStartDateUtc.HasValue ? orderItem.Product.FormatRentalDate(orderItem.RentalStartDateUtc.Value) : ""; - var rentalEndDate = orderItem.RentalEndDateUtc.HasValue ? orderItem.Product.FormatRentalDate(orderItem.RentalEndDateUtc.Value) : ""; - var rentalInfo = string.Format(_localizationService.GetResource("Order.Rental.FormattedDate"), - rentalStartDate, rentalEndDate); - - var rentalInfoParagraph = new Paragraph(rentalInfo, attributesFont); - pAttribTable.AddCell(rentalInfoParagraph); - } - productsTable.AddCell(pAttribTable); - - //SKU - if (_catalogSettings.ShowSkuOnProductDetailsPage) - { - var sku = p.FormatSku(orderItem.AttributesXml, _productAttributeParser); - cellProductItem = new PdfPCell(new Phrase(sku ?? String.Empty, font)); - cellProductItem.HorizontalAlignment = Element.ALIGN_CENTER; - productsTable.AddCell(cellProductItem); - } - - //price - string unitPrice; - if (order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax) - { - //including tax - var unitPriceInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(orderItem.UnitPriceInclTax, order.CurrencyRate); - unitPrice = _priceFormatter.FormatPrice(unitPriceInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, lang, true); - } - else - { - //excluding tax - var unitPriceExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(orderItem.UnitPriceExclTax, order.CurrencyRate); - unitPrice = _priceFormatter.FormatPrice(unitPriceExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, lang, false); - } - cellProductItem = new PdfPCell(new Phrase(unitPrice, font)); - cellProductItem.HorizontalAlignment = Element.ALIGN_LEFT; - productsTable.AddCell(cellProductItem); - - //qty - cellProductItem = new PdfPCell(new Phrase(orderItem.Quantity.ToString(), font)); - cellProductItem.HorizontalAlignment = Element.ALIGN_LEFT; - productsTable.AddCell(cellProductItem); - - //total - string subTotal; - if (order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax) - { - //including tax - var priceInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(orderItem.PriceInclTax, order.CurrencyRate); - subTotal = _priceFormatter.FormatPrice(priceInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, lang, true); - } - else - { - //excluding tax - var priceExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(orderItem.PriceExclTax, order.CurrencyRate); - subTotal = _priceFormatter.FormatPrice(priceExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, lang, false); - } - cellProductItem = new PdfPCell(new Phrase(subTotal, font)); - cellProductItem.HorizontalAlignment = Element.ALIGN_LEFT; - productsTable.AddCell(cellProductItem); - } - doc.Add(productsTable); - - #endregion - - #region Checkout attributes - - //vendors cannot see checkout attributes - if (vendorId == 0 && !String.IsNullOrEmpty(order.CheckoutAttributeDescription)) - { - doc.Add(new Paragraph(" ")); - var attribTable = new PdfPTable(1); - attribTable.RunDirection = GetDirection(lang); - attribTable.WidthPercentage = 100f; - - string attributes = HtmlHelper.ConvertHtmlToPlainText(order.CheckoutAttributeDescription, true, true); - var cCheckoutAttributes = new PdfPCell(new Phrase(attributes, font)); - cCheckoutAttributes.Border = Rectangle.NO_BORDER; - cCheckoutAttributes.HorizontalAlignment = Element.ALIGN_RIGHT; - attribTable.AddCell(cCheckoutAttributes); - doc.Add(attribTable); - } - - #endregion - - #region Totals - - //vendors cannot see totals - if (vendorId == 0) - { - //subtotal - var totalsTable = new PdfPTable(1); - totalsTable.RunDirection = GetDirection(lang); - totalsTable.DefaultCell.Border = Rectangle.NO_BORDER; - totalsTable.WidthPercentage = 100f; - - //order subtotal - if (order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax && !_taxSettings.ForceTaxExclusionFromOrderSubtotal) - { - //including tax - - var orderSubtotalInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubtotalInclTax, order.CurrencyRate); - string orderSubtotalInclTaxStr = _priceFormatter.FormatPrice(orderSubtotalInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, lang, true); - - var p = new PdfPCell(new Paragraph(String.Format("{0} {1}", _localizationService.GetResource("PDFInvoice.Sub-Total", lang.Id), orderSubtotalInclTaxStr), font)); - p.HorizontalAlignment = Element.ALIGN_RIGHT; - p.Border = Rectangle.NO_BORDER; - totalsTable.AddCell(p); - } - else - { - //excluding tax - - var orderSubtotalExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubtotalExclTax, order.CurrencyRate); - string orderSubtotalExclTaxStr = _priceFormatter.FormatPrice(orderSubtotalExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, lang, false); - - var p = new PdfPCell(new Paragraph(String.Format("{0} {1}", _localizationService.GetResource("PDFInvoice.Sub-Total", lang.Id), orderSubtotalExclTaxStr), font)); - p.HorizontalAlignment = Element.ALIGN_RIGHT; - p.Border = Rectangle.NO_BORDER; - totalsTable.AddCell(p); - } - - //discount (applied to order subtotal) - if (order.OrderSubTotalDiscountExclTax > decimal.Zero) - { - //order subtotal - if (order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax && !_taxSettings.ForceTaxExclusionFromOrderSubtotal) - { - //including tax - - var orderSubTotalDiscountInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubTotalDiscountInclTax, order.CurrencyRate); - string orderSubTotalDiscountInCustomerCurrencyStr = _priceFormatter.FormatPrice(-orderSubTotalDiscountInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, lang, true); - - var p = new PdfPCell(new Paragraph(String.Format("{0} {1}", _localizationService.GetResource("PDFInvoice.Discount", lang.Id), orderSubTotalDiscountInCustomerCurrencyStr), font)); - p.HorizontalAlignment = Element.ALIGN_RIGHT; - p.Border = Rectangle.NO_BORDER; - totalsTable.AddCell(p); - } - else - { - //excluding tax - - var orderSubTotalDiscountExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubTotalDiscountExclTax, order.CurrencyRate); - string orderSubTotalDiscountInCustomerCurrencyStr = _priceFormatter.FormatPrice(-orderSubTotalDiscountExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, lang, false); - - var p = new PdfPCell(new Paragraph(String.Format("{0} {1}", _localizationService.GetResource("PDFInvoice.Discount", lang.Id), orderSubTotalDiscountInCustomerCurrencyStr), font)); - p.HorizontalAlignment = Element.ALIGN_RIGHT; - p.Border = Rectangle.NO_BORDER; - totalsTable.AddCell(p); - } - } - - //shipping - if (order.ShippingStatus != ShippingStatus.ShippingNotRequired) - { - if (order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax) - { - //including tax - var orderShippingInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderShippingInclTax, order.CurrencyRate); - string orderShippingInclTaxStr = _priceFormatter.FormatShippingPrice(orderShippingInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, lang, true); - - var p = new PdfPCell(new Paragraph(String.Format("{0} {1}", _localizationService.GetResource("PDFInvoice.Shipping", lang.Id), orderShippingInclTaxStr), font)); - p.HorizontalAlignment = Element.ALIGN_RIGHT; - p.Border = Rectangle.NO_BORDER; - totalsTable.AddCell(p); - } - else - { - //excluding tax - var orderShippingExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderShippingExclTax, order.CurrencyRate); - string orderShippingExclTaxStr = _priceFormatter.FormatShippingPrice(orderShippingExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, lang, false); - - var p = new PdfPCell(new Paragraph(String.Format("{0} {1}", _localizationService.GetResource("PDFInvoice.Shipping", lang.Id), orderShippingExclTaxStr), font)); - p.HorizontalAlignment = Element.ALIGN_RIGHT; - p.Border = Rectangle.NO_BORDER; - totalsTable.AddCell(p); - } - } - - //payment fee - if (order.PaymentMethodAdditionalFeeExclTax > decimal.Zero) - { - if (order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax) - { - //including tax - var paymentMethodAdditionalFeeInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.PaymentMethodAdditionalFeeInclTax, order.CurrencyRate); - string paymentMethodAdditionalFeeInclTaxStr = _priceFormatter.FormatPaymentMethodAdditionalFee(paymentMethodAdditionalFeeInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, lang, true); - - var p = new PdfPCell(new Paragraph(String.Format("{0} {1}", _localizationService.GetResource("PDFInvoice.PaymentMethodAdditionalFee", lang.Id), paymentMethodAdditionalFeeInclTaxStr), font)); - p.HorizontalAlignment = Element.ALIGN_RIGHT; - p.Border = Rectangle.NO_BORDER; - totalsTable.AddCell(p); - } - else - { - //excluding tax - var paymentMethodAdditionalFeeExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.PaymentMethodAdditionalFeeExclTax, order.CurrencyRate); - string paymentMethodAdditionalFeeExclTaxStr = _priceFormatter.FormatPaymentMethodAdditionalFee(paymentMethodAdditionalFeeExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, lang, false); - - var p = new PdfPCell(new Paragraph(String.Format("{0} {1}", _localizationService.GetResource("PDFInvoice.PaymentMethodAdditionalFee", lang.Id), paymentMethodAdditionalFeeExclTaxStr), font)); - p.HorizontalAlignment = Element.ALIGN_RIGHT; - p.Border = Rectangle.NO_BORDER; - totalsTable.AddCell(p); - } - } - - //tax - string taxStr = string.Empty; - var taxRates = new SortedDictionary(); - bool displayTax = true; - bool displayTaxRates = true; - if (_taxSettings.HideTaxInOrderSummary && order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax) - { - displayTax = false; - } - else - { - if (order.OrderTax == 0 && _taxSettings.HideZeroTax) - { - displayTax = false; - displayTaxRates = false; - } - else - { - taxRates = order.TaxRatesDictionary; - - displayTaxRates = _taxSettings.DisplayTaxRates && taxRates.Any(); - displayTax = !displayTaxRates; - - var orderTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderTax, order.CurrencyRate); - taxStr = _priceFormatter.FormatPrice(orderTaxInCustomerCurrency, true, order.CustomerCurrencyCode, false, lang); - } - } - if (displayTax) - { - var p = new PdfPCell(new Paragraph(String.Format("{0} {1}", _localizationService.GetResource("PDFInvoice.Tax", lang.Id), taxStr), font)); - p.HorizontalAlignment = Element.ALIGN_RIGHT; - p.Border = Rectangle.NO_BORDER; - totalsTable.AddCell(p); - } - if (displayTaxRates) - { - foreach (var item in taxRates) - { - string taxRate = String.Format(_localizationService.GetResource("PDFInvoice.TaxRate", lang.Id), _priceFormatter.FormatTaxRate(item.Key)); - string taxValue = _priceFormatter.FormatPrice(_currencyService.ConvertCurrency(item.Value.VatAmount, order.CurrencyRate), true, order.CustomerCurrencyCode, false, lang); - - var p = new PdfPCell(new Paragraph(String.Format("{0} {1}", taxRate, taxValue), font)); - p.HorizontalAlignment = Element.ALIGN_RIGHT; - p.Border = Rectangle.NO_BORDER; - totalsTable.AddCell(p); - } - } - - //discount (applied to order total) - if (order.OrderDiscount > decimal.Zero) - { - var orderDiscountInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderDiscount, order.CurrencyRate); - string orderDiscountInCustomerCurrencyStr = _priceFormatter.FormatPrice(-orderDiscountInCustomerCurrency, true, order.CustomerCurrencyCode, false, lang); - - var p = new PdfPCell(new Paragraph(String.Format("{0} {1}", _localizationService.GetResource("PDFInvoice.Discount", lang.Id), orderDiscountInCustomerCurrencyStr), font)); - p.HorizontalAlignment = Element.ALIGN_RIGHT; - p.Border = Rectangle.NO_BORDER; - totalsTable.AddCell(p); - } - - //gift cards - foreach (var gcuh in order.GiftCardUsageHistory) - { - string gcTitle = string.Format(_localizationService.GetResource("PDFInvoice.GiftCardInfo", lang.Id), gcuh.GiftCard.GiftCardCouponCode); - string gcAmountStr = _priceFormatter.FormatPrice(-(_currencyService.ConvertCurrency(gcuh.UsedValue, order.CurrencyRate)), true, order.CustomerCurrencyCode, false, lang); - - var p = new PdfPCell(new Paragraph(String.Format("{0} {1}", gcTitle, gcAmountStr), font)); - p.HorizontalAlignment = Element.ALIGN_RIGHT; - p.Border = Rectangle.NO_BORDER; - totalsTable.AddCell(p); - } - - //reward points - if (order.RedeemedRewardPointsEntry != null) - { - string rpTitle = string.Format(_localizationService.GetResource("PDFInvoice.RewardPoints", lang.Id), -order.RedeemedRewardPointsEntry.Points); - string rpAmount = _priceFormatter.FormatPrice(-(_currencyService.ConvertCurrency(order.RedeemedRewardPointsEntry.UsedAmount, order.CurrencyRate)), true, order.CustomerCurrencyCode, false, lang); - - var p = new PdfPCell(new Paragraph(String.Format("{0} {1}", rpTitle, rpAmount), font)); - p.HorizontalAlignment = Element.ALIGN_RIGHT; - p.Border = Rectangle.NO_BORDER; - totalsTable.AddCell(p); - } - - //order total - var orderTotalInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderTotal, order.CurrencyRate); - string orderTotalStr = _priceFormatter.FormatPrice(orderTotalInCustomerCurrency, true, order.CustomerCurrencyCode, false, lang); - - - var pTotal = new PdfPCell(new Paragraph(String.Format("{0} {1}", _localizationService.GetResource("PDFInvoice.OrderTotal", lang.Id), orderTotalStr), titleFont)); - pTotal.HorizontalAlignment = Element.ALIGN_RIGHT; - pTotal.Border = Rectangle.NO_BORDER; - totalsTable.AddCell(pTotal); - - doc.Add(totalsTable); - } - - #endregion - - #region Order notes - - if (pdfSettingsByStore.RenderOrderNotes) - { - var orderNotes = order.OrderNotes - .Where(on => on.DisplayToCustomer) - .OrderByDescending(on => on.CreatedOnUtc) - .ToList(); - if (orderNotes.Any()) - { - var notesHeader = new PdfPTable(1); - notesHeader.RunDirection = GetDirection(lang); - notesHeader.WidthPercentage = 100f; - var cellOrderNote = new PdfPCell(new Phrase(_localizationService.GetResource("PDFInvoice.OrderNotes", lang.Id), titleFont)); - cellOrderNote.Border = Rectangle.NO_BORDER; - notesHeader.AddCell(cellOrderNote); - doc.Add(notesHeader); - doc.Add(new Paragraph(" ")); - - var notesTable = new PdfPTable(2); - notesTable.RunDirection = GetDirection(lang); - if (lang.Rtl) - { - notesTable.SetWidths(new[] {70, 30}); - } - else - { - notesTable.SetWidths(new[] {30, 70}); - } - notesTable.WidthPercentage = 100f; - - //created on - cellOrderNote = new PdfPCell(new Phrase(_localizationService.GetResource("PDFInvoice.OrderNotes.CreatedOn", lang.Id), font)); - cellOrderNote.BackgroundColor = BaseColor.LIGHT_GRAY; - cellOrderNote.HorizontalAlignment = Element.ALIGN_CENTER; - notesTable.AddCell(cellOrderNote); - - //note - cellOrderNote = new PdfPCell(new Phrase(_localizationService.GetResource("PDFInvoice.OrderNotes.Note", lang.Id), font)); - cellOrderNote.BackgroundColor = BaseColor.LIGHT_GRAY; - cellOrderNote.HorizontalAlignment = Element.ALIGN_CENTER; - notesTable.AddCell(cellOrderNote); - - foreach (var orderNote in orderNotes) - { - cellOrderNote = new PdfPCell(new Phrase(_dateTimeHelper.ConvertToUserTime(orderNote.CreatedOnUtc, DateTimeKind.Utc).ToString(), font)); - cellOrderNote.HorizontalAlignment = Element.ALIGN_LEFT; - notesTable.AddCell(cellOrderNote); - - cellOrderNote = new PdfPCell(new Phrase(HtmlHelper.ConvertHtmlToPlainText(orderNote.FormatOrderNoteText(), true, true), font)); - cellOrderNote.HorizontalAlignment = Element.ALIGN_LEFT; - notesTable.AddCell(cellOrderNote); - - //should we display a link to downloadable files here? - //I think, no. Onyway, PDFs are printable documents and links (files) are useful here - } - doc.Add(notesTable); - } - } - - #endregion - - #region Footer - - if (!String.IsNullOrEmpty(pdfSettingsByStore.InvoiceFooterTextColumn1) || !String.IsNullOrEmpty(pdfSettingsByStore.InvoiceFooterTextColumn2)) - { - var column1Lines = String.IsNullOrEmpty(pdfSettingsByStore.InvoiceFooterTextColumn1) ? - new List() : - pdfSettingsByStore.InvoiceFooterTextColumn1 - .Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries) - .ToList(); - var column2Lines = String.IsNullOrEmpty(pdfSettingsByStore.InvoiceFooterTextColumn2) ? - new List() : - pdfSettingsByStore.InvoiceFooterTextColumn2 - .Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries) - .ToList(); - if (column1Lines.Any() || column2Lines.Any()) - { - var totalLines = Math.Max(column1Lines.Count, column2Lines.Count); - const float margin = 43; - - //if you have really a lot of lines in the footer, then replace 9 with 10 or 11 - int footerHeight = totalLines * 9; - var directContent = pdfWriter.DirectContent; - directContent.MoveTo(pageSize.GetLeft(margin), pageSize.GetBottom(margin) + footerHeight); - directContent.LineTo(pageSize.GetRight(margin), pageSize.GetBottom(margin) + footerHeight); - directContent.Stroke(); - - - var footerTable = new PdfPTable(2); - footerTable.WidthPercentage = 100f; - footerTable.SetTotalWidth(new float[] { 250, 250 }); - footerTable.RunDirection = GetDirection(lang); - - //column 1 - if (column1Lines.Any()) - { - var column1 = new PdfPCell(new Phrase()); - column1.Border = Rectangle.NO_BORDER; - column1.HorizontalAlignment = Element.ALIGN_LEFT; - foreach (var footerLine in column1Lines) - { - column1.Phrase.Add(new Phrase(footerLine, font)); - column1.Phrase.Add(new Phrase(Environment.NewLine)); - } - footerTable.AddCell(column1); - } - else - { - var column = new PdfPCell(new Phrase(" ")); - column.Border = Rectangle.NO_BORDER; - footerTable.AddCell(column); - } - - //column 2 - if (column2Lines.Any()) - { - var column2 = new PdfPCell(new Phrase()); - column2.Border = Rectangle.NO_BORDER; - column2.HorizontalAlignment = Element.ALIGN_LEFT; - foreach (var footerLine in column2Lines) - { - column2.Phrase.Add(new Phrase(footerLine, font)); - column2.Phrase.Add(new Phrase(Environment.NewLine)); - } - footerTable.AddCell(column2); - } - else - { - var column = new PdfPCell(new Phrase(" ")); - column.Border = Rectangle.NO_BORDER; - footerTable.AddCell(column); - } - - footerTable.WriteSelectedRows(0, totalLines, pageSize.GetLeft(margin), pageSize.GetBottom(margin) + footerHeight, directContent); - } - } - - #endregion - - ordNum++; - if (ordNum < ordCount) - { - doc.NewPage(); - } - } - doc.Close(); - } - - /// - /// Print packaging slips to PDF - /// - /// Stream - /// Shipments - /// Language identifier; 0 to use a language used when placing an order - public virtual void PrintPackagingSlipsToPdf(Stream stream, IList shipments, int languageId = 0) - { - if (stream == null) - throw new ArgumentNullException("stream"); - - if (shipments == null) - throw new ArgumentNullException("shipments"); - - var pageSize = PageSize.A4; - - if (_pdfSettings.LetterPageSizeEnabled) - { - pageSize = PageSize.LETTER; - } - - var doc = new Document(pageSize); - PdfWriter.GetInstance(doc, stream); - doc.Open(); - - //fonts - var titleFont = GetFont(); - titleFont.SetStyle(Font.BOLD); - titleFont.Color = BaseColor.BLACK; - var font = GetFont(); - var attributesFont = GetFont(); - attributesFont.SetStyle(Font.ITALIC); - - int shipmentCount = shipments.Count; - int shipmentNum = 0; - - foreach (var shipment in shipments) - { - var order = shipment.Order; - - var lang = _languageService.GetLanguageById(languageId == 0 ? order.CustomerLanguageId : languageId); - if (lang == null || !lang.Published) - lang = _workContext.WorkingLanguage; - - var addressTable = new PdfPTable(1); - if (lang.Rtl) - addressTable.RunDirection = PdfWriter.RUN_DIRECTION_RTL; - addressTable.DefaultCell.Border = Rectangle.NO_BORDER; - addressTable.WidthPercentage = 100f; - - addressTable.AddCell(new Paragraph(String.Format(_localizationService.GetResource("PDFPackagingSlip.Shipment", lang.Id), shipment.Id), titleFont)); - addressTable.AddCell(new Paragraph(String.Format(_localizationService.GetResource("PDFPackagingSlip.Order", lang.Id), order.CustomOrderNumber), titleFont)); - - if (!order.PickUpInStore) - { - if (order.ShippingAddress == null) - throw new NopException(string.Format("Shipping is required, but address is not available. Order ID = {0}", order.Id)); - - if (_addressSettings.CompanyEnabled && !String.IsNullOrEmpty(order.ShippingAddress.Company)) - addressTable.AddCell(new Paragraph(String.Format(_localizationService.GetResource("PDFPackagingSlip.Company", lang.Id), - order.ShippingAddress.Company), font)); - - addressTable.AddCell(new Paragraph(String.Format(_localizationService.GetResource("PDFPackagingSlip.Name", lang.Id), - order.ShippingAddress.FirstName + " " + order.ShippingAddress.LastName), font)); - if (_addressSettings.PhoneEnabled) - addressTable.AddCell(new Paragraph(String.Format(_localizationService.GetResource("PDFPackagingSlip.Phone", lang.Id), - order.ShippingAddress.PhoneNumber), font)); - if (_addressSettings.StreetAddressEnabled) - addressTable.AddCell(new Paragraph(String.Format(_localizationService.GetResource("PDFPackagingSlip.Address", lang.Id), - order.ShippingAddress.Address1), font)); - - if (_addressSettings.StreetAddress2Enabled && !String.IsNullOrEmpty(order.ShippingAddress.Address2)) - addressTable.AddCell(new Paragraph(String.Format(_localizationService.GetResource("PDFPackagingSlip.Address2", lang.Id), - order.ShippingAddress.Address2), font)); - - if (_addressSettings.CityEnabled || _addressSettings.StateProvinceEnabled || _addressSettings.ZipPostalCodeEnabled) - addressTable.AddCell(new Paragraph(String.Format("{0}, {1} {2}", order.ShippingAddress.City, order.ShippingAddress.StateProvince != null - ? order.ShippingAddress.StateProvince.GetLocalized(x => x.Name, lang.Id) - : "", order.ShippingAddress.ZipPostalCode), font)); - - if (_addressSettings.CountryEnabled && order.ShippingAddress.Country != null) - addressTable.AddCell(new Paragraph(order.ShippingAddress.Country.GetLocalized(x => x.Name, lang.Id), font)); - - //custom attributes - var customShippingAddressAttributes = _addressAttributeFormatter.FormatAttributes(order.ShippingAddress.CustomAttributes); - if (!String.IsNullOrEmpty(customShippingAddressAttributes)) - { - addressTable.AddCell(new Paragraph(HtmlHelper.ConvertHtmlToPlainText(customShippingAddressAttributes, true, true), font)); - } - } - else - if (order.PickupAddress != null) - { - addressTable.AddCell(new Paragraph(_localizationService.GetResource("PDFInvoice.Pickup", lang.Id), titleFont)); - if (!string.IsNullOrEmpty(order.PickupAddress.Address1)) - addressTable.AddCell(new Paragraph(string.Format(" {0}", string.Format(_localizationService.GetResource("PDFInvoice.Address", lang.Id), order.PickupAddress.Address1)), font)); - if (!string.IsNullOrEmpty(order.PickupAddress.City)) - addressTable.AddCell(new Paragraph(string.Format(" {0}", order.PickupAddress.City), font)); - if (order.PickupAddress.Country != null) - addressTable.AddCell(new Paragraph(string.Format(" {0}", order.PickupAddress.Country.GetLocalized(x => x.Name, lang.Id)), font)); - if (!string.IsNullOrEmpty(order.PickupAddress.ZipPostalCode)) - addressTable.AddCell(new Paragraph(string.Format(" {0}", order.PickupAddress.ZipPostalCode), font)); - addressTable.AddCell(new Paragraph(" ")); - } - - addressTable.AddCell(new Paragraph(" ")); - - addressTable.AddCell(new Paragraph(String.Format(_localizationService.GetResource("PDFPackagingSlip.ShippingMethod", lang.Id), order.ShippingMethod), font)); - addressTable.AddCell(new Paragraph(" ")); - doc.Add(addressTable); - - var productsTable = new PdfPTable(3); - productsTable.WidthPercentage = 100f; - if (lang.Rtl) - { - productsTable.RunDirection = PdfWriter.RUN_DIRECTION_RTL; - productsTable.SetWidths(new[] {20, 20, 60}); - } - else - { - productsTable.SetWidths(new[] {60, 20, 20}); - } - - //product name - var cell = new PdfPCell(new Phrase(_localizationService.GetResource("PDFPackagingSlip.ProductName", lang.Id),font)); - cell.BackgroundColor = BaseColor.LIGHT_GRAY; - cell.HorizontalAlignment = Element.ALIGN_CENTER; - productsTable.AddCell(cell); - - //SKU - cell = new PdfPCell(new Phrase(_localizationService.GetResource("PDFPackagingSlip.SKU", lang.Id), font)); - cell.BackgroundColor = BaseColor.LIGHT_GRAY; - cell.HorizontalAlignment = Element.ALIGN_CENTER; - productsTable.AddCell(cell); - - //qty - cell = new PdfPCell(new Phrase(_localizationService.GetResource("PDFPackagingSlip.QTY", lang.Id), font)); - cell.BackgroundColor = BaseColor.LIGHT_GRAY; - cell.HorizontalAlignment = Element.ALIGN_CENTER; - productsTable.AddCell(cell); - - foreach (var si in shipment.ShipmentItems) - { - var productAttribTable = new PdfPTable(1); - if (lang.Rtl) - productAttribTable.RunDirection = PdfWriter.RUN_DIRECTION_RTL; - productAttribTable.DefaultCell.Border = Rectangle.NO_BORDER; - - //product name - var orderItem = _orderService.GetOrderItemById(si.OrderItemId); - if (orderItem == null) - continue; - - var p = orderItem.Product; - string name = p.GetLocalized(x => x.Name, lang.Id); - productAttribTable.AddCell(new Paragraph(name, font)); - //attributes - if (!String.IsNullOrEmpty(orderItem.AttributeDescription)) - { - var attributesParagraph = new Paragraph(HtmlHelper.ConvertHtmlToPlainText(orderItem.AttributeDescription, true, true), attributesFont); - productAttribTable.AddCell(attributesParagraph); - } - //rental info - if (orderItem.Product.IsRental) - { - var rentalStartDate = orderItem.RentalStartDateUtc.HasValue ? orderItem.Product.FormatRentalDate(orderItem.RentalStartDateUtc.Value) : ""; - var rentalEndDate = orderItem.RentalEndDateUtc.HasValue ? orderItem.Product.FormatRentalDate(orderItem.RentalEndDateUtc.Value) : ""; - var rentalInfo = string.Format(_localizationService.GetResource("Order.Rental.FormattedDate"), - rentalStartDate, rentalEndDate); - - var rentalInfoParagraph = new Paragraph(rentalInfo, attributesFont); - productAttribTable.AddCell(rentalInfoParagraph); - } - productsTable.AddCell(productAttribTable); - - //SKU - var sku = p.FormatSku(orderItem.AttributesXml, _productAttributeParser); - cell = new PdfPCell(new Phrase(sku ?? String.Empty, font)); - cell.HorizontalAlignment = Element.ALIGN_CENTER; - productsTable.AddCell(cell); - - //qty - cell = new PdfPCell(new Phrase(si.Quantity.ToString(), font)); - cell.HorizontalAlignment = Element.ALIGN_CENTER; - productsTable.AddCell(cell); - } - doc.Add(productsTable); - - shipmentNum++; - if (shipmentNum < shipmentCount) - { - doc.NewPage(); - } - } - - - doc.Close(); - } - - /// - /// Print products to PDF - /// - /// Stream - /// Products - public virtual void PrintProductsToPdf(Stream stream, IList products) - { - if (stream == null) - throw new ArgumentNullException("stream"); - - if (products == null) - throw new ArgumentNullException("products"); - - var lang = _workContext.WorkingLanguage; - - var pageSize = PageSize.A4; - - if (_pdfSettings.LetterPageSizeEnabled) - { - pageSize = PageSize.LETTER; - } - - var doc = new Document(pageSize); - PdfWriter.GetInstance(doc, stream); - doc.Open(); - - //fonts - var titleFont = GetFont(); - titleFont.SetStyle(Font.BOLD); - titleFont.Color = BaseColor.BLACK; - var font = GetFont(); - - int productNumber = 1; - int prodCount = products.Count; - - foreach (var product in products) - { - string productName = product.GetLocalized(x => x.Name, lang.Id); - string productDescription = product.GetLocalized(x => x.FullDescription, lang.Id); - - var productTable = new PdfPTable(1); - productTable.WidthPercentage = 100f; - productTable.DefaultCell.Border = Rectangle.NO_BORDER; - if (lang.Rtl) - { - productTable.RunDirection = PdfWriter.RUN_DIRECTION_RTL; - } - - productTable.AddCell(new Paragraph(String.Format("{0}. {1}", productNumber, productName), titleFont)); - productTable.AddCell(new Paragraph(" ")); - productTable.AddCell(new Paragraph(HtmlHelper.StripTags(HtmlHelper.ConvertHtmlToPlainText(productDescription, decode: true)), font)); - productTable.AddCell(new Paragraph(" ")); - - if (product.ProductType == ProductType.SimpleProduct) - { - //simple product - //render its properties such as price, weight, etc - var priceStr = string.Format("{0} {1}", product.Price.ToString("0.00"), _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId).CurrencyCode); - if (product.IsRental) - priceStr = _priceFormatter.FormatRentalProductPeriod(product, priceStr); - productTable.AddCell(new Paragraph(String.Format("{0}: {1}", _localizationService.GetResource("PDFProductCatalog.Price", lang.Id), priceStr), font)); - productTable.AddCell(new Paragraph(String.Format("{0}: {1}", _localizationService.GetResource("PDFProductCatalog.SKU", lang.Id), product.Sku), font)); - - if (product.IsShipEnabled && product.Weight > Decimal.Zero) - productTable.AddCell(new Paragraph(String.Format("{0}: {1} {2}", _localizationService.GetResource("PDFProductCatalog.Weight", lang.Id), product.Weight.ToString("0.00"), _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId).Name), font)); - - if (product.ManageInventoryMethod == ManageInventoryMethod.ManageStock) - productTable.AddCell(new Paragraph(String.Format("{0}: {1}", _localizationService.GetResource("PDFProductCatalog.StockQuantity", lang.Id), product.GetTotalStockQuantity()), font)); - - productTable.AddCell(new Paragraph(" ")); - } - var pictures = _pictureService.GetPicturesByProductId(product.Id); - if (pictures.Any()) - { - var table = new PdfPTable(2); - table.WidthPercentage = 100f; - if (lang.Rtl) - { - table.RunDirection = PdfWriter.RUN_DIRECTION_RTL; - } - - foreach (var pic in pictures) - { - var picBinary = _pictureService.LoadPictureBinary(pic); - if (picBinary != null && picBinary.Length > 0) - { - var pictureLocalPath = _pictureService.GetThumbLocalPath(pic, 200, false); - var cell = new PdfPCell(Image.GetInstance(pictureLocalPath)); - cell.HorizontalAlignment = Element.ALIGN_LEFT; - cell.Border = Rectangle.NO_BORDER; - table.AddCell(cell); - } - } - - if (pictures.Count % 2 > 0) - { - var cell = new PdfPCell(new Phrase(" ")); - cell.Border = Rectangle.NO_BORDER; - table.AddCell(cell); - } - - productTable.AddCell(table); - productTable.AddCell(new Paragraph(" ")); - } - - - if (product.ProductType == ProductType.GroupedProduct) - { - //grouped product. render its associated products - int pvNum = 1; - foreach (var associatedProduct in _productService.GetAssociatedProducts(product.Id, showHidden: true)) - { - productTable.AddCell(new Paragraph(String.Format("{0}-{1}. {2}", productNumber, pvNum, associatedProduct.GetLocalized(x => x.Name, lang.Id)), font)); - productTable.AddCell(new Paragraph(" ")); - - //uncomment to render associated product description - //string apDescription = associatedProduct.GetLocalized(x => x.ShortDescription, lang.Id); - //if (!String.IsNullOrEmpty(apDescription)) - //{ - // productTable.AddCell(new Paragraph(HtmlHelper.StripTags(HtmlHelper.ConvertHtmlToPlainText(apDescription)), font)); - // productTable.AddCell(new Paragraph(" ")); - //} - - //uncomment to render associated product picture - //var apPicture = _pictureService.GetPicturesByProductId(associatedProduct.Id).FirstOrDefault(); - //if (apPicture != null) - //{ - // var picBinary = _pictureService.LoadPictureBinary(apPicture); - // if (picBinary != null && picBinary.Length > 0) - // { - // var pictureLocalPath = _pictureService.GetThumbLocalPath(apPicture, 200, false); - // productTable.AddCell(Image.GetInstance(pictureLocalPath)); - // } - //} - - productTable.AddCell(new Paragraph(String.Format("{0}: {1} {2}", _localizationService.GetResource("PDFProductCatalog.Price", lang.Id), associatedProduct.Price.ToString("0.00"), _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId).CurrencyCode), font)); - productTable.AddCell(new Paragraph(String.Format("{0}: {1}", _localizationService.GetResource("PDFProductCatalog.SKU", lang.Id), associatedProduct.Sku), font)); - - if (associatedProduct.IsShipEnabled && associatedProduct.Weight > Decimal.Zero) - productTable.AddCell(new Paragraph(String.Format("{0}: {1} {2}", _localizationService.GetResource("PDFProductCatalog.Weight", lang.Id), associatedProduct.Weight.ToString("0.00"), _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId).Name), font)); - - if (associatedProduct.ManageInventoryMethod == ManageInventoryMethod.ManageStock) - productTable.AddCell(new Paragraph(String.Format("{0}: {1}", _localizationService.GetResource("PDFProductCatalog.StockQuantity", lang.Id), associatedProduct.GetTotalStockQuantity()), font)); - - productTable.AddCell(new Paragraph(" ")); - - pvNum++; - } - } - - doc.Add(productTable); - - productNumber++; - - if (productNumber <= prodCount) - { - doc.NewPage(); - } - } - - doc.Close(); - } - - #endregion - } +// RTL Support provided by Credo inc (www.credo.co.il || info@credo.co.il) + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using iTextSharp.text; +using iTextSharp.text.pdf; +using Nop.Core; +using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Common; +using Nop.Core.Domain.Directory; +using Nop.Core.Domain.Localization; +using Nop.Core.Domain.Orders; +using Nop.Core.Domain.Shipping; +using Nop.Core.Domain.Tax; +using Nop.Core.Html; +using Nop.Services.Catalog; +using Nop.Services.Configuration; +using Nop.Services.Directory; +using Nop.Services.Helpers; +using Nop.Services.Localization; +using Nop.Services.Media; +using Nop.Services.Orders; +using Nop.Services.Payments; +using Nop.Services.Stores; + +namespace Nop.Services.Common +{ + /// + /// PDF service + /// + public partial class PdfService : IPdfService + { + #region Fields + + private readonly ILocalizationService _localizationService; + private readonly ILanguageService _languageService; + private readonly IWorkContext _workContext; + private readonly IOrderService _orderService; + private readonly IPaymentService _paymentService; + private readonly IDateTimeHelper _dateTimeHelper; + private readonly IPriceFormatter _priceFormatter; + private readonly ICurrencyService _currencyService; + private readonly IMeasureService _measureService; + private readonly IPictureService _pictureService; + private readonly IProductService _productService; + private readonly IProductAttributeParser _productAttributeParser; + private readonly IStoreService _storeService; + private readonly IStoreContext _storeContext; + private readonly ISettingService _settingContext; + private readonly IAddressAttributeFormatter _addressAttributeFormatter; + + private readonly CatalogSettings _catalogSettings; + private readonly CurrencySettings _currencySettings; + private readonly MeasureSettings _measureSettings; + private readonly PdfSettings _pdfSettings; + private readonly TaxSettings _taxSettings; + private readonly AddressSettings _addressSettings; + + #endregion + + #region Ctor + + public PdfService(ILocalizationService localizationService, + ILanguageService languageService, + IWorkContext workContext, + IOrderService orderService, + IPaymentService paymentService, + IDateTimeHelper dateTimeHelper, + IPriceFormatter priceFormatter, + ICurrencyService currencyService, + IMeasureService measureService, + IPictureService pictureService, + IProductService productService, + IProductAttributeParser productAttributeParser, + IStoreService storeService, + IStoreContext storeContext, + ISettingService settingContext, + IAddressAttributeFormatter addressAttributeFormatter, + CatalogSettings catalogSettings, + CurrencySettings currencySettings, + MeasureSettings measureSettings, + PdfSettings pdfSettings, + TaxSettings taxSettings, + AddressSettings addressSettings) + { + this._localizationService = localizationService; + this._languageService = languageService; + this._workContext = workContext; + this._orderService = orderService; + this._paymentService = paymentService; + this._dateTimeHelper = dateTimeHelper; + this._priceFormatter = priceFormatter; + this._currencyService = currencyService; + this._measureService = measureService; + this._pictureService = pictureService; + this._productService = productService; + this._productAttributeParser = productAttributeParser; + this._storeService = storeService; + this._storeContext = storeContext; + this._settingContext = settingContext; + this._addressAttributeFormatter = addressAttributeFormatter; + this._currencySettings = currencySettings; + this._catalogSettings = catalogSettings; + this._measureSettings = measureSettings; + this._pdfSettings = pdfSettings; + this._taxSettings = taxSettings; + this._addressSettings = addressSettings; + } + + #endregion + + #region Utilities + + /// + /// Get font + /// + /// Font + protected virtual Font GetFont() + { + //nopCommerce supports unicode characters + //nopCommerce uses Free Serif font by default (~/App_Data/Pdf/FreeSerif.ttf file) + //It was downloaded from http://savannah.gnu.org/projects/freefont + return GetFont(_pdfSettings.FontFileName); + } + /// + /// Get font + /// + /// Font file name + /// Font + protected virtual Font GetFont(string fontFileName) + { + if (fontFileName == null) + throw new ArgumentNullException("fontFileName"); + + string fontPath = Path.Combine(CommonHelper.MapPath("~/App_Data/Pdf/"), fontFileName); + var baseFont = BaseFont.CreateFont(fontPath, BaseFont.IDENTITY_H, BaseFont.EMBEDDED); + var font = new Font(baseFont, 10, Font.NORMAL); + return font; + } + + /// + /// Get font direction + /// + /// Language + /// Font direction + protected virtual int GetDirection(Language lang) + { + return lang.Rtl ? PdfWriter.RUN_DIRECTION_RTL : PdfWriter.RUN_DIRECTION_LTR; + } + + /// + /// Get element alignment + /// + /// Language + /// Is opposite? + /// Element alignment + protected virtual int GetAlignment(Language lang, bool isOpposite = false) + { + //if we need the element to be opposite, like logo etc`. + if (!isOpposite) + return lang.Rtl ? Element.ALIGN_RIGHT : Element.ALIGN_LEFT; + + return lang.Rtl ? Element.ALIGN_LEFT : Element.ALIGN_RIGHT; + } + + #endregion + + #region Methods + + /// + /// Print an order to PDF + /// + /// Order + /// Language identifier; 0 to use a language used when placing an order + /// Vendor identifier to limit products; 0 to to print all products. If specified, then totals won't be printed + /// A path of generated file + public virtual string PrintOrderToPdf(Order order, int languageId = 0, int vendorId = 0) + { + if (order == null) + throw new ArgumentNullException("order"); + + string fileName = string.Format("order_{0}_{1}.pdf", order.OrderGuid, CommonHelper.GenerateRandomDigitCode(4)); + string filePath = Path.Combine(CommonHelper.MapPath("~/content/files/ExportImport"), fileName); + using (var fileStream = new FileStream(filePath, FileMode.Create)) + { + var orders = new List(); + orders.Add(order); + PrintOrdersToPdf(fileStream, orders, languageId, vendorId); + } + return filePath; + } + + /// + /// Print orders to PDF + /// + /// Stream + /// Orders + /// Language identifier; 0 to use a language used when placing an order + /// Vendor identifier to limit products; 0 to to print all products. If specified, then totals won't be printed + public virtual void PrintOrdersToPdf(Stream stream, IList orders, int languageId = 0, int vendorId = 0) + { + if (stream == null) + throw new ArgumentNullException("stream"); + + if (orders == null) + throw new ArgumentNullException("orders"); + + var pageSize = PageSize.A4; + + if (_pdfSettings.LetterPageSizeEnabled) + { + pageSize = PageSize.LETTER; + } + + + var doc = new Document(pageSize); + var pdfWriter = PdfWriter.GetInstance(doc, stream); + doc.Open(); + + //fonts + var titleFont = GetFont(); + titleFont.SetStyle(Font.BOLD); + titleFont.Color = BaseColor.BLACK; + var font = GetFont(); + var attributesFont = GetFont(); + attributesFont.SetStyle(Font.ITALIC); + + int ordCount = orders.Count; + int ordNum = 0; + + foreach (var order in orders) + { + //by default _pdfSettings contains settings for the current active store + //and we need PdfSettings for the store which was used to place an order + //so let's load it based on a store of the current order + var pdfSettingsByStore = _settingContext.LoadSetting(order.StoreId); + + + var lang = _languageService.GetLanguageById(languageId == 0 ? order.CustomerLanguageId : languageId); + if (lang == null || !lang.Published) + lang = _workContext.WorkingLanguage; + + #region Header + + //logo + var logoPicture = _pictureService.GetPictureById(pdfSettingsByStore.LogoPictureId); + var logoExists = logoPicture != null; + + //header + var headerTable = new PdfPTable(logoExists ? 2 : 1); + headerTable.RunDirection = GetDirection(lang); + headerTable.DefaultCell.Border = Rectangle.NO_BORDER; + + //store info + var store = _storeService.GetStoreById(order.StoreId) ?? _storeContext.CurrentStore; + var anchor = new Anchor(store.Url.Trim(new [] { '/' }), font); + anchor.Reference = store.Url; + + var cellHeader = new PdfPCell(new Phrase(String.Format(_localizationService.GetResource("PDFInvoice.Order#", lang.Id), order.CustomOrderNumber), titleFont)); + cellHeader.Phrase.Add(new Phrase(Environment.NewLine)); + cellHeader.Phrase.Add(new Phrase(anchor)); + cellHeader.Phrase.Add(new Phrase(Environment.NewLine)); + cellHeader.Phrase.Add(new Phrase(String.Format(_localizationService.GetResource("PDFInvoice.OrderDate", lang.Id), _dateTimeHelper.ConvertToUserTime(order.CreatedOnUtc, DateTimeKind.Utc).ToString("D", new CultureInfo(lang.LanguageCulture))), font)); + cellHeader.Phrase.Add(new Phrase(Environment.NewLine)); + cellHeader.Phrase.Add(new Phrase(Environment.NewLine)); + cellHeader.HorizontalAlignment = Element.ALIGN_LEFT; + cellHeader.Border = Rectangle.NO_BORDER; + + headerTable.AddCell(cellHeader); + + if (logoExists) + if (lang.Rtl) + headerTable.SetWidths(new[] { 0.2f, 0.8f }); + else + headerTable.SetWidths(new[] { 0.8f, 0.2f }); + headerTable.WidthPercentage = 100f; + + //logo + if (logoExists) + { + var logoFilePath = _pictureService.GetThumbLocalPath(logoPicture, 0, false); + var logo = Image.GetInstance(logoFilePath); + logo.Alignment = GetAlignment(lang, true); + logo.ScaleToFit(65f, 65f); + + var cellLogo = new PdfPCell(); + cellLogo.Border = Rectangle.NO_BORDER; + cellLogo.AddElement(logo); + headerTable.AddCell(cellLogo); + } + doc.Add(headerTable); + + #endregion + + #region Addresses + + var addressTable = new PdfPTable(2); + addressTable.RunDirection = GetDirection(lang); + addressTable.DefaultCell.Border = Rectangle.NO_BORDER; + addressTable.WidthPercentage = 100f; + addressTable.SetWidths(new[] { 50, 50 }); + + //billing info + var billingAddress = new PdfPTable(1); + billingAddress.DefaultCell.Border = Rectangle.NO_BORDER; + billingAddress.RunDirection = GetDirection(lang); + + billingAddress.AddCell(new Paragraph(_localizationService.GetResource("PDFInvoice.BillingInformation", lang.Id), titleFont)); + + if (_addressSettings.CompanyEnabled && !String.IsNullOrEmpty(order.BillingAddress.Company)) + billingAddress.AddCell(new Paragraph(" " + String.Format(_localizationService.GetResource("PDFInvoice.Company", lang.Id), order.BillingAddress.Company), font)); + + billingAddress.AddCell(new Paragraph(" " + String.Format(_localizationService.GetResource("PDFInvoice.Name", lang.Id), order.BillingAddress.FirstName + " " + order.BillingAddress.LastName), font)); + if (_addressSettings.PhoneEnabled) + billingAddress.AddCell(new Paragraph(" " + String.Format(_localizationService.GetResource("PDFInvoice.Phone", lang.Id), order.BillingAddress.PhoneNumber), font)); + if (_addressSettings.FaxEnabled && !String.IsNullOrEmpty(order.BillingAddress.FaxNumber)) + billingAddress.AddCell(new Paragraph(" " + String.Format(_localizationService.GetResource("PDFInvoice.Fax", lang.Id), order.BillingAddress.FaxNumber), font)); + if (_addressSettings.StreetAddressEnabled) + billingAddress.AddCell(new Paragraph(" " + String.Format(_localizationService.GetResource("PDFInvoice.Address", lang.Id), order.BillingAddress.Address1), font)); + if (_addressSettings.StreetAddress2Enabled && !String.IsNullOrEmpty(order.BillingAddress.Address2)) + billingAddress.AddCell(new Paragraph(" " + String.Format(_localizationService.GetResource("PDFInvoice.Address2", lang.Id), order.BillingAddress.Address2), font)); + if (_addressSettings.CityEnabled || _addressSettings.StateProvinceEnabled || _addressSettings.ZipPostalCodeEnabled) + billingAddress.AddCell(new Paragraph(" " + String.Format("{0}, {1} {2}", order.BillingAddress.City, order.BillingAddress.StateProvince != null ? order.BillingAddress.StateProvince.GetLocalized(x => x.Name, lang.Id) : "", order.BillingAddress.ZipPostalCode), font)); + if (_addressSettings.CountryEnabled && order.BillingAddress.Country != null) + billingAddress.AddCell(new Paragraph(" " + order.BillingAddress.Country.GetLocalized(x => x.Name, lang.Id), font)); + + //VAT number + if (!String.IsNullOrEmpty(order.VatNumber)) + billingAddress.AddCell(new Paragraph(" " + String.Format(_localizationService.GetResource("PDFInvoice.VATNumber", lang.Id), order.VatNumber), font)); + + //custom attributes + var customBillingAddressAttributes = _addressAttributeFormatter.FormatAttributes( order.BillingAddress.CustomAttributes); + if (!String.IsNullOrEmpty(customBillingAddressAttributes)) + { + //TODO: we should add padding to each line (in case if we have sevaral custom address attributes) + billingAddress.AddCell(new Paragraph(" " + HtmlHelper.ConvertHtmlToPlainText(customBillingAddressAttributes, true, true), font)); + } + + + + //vendors payment details + if (vendorId == 0) + { + //payment method + var paymentMethod = _paymentService.LoadPaymentMethodBySystemName(order.PaymentMethodSystemName); + string paymentMethodStr = paymentMethod != null ? paymentMethod.GetLocalizedFriendlyName(_localizationService, lang.Id) : order.PaymentMethodSystemName; + if (!String.IsNullOrEmpty(paymentMethodStr)) + { + billingAddress.AddCell(new Paragraph(" ")); + billingAddress.AddCell(new Paragraph(" " + String.Format(_localizationService.GetResource("PDFInvoice.PaymentMethod", lang.Id), paymentMethodStr), font)); + billingAddress.AddCell(new Paragraph()); + } + + //custom values + var customValues = order.DeserializeCustomValues(); + if (customValues != null) + { + foreach (var item in customValues) + { + billingAddress.AddCell(new Paragraph(" ")); + billingAddress.AddCell(new Paragraph(" " + item.Key + ": " + item.Value, font)); + billingAddress.AddCell(new Paragraph()); + } + } + } + addressTable.AddCell(billingAddress); + + //shipping info + var shippingAddress = new PdfPTable(1); + shippingAddress.DefaultCell.Border = Rectangle.NO_BORDER; + shippingAddress.RunDirection = GetDirection(lang); + + if (order.ShippingStatus != ShippingStatus.ShippingNotRequired) + { + //cell = new PdfPCell(); + //cell.Border = Rectangle.NO_BORDER; + + if (!order.PickUpInStore) + { + if (order.ShippingAddress == null) + throw new NopException(string.Format("Shipping is required, but address is not available. Order ID = {0}", order.Id)); + + shippingAddress.AddCell(new Paragraph(_localizationService.GetResource("PDFInvoice.ShippingInformation", lang.Id), titleFont)); + if (!String.IsNullOrEmpty(order.ShippingAddress.Company)) + shippingAddress.AddCell(new Paragraph(" " + String.Format(_localizationService.GetResource("PDFInvoice.Company", lang.Id), order.ShippingAddress.Company), font)); + shippingAddress.AddCell(new Paragraph(" " + String.Format(_localizationService.GetResource("PDFInvoice.Name", lang.Id), order.ShippingAddress.FirstName + " " + order.ShippingAddress.LastName), font)); + if (_addressSettings.PhoneEnabled) + shippingAddress.AddCell(new Paragraph(" " + String.Format(_localizationService.GetResource("PDFInvoice.Phone", lang.Id), order.ShippingAddress.PhoneNumber), font)); + if (_addressSettings.FaxEnabled && !String.IsNullOrEmpty(order.ShippingAddress.FaxNumber)) + shippingAddress.AddCell(new Paragraph(" " + String.Format(_localizationService.GetResource("PDFInvoice.Fax", lang.Id), order.ShippingAddress.FaxNumber), font)); + if (_addressSettings.StreetAddressEnabled) + shippingAddress.AddCell(new Paragraph(" " + String.Format(_localizationService.GetResource("PDFInvoice.Address", lang.Id), order.ShippingAddress.Address1), font)); + if (_addressSettings.StreetAddress2Enabled && !String.IsNullOrEmpty(order.ShippingAddress.Address2)) + shippingAddress.AddCell(new Paragraph(" " + String.Format(_localizationService.GetResource("PDFInvoice.Address2", lang.Id), order.ShippingAddress.Address2), font)); + if (_addressSettings.CityEnabled || _addressSettings.StateProvinceEnabled || _addressSettings.ZipPostalCodeEnabled) + shippingAddress.AddCell(new Paragraph(" " + String.Format("{0}, {1} {2}", order.ShippingAddress.City, order.ShippingAddress.StateProvince != null ? order.ShippingAddress.StateProvince.GetLocalized(x => x.Name, lang.Id) : "", order.ShippingAddress.ZipPostalCode), font)); + if (_addressSettings.CountryEnabled && order.ShippingAddress.Country != null) + shippingAddress.AddCell(new Paragraph(" " + order.ShippingAddress.Country.GetLocalized(x => x.Name, lang.Id), font)); + //custom attributes + var customShippingAddressAttributes = _addressAttributeFormatter.FormatAttributes(order.ShippingAddress.CustomAttributes); + if (!String.IsNullOrEmpty(customShippingAddressAttributes)) + { + //TODO: we should add padding to each line (in case if we have sevaral custom address attributes) + shippingAddress.AddCell(new Paragraph(" " + HtmlHelper.ConvertHtmlToPlainText(customShippingAddressAttributes, true, true), font)); + } + shippingAddress.AddCell(new Paragraph(" ")); + } + else + if (order.PickupAddress != null) + { + shippingAddress.AddCell(new Paragraph(_localizationService.GetResource("PDFInvoice.Pickup", lang.Id), titleFont)); + if (!string.IsNullOrEmpty(order.PickupAddress.Address1)) + shippingAddress.AddCell(new Paragraph(string.Format(" {0}", string.Format(_localizationService.GetResource("PDFInvoice.Address", lang.Id), order.PickupAddress.Address1)), font)); + if (!string.IsNullOrEmpty(order.PickupAddress.City)) + shippingAddress.AddCell(new Paragraph(string.Format(" {0}", order.PickupAddress.City), font)); + if (order.PickupAddress.Country != null) + shippingAddress.AddCell(new Paragraph(string.Format(" {0}", order.PickupAddress.Country.GetLocalized(x => x.Name, lang.Id)), font)); + if (!string.IsNullOrEmpty(order.PickupAddress.ZipPostalCode)) + shippingAddress.AddCell(new Paragraph(string.Format(" {0}", order.PickupAddress.ZipPostalCode), font)); + shippingAddress.AddCell(new Paragraph(" ")); + } + shippingAddress.AddCell(new Paragraph(" " + String.Format(_localizationService.GetResource("PDFInvoice.ShippingMethod", lang.Id), order.ShippingMethod), font)); + shippingAddress.AddCell(new Paragraph()); + + addressTable.AddCell(shippingAddress); + } + else + { + shippingAddress.AddCell(new Paragraph()); + addressTable.AddCell(shippingAddress); + } + + doc.Add(addressTable); + doc.Add(new Paragraph(" ")); + + #endregion + + #region Products + + //products + var productsHeader = new PdfPTable(1); + productsHeader.RunDirection = GetDirection(lang); + productsHeader.WidthPercentage = 100f; + var cellProducts = new PdfPCell(new Phrase(_localizationService.GetResource("PDFInvoice.Product(s)", lang.Id), titleFont)); + cellProducts.Border = Rectangle.NO_BORDER; + productsHeader.AddCell(cellProducts); + doc.Add(productsHeader); + doc.Add(new Paragraph(" ")); + + + var orderItems = order.OrderItems; + + var productsTable = new PdfPTable(_catalogSettings.ShowSkuOnProductDetailsPage ? 5 : 4); + productsTable.RunDirection = GetDirection(lang); + productsTable.WidthPercentage = 100f; + if (lang.Rtl) + { + productsTable.SetWidths(_catalogSettings.ShowSkuOnProductDetailsPage + ? new[] {15, 10, 15, 15, 45} + : new[] {20, 10, 20, 50}); + } + else + { + productsTable.SetWidths(_catalogSettings.ShowSkuOnProductDetailsPage + ? new[] {45, 15, 15, 10, 15} + : new[] {50, 20, 10, 20}); + } + + //product name + var cellProductItem = new PdfPCell(new Phrase(_localizationService.GetResource("PDFInvoice.ProductName", lang.Id), font)); + cellProductItem.BackgroundColor = BaseColor.LIGHT_GRAY; + cellProductItem.HorizontalAlignment = Element.ALIGN_CENTER; + productsTable.AddCell(cellProductItem); + + //SKU + if (_catalogSettings.ShowSkuOnProductDetailsPage) + { + cellProductItem = new PdfPCell(new Phrase(_localizationService.GetResource("PDFInvoice.SKU", lang.Id), font)); + cellProductItem.BackgroundColor = BaseColor.LIGHT_GRAY; + cellProductItem.HorizontalAlignment = Element.ALIGN_CENTER; + productsTable.AddCell(cellProductItem); + } + + //price + cellProductItem = new PdfPCell(new Phrase(_localizationService.GetResource("PDFInvoice.ProductPrice", lang.Id), font)); + cellProductItem.BackgroundColor = BaseColor.LIGHT_GRAY; + cellProductItem.HorizontalAlignment = Element.ALIGN_CENTER; + productsTable.AddCell(cellProductItem); + + //qty + cellProductItem = new PdfPCell(new Phrase(_localizationService.GetResource("PDFInvoice.ProductQuantity", lang.Id), font)); + cellProductItem.BackgroundColor = BaseColor.LIGHT_GRAY; + cellProductItem.HorizontalAlignment = Element.ALIGN_CENTER; + productsTable.AddCell(cellProductItem); + + //total + cellProductItem = new PdfPCell(new Phrase(_localizationService.GetResource("PDFInvoice.ProductTotal", lang.Id), font)); + cellProductItem.BackgroundColor = BaseColor.LIGHT_GRAY; + cellProductItem.HorizontalAlignment = Element.ALIGN_CENTER; + productsTable.AddCell(cellProductItem); + + foreach (var orderItem in orderItems) + { + var p = orderItem.Product; + + //a vendor should have access only to his products + if (vendorId > 0 && p.VendorId != vendorId) + continue; + + var pAttribTable = new PdfPTable(1); + pAttribTable.RunDirection = GetDirection(lang); + pAttribTable.DefaultCell.Border = Rectangle.NO_BORDER; + + //product name + string name = p.GetLocalized(x => x.Name, lang.Id); + pAttribTable.AddCell(new Paragraph(name, font)); + cellProductItem.AddElement(new Paragraph(name, font)); + //attributes + if (!String.IsNullOrEmpty(orderItem.AttributeDescription)) + { + var attributesParagraph = new Paragraph(HtmlHelper.ConvertHtmlToPlainText(orderItem.AttributeDescription, true, true), attributesFont); + pAttribTable.AddCell(attributesParagraph); + } + //rental info + if (orderItem.Product.IsRental) + { + var rentalStartDate = orderItem.RentalStartDateUtc.HasValue ? orderItem.Product.FormatRentalDate(orderItem.RentalStartDateUtc.Value) : ""; + var rentalEndDate = orderItem.RentalEndDateUtc.HasValue ? orderItem.Product.FormatRentalDate(orderItem.RentalEndDateUtc.Value) : ""; + var rentalInfo = string.Format(_localizationService.GetResource("Order.Rental.FormattedDate"), + rentalStartDate, rentalEndDate); + + var rentalInfoParagraph = new Paragraph(rentalInfo, attributesFont); + pAttribTable.AddCell(rentalInfoParagraph); + } + productsTable.AddCell(pAttribTable); + + //SKU + if (_catalogSettings.ShowSkuOnProductDetailsPage) + { + var sku = p.FormatSku(orderItem.AttributesXml, _productAttributeParser); + cellProductItem = new PdfPCell(new Phrase(sku ?? String.Empty, font)); + cellProductItem.HorizontalAlignment = Element.ALIGN_CENTER; + productsTable.AddCell(cellProductItem); + } + + //price + string unitPrice; + if (order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax) + { + //including tax + var unitPriceInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(orderItem.UnitPriceInclTax, order.CurrencyRate); + unitPrice = _priceFormatter.FormatPrice(unitPriceInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, lang, true); + } + else + { + //excluding tax + var unitPriceExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(orderItem.UnitPriceExclTax, order.CurrencyRate); + unitPrice = _priceFormatter.FormatPrice(unitPriceExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, lang, false); + } + cellProductItem = new PdfPCell(new Phrase(unitPrice, font)); + cellProductItem.HorizontalAlignment = Element.ALIGN_LEFT; + productsTable.AddCell(cellProductItem); + + //qty + cellProductItem = new PdfPCell(new Phrase(orderItem.Quantity.ToString(), font)); + cellProductItem.HorizontalAlignment = Element.ALIGN_LEFT; + productsTable.AddCell(cellProductItem); + + //total + string subTotal; + if (order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax) + { + //including tax + var priceInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(orderItem.PriceInclTax, order.CurrencyRate); + subTotal = _priceFormatter.FormatPrice(priceInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, lang, true); + } + else + { + //excluding tax + var priceExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(orderItem.PriceExclTax, order.CurrencyRate); + subTotal = _priceFormatter.FormatPrice(priceExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, lang, false); + } + cellProductItem = new PdfPCell(new Phrase(subTotal, font)); + cellProductItem.HorizontalAlignment = Element.ALIGN_LEFT; + productsTable.AddCell(cellProductItem); + } + doc.Add(productsTable); + + #endregion + + #region Checkout attributes + + //vendors cannot see checkout attributes + if (vendorId == 0 && !String.IsNullOrEmpty(order.CheckoutAttributeDescription)) + { + doc.Add(new Paragraph(" ")); + var attribTable = new PdfPTable(1); + attribTable.RunDirection = GetDirection(lang); + attribTable.WidthPercentage = 100f; + + string attributes = HtmlHelper.ConvertHtmlToPlainText(order.CheckoutAttributeDescription, true, true); + var cCheckoutAttributes = new PdfPCell(new Phrase(attributes, font)); + cCheckoutAttributes.Border = Rectangle.NO_BORDER; + cCheckoutAttributes.HorizontalAlignment = Element.ALIGN_RIGHT; + attribTable.AddCell(cCheckoutAttributes); + doc.Add(attribTable); + } + + #endregion + + #region Totals + + //vendors cannot see totals + if (vendorId == 0) + { + //subtotal + var totalsTable = new PdfPTable(1); + totalsTable.RunDirection = GetDirection(lang); + totalsTable.DefaultCell.Border = Rectangle.NO_BORDER; + totalsTable.WidthPercentage = 100f; + + //order subtotal + if (order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax && !_taxSettings.ForceTaxExclusionFromOrderSubtotal) + { + //including tax + + var orderSubtotalInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubtotalInclTax, order.CurrencyRate); + string orderSubtotalInclTaxStr = _priceFormatter.FormatPrice(orderSubtotalInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, lang, true); + + var p = new PdfPCell(new Paragraph(String.Format("{0} {1}", _localizationService.GetResource("PDFInvoice.Sub-Total", lang.Id), orderSubtotalInclTaxStr), font)); + p.HorizontalAlignment = Element.ALIGN_RIGHT; + p.Border = Rectangle.NO_BORDER; + totalsTable.AddCell(p); + } + else + { + //excluding tax + + var orderSubtotalExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubtotalExclTax, order.CurrencyRate); + string orderSubtotalExclTaxStr = _priceFormatter.FormatPrice(orderSubtotalExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, lang, false); + + var p = new PdfPCell(new Paragraph(String.Format("{0} {1}", _localizationService.GetResource("PDFInvoice.Sub-Total", lang.Id), orderSubtotalExclTaxStr), font)); + p.HorizontalAlignment = Element.ALIGN_RIGHT; + p.Border = Rectangle.NO_BORDER; + totalsTable.AddCell(p); + } + + //discount (applied to order subtotal) + if (order.OrderSubTotalDiscountExclTax > decimal.Zero) + { + //order subtotal + if (order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax && !_taxSettings.ForceTaxExclusionFromOrderSubtotal) + { + //including tax + + var orderSubTotalDiscountInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubTotalDiscountInclTax, order.CurrencyRate); + string orderSubTotalDiscountInCustomerCurrencyStr = _priceFormatter.FormatPrice(-orderSubTotalDiscountInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, lang, true); + + var p = new PdfPCell(new Paragraph(String.Format("{0} {1}", _localizationService.GetResource("PDFInvoice.Discount", lang.Id), orderSubTotalDiscountInCustomerCurrencyStr), font)); + p.HorizontalAlignment = Element.ALIGN_RIGHT; + p.Border = Rectangle.NO_BORDER; + totalsTable.AddCell(p); + } + else + { + //excluding tax + + var orderSubTotalDiscountExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubTotalDiscountExclTax, order.CurrencyRate); + string orderSubTotalDiscountInCustomerCurrencyStr = _priceFormatter.FormatPrice(-orderSubTotalDiscountExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, lang, false); + + var p = new PdfPCell(new Paragraph(String.Format("{0} {1}", _localizationService.GetResource("PDFInvoice.Discount", lang.Id), orderSubTotalDiscountInCustomerCurrencyStr), font)); + p.HorizontalAlignment = Element.ALIGN_RIGHT; + p.Border = Rectangle.NO_BORDER; + totalsTable.AddCell(p); + } + } + + //shipping + if (order.ShippingStatus != ShippingStatus.ShippingNotRequired && order.OrderShippingInclTax != decimal.Zero) + { + if (order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax) + { + //including tax + var orderShippingInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderShippingInclTax, order.CurrencyRate); + string orderShippingInclTaxStr = _priceFormatter.FormatShippingPrice(orderShippingInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, lang, true); + + var p = new PdfPCell(new Paragraph(String.Format("{0} {1}", _localizationService.GetResource("PDFInvoice.Shipping", lang.Id), orderShippingInclTaxStr), font)); + p.HorizontalAlignment = Element.ALIGN_RIGHT; + p.Border = Rectangle.NO_BORDER; + totalsTable.AddCell(p); + } + else + { + //excluding tax + var orderShippingExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderShippingExclTax, order.CurrencyRate); + string orderShippingExclTaxStr = _priceFormatter.FormatShippingPrice(orderShippingExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, lang, false); + + var p = new PdfPCell(new Paragraph(String.Format("{0} {1}", _localizationService.GetResource("PDFInvoice.Shipping", lang.Id), orderShippingExclTaxStr), font)); + p.HorizontalAlignment = Element.ALIGN_RIGHT; + p.Border = Rectangle.NO_BORDER; + totalsTable.AddCell(p); + } + } + + //reward points applied to order amount + if (order.RedeemedRewardPointsEntry != null && order.RedeemedRewardPointsEntry.UsedAmount != decimal.Zero) + { + string rpTitle = string.Format(_localizationService.GetResource("PDFInvoice.RewardPoints", lang.Id), -order.RedeemedRewardPointsEntry.Points); + string rpAmount = _priceFormatter.FormatPrice(-(_currencyService.ConvertCurrency(order.RedeemedRewardPointsEntry.UsedAmount, order.CurrencyRate)), true, order.CustomerCurrencyCode, false, lang); + + var p = new PdfPCell(new Paragraph(String.Format("{0} {1}", rpTitle, rpAmount), font)); + p.HorizontalAlignment = Element.ALIGN_RIGHT; + p.Border = Rectangle.NO_BORDER; + totalsTable.AddCell(p); + } + //payment fee + if (order.PaymentMethodAdditionalFeeExclTax != decimal.Zero) //alow negative + { + if (order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax) + { + //including tax + var paymentMethodAdditionalFeeInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.PaymentMethodAdditionalFeeInclTax, order.CurrencyRate); + string paymentMethodAdditionalFeeInclTaxStr = _priceFormatter.FormatPaymentMethodAdditionalFee(paymentMethodAdditionalFeeInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, lang, true); + + var p = new PdfPCell(new Paragraph(String.Format("{0} {1}", _localizationService.GetResource("PDFInvoice.PaymentMethodAdditionalFee", lang.Id), paymentMethodAdditionalFeeInclTaxStr), font)); + p.HorizontalAlignment = Element.ALIGN_RIGHT; + p.Border = Rectangle.NO_BORDER; + totalsTable.AddCell(p); + } + else + { + //excluding tax + var paymentMethodAdditionalFeeExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.PaymentMethodAdditionalFeeExclTax, order.CurrencyRate); + string paymentMethodAdditionalFeeExclTaxStr = _priceFormatter.FormatPaymentMethodAdditionalFee(paymentMethodAdditionalFeeExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, lang, false); + + var p = new PdfPCell(new Paragraph(String.Format("{0} {1}", _localizationService.GetResource("PDFInvoice.PaymentMethodAdditionalFee", lang.Id), paymentMethodAdditionalFeeExclTaxStr), font)); + p.HorizontalAlignment = Element.ALIGN_RIGHT; + p.Border = Rectangle.NO_BORDER; + totalsTable.AddCell(p); + } + } + + //discount (applied to order total) + if (order.OrderDiscount > decimal.Zero) + { + var orderDiscountInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderDiscount, order.CurrencyRate); + string orderDiscountInCustomerCurrencyStr = _priceFormatter.FormatPrice(-orderDiscountInCustomerCurrency, true, order.CustomerCurrencyCode, false, lang); + + var p = new PdfPCell(new Paragraph(String.Format("{0} {1}", _localizationService.GetResource("PDFInvoice.Discount", lang.Id), orderDiscountInCustomerCurrencyStr), font)); + p.HorizontalAlignment = Element.ALIGN_RIGHT; + p.Border = Rectangle.NO_BORDER; + totalsTable.AddCell(p); + } + + //tax + string taxStr = string.Empty; + var taxRates = new SortedDictionary(); + bool displayTax = true; + bool displayTaxRates = true; + if (_taxSettings.HideTaxInOrderSummary && order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax) + { + displayTax = false; + } + else + { + if (order.OrderTax == 0 && _taxSettings.HideZeroTax) + { + displayTax = false; + displayTaxRates = false; + } + else + { + taxRates = order.TaxRatesDictionary; + + displayTaxRates = _taxSettings.DisplayTaxRates && taxRates.Any(); + displayTax = !displayTaxRates; + + var orderTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderTax, order.CurrencyRate); + taxStr = _priceFormatter.FormatPrice(orderTaxInCustomerCurrency, true, order.CustomerCurrencyCode, false, lang); + } + } + if (displayTax) + { + var p = new PdfPCell(new Paragraph(String.Format("{0} {1}", _localizationService.GetResource("PDFInvoice.Tax", lang.Id), taxStr), font)); + p.HorizontalAlignment = Element.ALIGN_RIGHT; + p.Border = Rectangle.NO_BORDER; + totalsTable.AddCell(p); + } + if (displayTaxRates) + { + foreach (var item in taxRates) + { + string taxRate = String.Format(_localizationService.GetResource("PDFInvoice.TaxRate", lang.Id), _priceFormatter.FormatTaxRate(item.Key)); + string taxValue = _priceFormatter.FormatPrice(_currencyService.ConvertCurrency(item.Value.TaxAmount, order.CurrencyRate), true, order.CustomerCurrencyCode, false, lang); + + var p = new PdfPCell(new Paragraph(String.Format("{0} {1}", taxRate, taxValue), font)); + p.HorizontalAlignment = Element.ALIGN_RIGHT; + p.Border = Rectangle.NO_BORDER; + totalsTable.AddCell(p); + } + } + + //shipping non taxable + if (order.ShippingStatus != ShippingStatus.ShippingNotRequired && order.OrderShippingNonTaxable != decimal.Zero) + { + var orderShippingNonTaxableInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderShippingNonTaxable, order.CurrencyRate); + string orderShippingNonTaxableTaxStr = _priceFormatter.FormatShippingPrice(orderShippingNonTaxableInCustomerCurrency, true, order.CustomerCurrencyCode, lang, true); + + var p = new PdfPCell(new Paragraph(String.Format("{0} {1}", _localizationService.GetResource("PDFInvoice.Shipping", lang.Id), orderShippingNonTaxableTaxStr), font)); + p.HorizontalAlignment = Element.ALIGN_RIGHT; + p.Border = Rectangle.NO_BORDER; + totalsTable.AddCell(p); + } + + //payment fee non taxable + if (order.PaymentMethodAdditionalFeeNonTaxable != decimal.Zero) //alow negative + { + var paymentMethodAdditionalFeeNonTaxableInCustomerCurrency = _currencyService.ConvertCurrency(order.PaymentMethodAdditionalFeeNonTaxable, order.CurrencyRate); + string paymentMethodAdditionalFeeNonTaxStr = _priceFormatter.FormatPaymentMethodAdditionalFee(paymentMethodAdditionalFeeNonTaxableInCustomerCurrency, true, order.CustomerCurrencyCode, lang, true); + + var p = new PdfPCell(new Paragraph(String.Format("{0} {1}", _localizationService.GetResource("PDFInvoice.PaymentMethodAdditionalFee", lang.Id), paymentMethodAdditionalFeeNonTaxStr), font)); + p.HorizontalAlignment = Element.ALIGN_RIGHT; + p.Border = Rectangle.NO_BORDER; + totalsTable.AddCell(p); + } + + //gift cards + foreach (var gcuh in order.GiftCardUsageHistory) + { + string gcTitle = string.Format(_localizationService.GetResource("PDFInvoice.GiftCardInfo", lang.Id), gcuh.GiftCard.GiftCardCouponCode); + string gcAmountStr = _priceFormatter.FormatPrice(-(_currencyService.ConvertCurrency(gcuh.UsedValue, order.CurrencyRate)), true, order.CustomerCurrencyCode, false, lang); + + var p = new PdfPCell(new Paragraph(String.Format("{0} {1}", gcTitle, gcAmountStr), font)); + p.HorizontalAlignment = Element.ALIGN_RIGHT; + p.Border = Rectangle.NO_BORDER; + totalsTable.AddCell(p); + } + + //reward points + if (order.RedeemedRewardPointsEntry != null && order.RedeemedRewardPointsEntry.UsedAmountPurchased != decimal.Zero) + { + string rpTitle = string.Format(_localizationService.GetResource("PDFInvoice.RewardPointsPurchased", lang.Id), -order.RedeemedRewardPointsEntry.PointsPurchased); + string rpAmount = _priceFormatter.FormatPrice(-(_currencyService.ConvertCurrency(order.RedeemedRewardPointsEntry.UsedAmountPurchased, order.CurrencyRate)), true, order.CustomerCurrencyCode, false, lang); + + var p = new PdfPCell(new Paragraph(String.Format("{0} {1}", rpTitle, rpAmount), font)); + p.HorizontalAlignment = Element.ALIGN_RIGHT; + p.Border = Rectangle.NO_BORDER; + totalsTable.AddCell(p); + } + + //order total + var orderTotalInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderTotal, order.CurrencyRate); + string orderTotalStr = _priceFormatter.FormatPrice(orderTotalInCustomerCurrency, true, order.CustomerCurrencyCode, false, lang); + + + var pTotal = new PdfPCell(new Paragraph(String.Format("{0} {1}", _localizationService.GetResource("PDFInvoice.OrderTotal", lang.Id), orderTotalStr), titleFont)); + pTotal.HorizontalAlignment = Element.ALIGN_RIGHT; + pTotal.Border = Rectangle.NO_BORDER; + totalsTable.AddCell(pTotal); + + doc.Add(totalsTable); + } + + #endregion + + #region Order notes + + if (pdfSettingsByStore.RenderOrderNotes) + { + var orderNotes = order.OrderNotes + .Where(on => on.DisplayToCustomer) + .OrderByDescending(on => on.CreatedOnUtc) + .ToList(); + if (orderNotes.Any()) + { + var notesHeader = new PdfPTable(1); + notesHeader.RunDirection = GetDirection(lang); + notesHeader.WidthPercentage = 100f; + var cellOrderNote = new PdfPCell(new Phrase(_localizationService.GetResource("PDFInvoice.OrderNotes", lang.Id), titleFont)); + cellOrderNote.Border = Rectangle.NO_BORDER; + notesHeader.AddCell(cellOrderNote); + doc.Add(notesHeader); + doc.Add(new Paragraph(" ")); + + var notesTable = new PdfPTable(2); + notesTable.RunDirection = GetDirection(lang); + if (lang.Rtl) + { + notesTable.SetWidths(new[] {70, 30}); + } + else + { + notesTable.SetWidths(new[] {30, 70}); + } + notesTable.WidthPercentage = 100f; + + //created on + cellOrderNote = new PdfPCell(new Phrase(_localizationService.GetResource("PDFInvoice.OrderNotes.CreatedOn", lang.Id), font)); + cellOrderNote.BackgroundColor = BaseColor.LIGHT_GRAY; + cellOrderNote.HorizontalAlignment = Element.ALIGN_CENTER; + notesTable.AddCell(cellOrderNote); + + //note + cellOrderNote = new PdfPCell(new Phrase(_localizationService.GetResource("PDFInvoice.OrderNotes.Note", lang.Id), font)); + cellOrderNote.BackgroundColor = BaseColor.LIGHT_GRAY; + cellOrderNote.HorizontalAlignment = Element.ALIGN_CENTER; + notesTable.AddCell(cellOrderNote); + + foreach (var orderNote in orderNotes) + { + cellOrderNote = new PdfPCell(new Phrase(_dateTimeHelper.ConvertToUserTime(orderNote.CreatedOnUtc, DateTimeKind.Utc).ToString(), font)); + cellOrderNote.HorizontalAlignment = Element.ALIGN_LEFT; + notesTable.AddCell(cellOrderNote); + + cellOrderNote = new PdfPCell(new Phrase(HtmlHelper.ConvertHtmlToPlainText(orderNote.FormatOrderNoteText(), true, true), font)); + cellOrderNote.HorizontalAlignment = Element.ALIGN_LEFT; + notesTable.AddCell(cellOrderNote); + + //should we display a link to downloadable files here? + //I think, no. Onyway, PDFs are printable documents and links (files) are useful here + } + doc.Add(notesTable); + } + } + + #endregion + + #region Footer + + if (!String.IsNullOrEmpty(pdfSettingsByStore.InvoiceFooterTextColumn1) || !String.IsNullOrEmpty(pdfSettingsByStore.InvoiceFooterTextColumn2)) + { + var column1Lines = String.IsNullOrEmpty(pdfSettingsByStore.InvoiceFooterTextColumn1) ? + new List() : + pdfSettingsByStore.InvoiceFooterTextColumn1 + .Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries) + .ToList(); + var column2Lines = String.IsNullOrEmpty(pdfSettingsByStore.InvoiceFooterTextColumn2) ? + new List() : + pdfSettingsByStore.InvoiceFooterTextColumn2 + .Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries) + .ToList(); + if (column1Lines.Any() || column2Lines.Any()) + { + var totalLines = Math.Max(column1Lines.Count, column2Lines.Count); + const float margin = 43; + + //if you have really a lot of lines in the footer, then replace 9 with 10 or 11 + int footerHeight = totalLines * 9; + var directContent = pdfWriter.DirectContent; + directContent.MoveTo(pageSize.GetLeft(margin), pageSize.GetBottom(margin) + footerHeight); + directContent.LineTo(pageSize.GetRight(margin), pageSize.GetBottom(margin) + footerHeight); + directContent.Stroke(); + + + var footerTable = new PdfPTable(2); + footerTable.WidthPercentage = 100f; + footerTable.SetTotalWidth(new float[] { 250, 250 }); + footerTable.RunDirection = GetDirection(lang); + + //column 1 + if (column1Lines.Any()) + { + var column1 = new PdfPCell(new Phrase()); + column1.Border = Rectangle.NO_BORDER; + column1.HorizontalAlignment = Element.ALIGN_LEFT; + foreach (var footerLine in column1Lines) + { + column1.Phrase.Add(new Phrase(footerLine, font)); + column1.Phrase.Add(new Phrase(Environment.NewLine)); + } + footerTable.AddCell(column1); + } + else + { + var column = new PdfPCell(new Phrase(" ")); + column.Border = Rectangle.NO_BORDER; + footerTable.AddCell(column); + } + + //column 2 + if (column2Lines.Any()) + { + var column2 = new PdfPCell(new Phrase()); + column2.Border = Rectangle.NO_BORDER; + column2.HorizontalAlignment = Element.ALIGN_LEFT; + foreach (var footerLine in column2Lines) + { + column2.Phrase.Add(new Phrase(footerLine, font)); + column2.Phrase.Add(new Phrase(Environment.NewLine)); + } + footerTable.AddCell(column2); + } + else + { + var column = new PdfPCell(new Phrase(" ")); + column.Border = Rectangle.NO_BORDER; + footerTable.AddCell(column); + } + + footerTable.WriteSelectedRows(0, totalLines, pageSize.GetLeft(margin), pageSize.GetBottom(margin) + footerHeight, directContent); + } + } + + #endregion + + ordNum++; + if (ordNum < ordCount) + { + doc.NewPage(); + } + } + doc.Close(); + } + + /// + /// Print packaging slips to PDF + /// + /// Stream + /// Shipments + /// Language identifier; 0 to use a language used when placing an order + public virtual void PrintPackagingSlipsToPdf(Stream stream, IList shipments, int languageId = 0) + { + if (stream == null) + throw new ArgumentNullException("stream"); + + if (shipments == null) + throw new ArgumentNullException("shipments"); + + var pageSize = PageSize.A4; + + if (_pdfSettings.LetterPageSizeEnabled) + { + pageSize = PageSize.LETTER; + } + + var doc = new Document(pageSize); + PdfWriter.GetInstance(doc, stream); + doc.Open(); + + //fonts + var titleFont = GetFont(); + titleFont.SetStyle(Font.BOLD); + titleFont.Color = BaseColor.BLACK; + var font = GetFont(); + var attributesFont = GetFont(); + attributesFont.SetStyle(Font.ITALIC); + + int shipmentCount = shipments.Count; + int shipmentNum = 0; + + foreach (var shipment in shipments) + { + var order = shipment.Order; + + var lang = _languageService.GetLanguageById(languageId == 0 ? order.CustomerLanguageId : languageId); + if (lang == null || !lang.Published) + lang = _workContext.WorkingLanguage; + + var addressTable = new PdfPTable(1); + if (lang.Rtl) + addressTable.RunDirection = PdfWriter.RUN_DIRECTION_RTL; + addressTable.DefaultCell.Border = Rectangle.NO_BORDER; + addressTable.WidthPercentage = 100f; + + addressTable.AddCell(new Paragraph(String.Format(_localizationService.GetResource("PDFPackagingSlip.Shipment", lang.Id), shipment.Id), titleFont)); + addressTable.AddCell(new Paragraph(String.Format(_localizationService.GetResource("PDFPackagingSlip.Order", lang.Id), order.CustomOrderNumber), titleFont)); + + if (!order.PickUpInStore) + { + if (order.ShippingAddress == null) + throw new NopException(string.Format("Shipping is required, but address is not available. Order ID = {0}", order.Id)); + + if (_addressSettings.CompanyEnabled && !String.IsNullOrEmpty(order.ShippingAddress.Company)) + addressTable.AddCell(new Paragraph(String.Format(_localizationService.GetResource("PDFPackagingSlip.Company", lang.Id), + order.ShippingAddress.Company), font)); + + addressTable.AddCell(new Paragraph(String.Format(_localizationService.GetResource("PDFPackagingSlip.Name", lang.Id), + order.ShippingAddress.FirstName + " " + order.ShippingAddress.LastName), font)); + if (_addressSettings.PhoneEnabled) + addressTable.AddCell(new Paragraph(String.Format(_localizationService.GetResource("PDFPackagingSlip.Phone", lang.Id), + order.ShippingAddress.PhoneNumber), font)); + if (_addressSettings.StreetAddressEnabled) + addressTable.AddCell(new Paragraph(String.Format(_localizationService.GetResource("PDFPackagingSlip.Address", lang.Id), + order.ShippingAddress.Address1), font)); + + if (_addressSettings.StreetAddress2Enabled && !String.IsNullOrEmpty(order.ShippingAddress.Address2)) + addressTable.AddCell(new Paragraph(String.Format(_localizationService.GetResource("PDFPackagingSlip.Address2", lang.Id), + order.ShippingAddress.Address2), font)); + + if (_addressSettings.CityEnabled || _addressSettings.StateProvinceEnabled || _addressSettings.ZipPostalCodeEnabled) + addressTable.AddCell(new Paragraph(String.Format("{0}, {1} {2}", order.ShippingAddress.City, order.ShippingAddress.StateProvince != null + ? order.ShippingAddress.StateProvince.GetLocalized(x => x.Name, lang.Id) + : "", order.ShippingAddress.ZipPostalCode), font)); + + if (_addressSettings.CountryEnabled && order.ShippingAddress.Country != null) + addressTable.AddCell(new Paragraph(order.ShippingAddress.Country.GetLocalized(x => x.Name, lang.Id), font)); + + //custom attributes + var customShippingAddressAttributes = _addressAttributeFormatter.FormatAttributes(order.ShippingAddress.CustomAttributes); + if (!String.IsNullOrEmpty(customShippingAddressAttributes)) + { + addressTable.AddCell(new Paragraph(HtmlHelper.ConvertHtmlToPlainText(customShippingAddressAttributes, true, true), font)); + } + } + else + if (order.PickupAddress != null) + { + addressTable.AddCell(new Paragraph(_localizationService.GetResource("PDFInvoice.Pickup", lang.Id), titleFont)); + if (!string.IsNullOrEmpty(order.PickupAddress.Address1)) + addressTable.AddCell(new Paragraph(string.Format(" {0}", string.Format(_localizationService.GetResource("PDFInvoice.Address", lang.Id), order.PickupAddress.Address1)), font)); + if (!string.IsNullOrEmpty(order.PickupAddress.City)) + addressTable.AddCell(new Paragraph(string.Format(" {0}", order.PickupAddress.City), font)); + if (order.PickupAddress.Country != null) + addressTable.AddCell(new Paragraph(string.Format(" {0}", order.PickupAddress.Country.GetLocalized(x => x.Name, lang.Id)), font)); + if (!string.IsNullOrEmpty(order.PickupAddress.ZipPostalCode)) + addressTable.AddCell(new Paragraph(string.Format(" {0}", order.PickupAddress.ZipPostalCode), font)); + addressTable.AddCell(new Paragraph(" ")); + } + + addressTable.AddCell(new Paragraph(" ")); + + addressTable.AddCell(new Paragraph(String.Format(_localizationService.GetResource("PDFPackagingSlip.ShippingMethod", lang.Id), order.ShippingMethod), font)); + addressTable.AddCell(new Paragraph(" ")); + doc.Add(addressTable); + + var productsTable = new PdfPTable(3); + productsTable.WidthPercentage = 100f; + if (lang.Rtl) + { + productsTable.RunDirection = PdfWriter.RUN_DIRECTION_RTL; + productsTable.SetWidths(new[] {20, 20, 60}); + } + else + { + productsTable.SetWidths(new[] {60, 20, 20}); + } + + //product name + var cell = new PdfPCell(new Phrase(_localizationService.GetResource("PDFPackagingSlip.ProductName", lang.Id),font)); + cell.BackgroundColor = BaseColor.LIGHT_GRAY; + cell.HorizontalAlignment = Element.ALIGN_CENTER; + productsTable.AddCell(cell); + + //SKU + cell = new PdfPCell(new Phrase(_localizationService.GetResource("PDFPackagingSlip.SKU", lang.Id), font)); + cell.BackgroundColor = BaseColor.LIGHT_GRAY; + cell.HorizontalAlignment = Element.ALIGN_CENTER; + productsTable.AddCell(cell); + + //qty + cell = new PdfPCell(new Phrase(_localizationService.GetResource("PDFPackagingSlip.QTY", lang.Id), font)); + cell.BackgroundColor = BaseColor.LIGHT_GRAY; + cell.HorizontalAlignment = Element.ALIGN_CENTER; + productsTable.AddCell(cell); + + foreach (var si in shipment.ShipmentItems) + { + var productAttribTable = new PdfPTable(1); + if (lang.Rtl) + productAttribTable.RunDirection = PdfWriter.RUN_DIRECTION_RTL; + productAttribTable.DefaultCell.Border = Rectangle.NO_BORDER; + + //product name + var orderItem = _orderService.GetOrderItemById(si.OrderItemId); + if (orderItem == null) + continue; + + var p = orderItem.Product; + string name = p.GetLocalized(x => x.Name, lang.Id); + productAttribTable.AddCell(new Paragraph(name, font)); + //attributes + if (!String.IsNullOrEmpty(orderItem.AttributeDescription)) + { + var attributesParagraph = new Paragraph(HtmlHelper.ConvertHtmlToPlainText(orderItem.AttributeDescription, true, true), attributesFont); + productAttribTable.AddCell(attributesParagraph); + } + //rental info + if (orderItem.Product.IsRental) + { + var rentalStartDate = orderItem.RentalStartDateUtc.HasValue ? orderItem.Product.FormatRentalDate(orderItem.RentalStartDateUtc.Value) : ""; + var rentalEndDate = orderItem.RentalEndDateUtc.HasValue ? orderItem.Product.FormatRentalDate(orderItem.RentalEndDateUtc.Value) : ""; + var rentalInfo = string.Format(_localizationService.GetResource("Order.Rental.FormattedDate"), + rentalStartDate, rentalEndDate); + + var rentalInfoParagraph = new Paragraph(rentalInfo, attributesFont); + productAttribTable.AddCell(rentalInfoParagraph); + } + productsTable.AddCell(productAttribTable); + + //SKU + var sku = p.FormatSku(orderItem.AttributesXml, _productAttributeParser); + cell = new PdfPCell(new Phrase(sku ?? String.Empty, font)); + cell.HorizontalAlignment = Element.ALIGN_CENTER; + productsTable.AddCell(cell); + + //qty + cell = new PdfPCell(new Phrase(si.Quantity.ToString(), font)); + cell.HorizontalAlignment = Element.ALIGN_CENTER; + productsTable.AddCell(cell); + } + doc.Add(productsTable); + + shipmentNum++; + if (shipmentNum < shipmentCount) + { + doc.NewPage(); + } + } + + + doc.Close(); + } + + /// + /// Print products to PDF + /// + /// Stream + /// Products + public virtual void PrintProductsToPdf(Stream stream, IList products) + { + if (stream == null) + throw new ArgumentNullException("stream"); + + if (products == null) + throw new ArgumentNullException("products"); + + var lang = _workContext.WorkingLanguage; + + var pageSize = PageSize.A4; + + if (_pdfSettings.LetterPageSizeEnabled) + { + pageSize = PageSize.LETTER; + } + + var doc = new Document(pageSize); + PdfWriter.GetInstance(doc, stream); + doc.Open(); + + //fonts + var titleFont = GetFont(); + titleFont.SetStyle(Font.BOLD); + titleFont.Color = BaseColor.BLACK; + var font = GetFont(); + + int productNumber = 1; + int prodCount = products.Count; + + foreach (var product in products) + { + string productName = product.GetLocalized(x => x.Name, lang.Id); + string productDescription = product.GetLocalized(x => x.FullDescription, lang.Id); + + var productTable = new PdfPTable(1); + productTable.WidthPercentage = 100f; + productTable.DefaultCell.Border = Rectangle.NO_BORDER; + if (lang.Rtl) + { + productTable.RunDirection = PdfWriter.RUN_DIRECTION_RTL; + } + + productTable.AddCell(new Paragraph(String.Format("{0}. {1}", productNumber, productName), titleFont)); + productTable.AddCell(new Paragraph(" ")); + productTable.AddCell(new Paragraph(HtmlHelper.StripTags(HtmlHelper.ConvertHtmlToPlainText(productDescription, decode: true)), font)); + productTable.AddCell(new Paragraph(" ")); + + if (product.ProductType == ProductType.SimpleProduct) + { + //simple product + //render its properties such as price, weight, etc + var priceStr = string.Format("{0} {1}", product.Price.ToString("0.00"), _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId).CurrencyCode); + if (product.IsRental) + priceStr = _priceFormatter.FormatRentalProductPeriod(product, priceStr); + productTable.AddCell(new Paragraph(String.Format("{0}: {1}", _localizationService.GetResource("PDFProductCatalog.Price", lang.Id), priceStr), font)); + productTable.AddCell(new Paragraph(String.Format("{0}: {1}", _localizationService.GetResource("PDFProductCatalog.SKU", lang.Id), product.Sku), font)); + + if (product.IsShipEnabled && product.Weight > Decimal.Zero) + productTable.AddCell(new Paragraph(String.Format("{0}: {1} {2}", _localizationService.GetResource("PDFProductCatalog.Weight", lang.Id), product.Weight.ToString("0.00"), _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId).Name), font)); + + if (product.ManageInventoryMethod == ManageInventoryMethod.ManageStock) + productTable.AddCell(new Paragraph(String.Format("{0}: {1}", _localizationService.GetResource("PDFProductCatalog.StockQuantity", lang.Id), product.GetTotalStockQuantity()), font)); + + productTable.AddCell(new Paragraph(" ")); + } + var pictures = _pictureService.GetPicturesByProductId(product.Id); + if (pictures.Any()) + { + var table = new PdfPTable(2); + table.WidthPercentage = 100f; + if (lang.Rtl) + { + table.RunDirection = PdfWriter.RUN_DIRECTION_RTL; + } + + foreach (var pic in pictures) + { + var picBinary = _pictureService.LoadPictureBinary(pic); + if (picBinary != null && picBinary.Length > 0) + { + var pictureLocalPath = _pictureService.GetThumbLocalPath(pic, 200, false); + var cell = new PdfPCell(Image.GetInstance(pictureLocalPath)); + cell.HorizontalAlignment = Element.ALIGN_LEFT; + cell.Border = Rectangle.NO_BORDER; + table.AddCell(cell); + } + } + + if (pictures.Count % 2 > 0) + { + var cell = new PdfPCell(new Phrase(" ")); + cell.Border = Rectangle.NO_BORDER; + table.AddCell(cell); + } + + productTable.AddCell(table); + productTable.AddCell(new Paragraph(" ")); + } + + + if (product.ProductType == ProductType.GroupedProduct) + { + //grouped product. render its associated products + int pvNum = 1; + foreach (var associatedProduct in _productService.GetAssociatedProducts(product.Id, showHidden: true)) + { + productTable.AddCell(new Paragraph(String.Format("{0}-{1}. {2}", productNumber, pvNum, associatedProduct.GetLocalized(x => x.Name, lang.Id)), font)); + productTable.AddCell(new Paragraph(" ")); + + //uncomment to render associated product description + //string apDescription = associatedProduct.GetLocalized(x => x.ShortDescription, lang.Id); + //if (!String.IsNullOrEmpty(apDescription)) + //{ + // productTable.AddCell(new Paragraph(HtmlHelper.StripTags(HtmlHelper.ConvertHtmlToPlainText(apDescription)), font)); + // productTable.AddCell(new Paragraph(" ")); + //} + + //uncomment to render associated product picture + //var apPicture = _pictureService.GetPicturesByProductId(associatedProduct.Id).FirstOrDefault(); + //if (apPicture != null) + //{ + // var picBinary = _pictureService.LoadPictureBinary(apPicture); + // if (picBinary != null && picBinary.Length > 0) + // { + // var pictureLocalPath = _pictureService.GetThumbLocalPath(apPicture, 200, false); + // productTable.AddCell(Image.GetInstance(pictureLocalPath)); + // } + //} + + productTable.AddCell(new Paragraph(String.Format("{0}: {1} {2}", _localizationService.GetResource("PDFProductCatalog.Price", lang.Id), associatedProduct.Price.ToString("0.00"), _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId).CurrencyCode), font)); + productTable.AddCell(new Paragraph(String.Format("{0}: {1}", _localizationService.GetResource("PDFProductCatalog.SKU", lang.Id), associatedProduct.Sku), font)); + + if (associatedProduct.IsShipEnabled && associatedProduct.Weight > Decimal.Zero) + productTable.AddCell(new Paragraph(String.Format("{0}: {1} {2}", _localizationService.GetResource("PDFProductCatalog.Weight", lang.Id), associatedProduct.Weight.ToString("0.00"), _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId).Name), font)); + + if (associatedProduct.ManageInventoryMethod == ManageInventoryMethod.ManageStock) + productTable.AddCell(new Paragraph(String.Format("{0}: {1}", _localizationService.GetResource("PDFProductCatalog.StockQuantity", lang.Id), associatedProduct.GetTotalStockQuantity()), font)); + + productTable.AddCell(new Paragraph(" ")); + + pvNum++; + } + } + + doc.Add(productTable); + + productNumber++; + + if (productNumber <= prodCount) + { + doc.NewPage(); + } + } + + doc.Close(); + } + + #endregion + } } \ No newline at end of file diff --git a/src/Libraries/Nop.Services/Common/PdfService7.cs b/src/Libraries/Nop.Services/Common/PdfService7.cs index 56ccedd41a5..ed1d4adf3d2 100644 --- a/src/Libraries/Nop.Services/Common/PdfService7.cs +++ b/src/Libraries/Nop.Services/Common/PdfService7.cs @@ -640,7 +640,7 @@ public virtual void PrintOrdersToPdf(Stream stream, IList orders, int lan { tabPage.AddHeaderCell(new Cell(1, 1).Add(_localizationService.GetResource("PDFInvoice.SKU", lang.Id)).AddStyle(styleCell).AddStyle(style9b).SetBackgroundColor(Color.LIGHT_GRAY)); } - tabPage.AddHeaderCell(new Cell(1, 1).Add(_localizationService.GetResource("PDFInvoice.VatRate", lang.Id)).AddStyle(styleCell).AddStyle(style9b).SetBackgroundColor(Color.LIGHT_GRAY).SetTextAlignment(TextAlignment.CENTER)); + tabPage.AddHeaderCell(new Cell(1, 1).Add(_localizationService.GetResource("PDFInvoice.TaxRate", lang.Id)).AddStyle(styleCell).AddStyle(style9b).SetBackgroundColor(Color.LIGHT_GRAY).SetTextAlignment(TextAlignment.CENTER)); tabPage.AddHeaderCell(new Cell(1, 1).Add(_priceFormatter.FormatTaxString(_localizationService.GetResource("PDFInvoice.ProductPrice", lang.Id), lang, includingTax)).AddStyle(styleCell).AddStyle(style9b).SetBackgroundColor(Color.LIGHT_GRAY).SetTextAlignment(TextAlignment.RIGHT)); tabPage.AddHeaderCell(new Cell(1, 1).Add(_localizationService.GetResource("PDFInvoice.ProductQuantity", lang.Id)).AddStyle(styleCell).AddStyle(style9b).SetBackgroundColor(Color.LIGHT_GRAY).SetTextAlignment(TextAlignment.RIGHT)); tabPage.AddHeaderCell(new Cell(1, 1).Add(_priceFormatter.FormatTaxString(_localizationService.GetResource("PDFInvoice.ProductTotal", lang.Id), lang, includingTax)).AddStyle(styleCell).AddStyle(style9b).SetBackgroundColor(Color.LIGHT_GRAY).SetTextAlignment(TextAlignment.RIGHT)); @@ -684,9 +684,9 @@ public virtual void PrintOrdersToPdf(Stream stream, IList orders, int lan paraPdf = new Paragraph(sku ?? String.Empty).AddStyle(styleNormal); tabPage.AddCell(new Cell().Add(paraPdf).AddStyle(styleCell)); } - //vatrate + //taxrate bool hasAttribVat = !String.IsNullOrEmpty(orderItem.AttributesXml) && orderItem.AttributesXml.Contains(" orders, int lan paraPdf = new Paragraph("").AddStyle(styleNormal); tabPage.AddCell(new Cell().Add(paraPdf).AddStyle(styleCell)); } - //vatrate + //taxrate paraPdf = new Paragraph(_priceFormatter.FormatTaxRate(taxRate)).AddStyle(styleNormal).SetTextAlignment(TextAlignment.CENTER); tabPage.AddCell(new Cell().Add(paraPdf).AddStyle(styleCell)); @@ -777,7 +777,7 @@ public virtual void PrintOrdersToPdf(Stream stream, IList orders, int lan tabPage.AddCell(new Cell(1, col).Add(" ").AddStyle(styleCell).SetBorderTop(new SolidBorder(0.1f))); - #region Order notes first. not used atm + #region Order notes in cell not used atm. They are printed on last page //var notesCell = 4; //int totNotes = 0; //if (pdfSettingsByStore.RenderOrderNotes) @@ -816,7 +816,7 @@ public virtual void PrintOrdersToPdf(Stream stream, IList orders, int lan // } //} - #endregion + #endregion. see #region Order totals var taxRates = order.TaxRatesDictionary; @@ -828,11 +828,19 @@ public virtual void PrintOrdersToPdf(Stream stream, IList orders, int lan //vendors cannot see totals if (vendorId == 0) { + int redeemedPoints = 0; + decimal redeemedAmount = decimal.Zero; + if (order.RedeemedRewardPointsEntry != null) + { + redeemedPoints = -order.RedeemedRewardPointsEntry.Points; + redeemedAmount = -order.RedeemedRewardPointsEntry.UsedAmount; + } var lstSummary = new List> //desc, amount, show when zero, doLocalize, borderTop, borderBottom { Tuple.Create("PDFInvoice.Sub-Total", includingTax ? order.OrderSubtotalInclTax : order.OrderSubtotalExclTax, true, true, false, false) //order subtotal ,Tuple.Create("PDFInvoice.Discount", includingTax ? -order.OrderSubTotalDiscountInclTax : -order.OrderSubTotalDiscountExclTax, false, true, false, false) //discount (applied to order subtotal) - ,Tuple.Create("PDFInvoice.Shipping", includingTax ? order.OrderShippingInclTax : order.OrderShippingExclTax, false, true, order.ShippingStatus != ShippingStatus.ShippingNotRequired, false) //shipping + ,Tuple.Create("PDFInvoice.Shipping", includingTax ? order.OrderShippingInclTax : order.OrderShippingExclTax, false, order.OrderShippingInclTax == decimal.Zero && order.OrderShippingNonTaxable == decimal.Zero, order.ShippingStatus != ShippingStatus.ShippingNotRequired, false) //shipping + ,Tuple.Create(string.Format(_localizationService.GetResource("PDFInvoice.RewardPoints", lang.Id), redeemedPoints), redeemedAmount, false, true, false, false) //earned reward points ,Tuple.Create("PDFInvoice.PaymentMethodAdditionalFee", includingTax ? order.PaymentMethodAdditionalFeeInclTax : order.PaymentMethodAdditionalFeeExclTax, false, true, false, false) //payment fee ,Tuple.Create("PDFInvoice.InvoiceDiscount", -order.OrderDiscount, false, true, false, false) //discount (applied to order total) }; @@ -844,6 +852,13 @@ public virtual void PrintOrdersToPdf(Stream stream, IList orders, int lan } //order total incl. lstSummary.Add(new Tuple("PDFInvoice.OrderAmountIncl", order.OrderAmountIncl, true, true, false, false)); + + //shipping non taxable + var shippTuple = Tuple.Create("PDFInvoice.Shipping", order.OrderShippingNonTaxable, false, false, order.ShippingStatus != ShippingStatus.ShippingNotRequired, false); + lstSummary.Add(shippTuple); + + //payment fee non taxable + lstSummary.Add(new Tuple("PDFInvoice.PaymentMethodAdditionalFee", order.PaymentMethodAdditionalFeeNonTaxable, false, true, false, false)); //gift cards foreach (var gcuh in order.GiftCardUsageHistory) @@ -854,11 +869,11 @@ public virtual void PrintOrdersToPdf(Stream stream, IList orders, int lan ); } - //reward points + //purchased reward points if (order.RedeemedRewardPointsEntry != null) { lstSummary.Add(new Tuple( - string.Format(_localizationService.GetResource("PDFInvoice.RewardPoints", lang.Id), -order.RedeemedRewardPointsEntry.Points), -order.RedeemedRewardPointsEntry.UsedAmount, false, false, false, false + string.Format(_localizationService.GetResource("PDFInvoice.RewardPointsPurchased", lang.Id), -order.RedeemedRewardPointsEntry.PointsPurchased), -order.RedeemedRewardPointsEntry.UsedAmountPurchased, false, false, false, false ) ); } @@ -950,11 +965,11 @@ public virtual void PrintOrdersToPdf(Stream stream, IList orders, int lan var taxTable = new Table(new float[] { 20, 20, 20, 20, 20 }).SetBorder(Border.NO_BORDER).SetWidthPercent(100f).AddStyle(styleNormal).SetHorizontalAlignment(HorizontalAlignment.LEFT); //header - taxTable.AddCell(new Cell().Add(_localizationService.GetResource("PDFInvoice.VatRate", lang.Id)).AddStyle(styleCell).AddStyle(style8).SetBackgroundColor(Color.LIGHT_GRAY).SetTextAlignment(TextAlignment.CENTER)); + taxTable.AddCell(new Cell().Add(_localizationService.GetResource("PDFInvoice.TaxRate", lang.Id)).AddStyle(styleCell).AddStyle(style8).SetBackgroundColor(Color.LIGHT_GRAY).SetTextAlignment(TextAlignment.CENTER)); taxTable.AddCell(new Cell().Add(_localizationService.GetResource(includingTax ? "PDFInvoice.OrderAmountIncl" : "PDFInvoice.OrderAmount", lang.Id)).AddStyle(styleCell).AddStyle(style8).SetBackgroundColor(Color.LIGHT_GRAY).SetTextAlignment(TextAlignment.CENTER)); taxTable.AddCell(new Cell().Add(_localizationService.GetResource(includingTax ? "PDFInvoice.DiscountAmountIncl" : "PDFInvoice.DiscountAmount", lang.Id)).AddStyle(styleCell).AddStyle(style8).SetBackgroundColor(Color.LIGHT_GRAY).SetTextAlignment(TextAlignment.CENTER)); taxTable.AddCell(new Cell().Add(_localizationService.GetResource("PDFInvoice.BaseAmount", lang.Id)).AddStyle(styleCell).AddStyle(style8).SetBackgroundColor(Color.LIGHT_GRAY).SetTextAlignment(TextAlignment.CENTER)); - taxTable.AddCell(new Cell().Add(_localizationService.GetResource("PDFInvoice.VatAmount", lang.Id)).AddStyle(styleCell).AddStyle(style8).SetBackgroundColor(Color.LIGHT_GRAY).SetTextAlignment(TextAlignment.CENTER)); + taxTable.AddCell(new Cell().Add(_localizationService.GetResource("PDFInvoice.TaxAmount", lang.Id)).AddStyle(styleCell).AddStyle(style8).SetBackgroundColor(Color.LIGHT_GRAY).SetTextAlignment(TextAlignment.CENTER)); foreach (var item in taxRates) @@ -963,14 +978,14 @@ public virtual void PrintOrdersToPdf(Stream stream, IList orders, int lan string Amount = _priceFormatter.FormatPrice(_currencyService.ConvertCurrency(item.Value.Amount, order.CurrencyRate), showCurrency, order.CustomerCurrencyCode, false, lang); string DiscountAmount = _priceFormatter.FormatPrice(_currencyService.ConvertCurrency(item.Value.DiscountAmount, order.CurrencyRate), showCurrency, order.CustomerCurrencyCode, false, lang); string BaseAmount = _priceFormatter.FormatPrice(_currencyService.ConvertCurrency(item.Value.BaseAmount, order.CurrencyRate), showCurrency, order.CustomerCurrencyCode, false, lang); - string VatAmount = _priceFormatter.FormatPrice(_currencyService.ConvertCurrency(item.Value.VatAmount, order.CurrencyRate), showCurrency, order.CustomerCurrencyCode, false, lang); + string TaxAmount = _priceFormatter.FormatPrice(_currencyService.ConvertCurrency(item.Value.TaxAmount, order.CurrencyRate), showCurrency, order.CustomerCurrencyCode, false, lang); //string AmountIncludingVAT = _priceFormatter.FormatPrice(_currencyService.ConvertCurrency(item.Value.AmountIncludingVAT, order.CurrencyRate), true, order.CustomerCurrencyCode, false, lang); taxTable.AddCell(new Cell().Add(taxRate).AddStyle(styleCell).AddStyle(style8b).SetTextAlignment(TextAlignment.CENTER).SetBackgroundColor(Color.WHITE)); taxTable.AddCell(new Cell().Add(Amount).AddStyle(styleCell).AddStyle(style8b).SetTextAlignment(TextAlignment.CENTER).SetBackgroundColor(Color.WHITE)); taxTable.AddCell(new Cell().Add(DiscountAmount).AddStyle(styleCell).AddStyle(style8b).SetTextAlignment(TextAlignment.CENTER).SetBackgroundColor(Color.WHITE)); taxTable.AddCell(new Cell().Add(BaseAmount).AddStyle(styleCell).AddStyle(style8b).SetTextAlignment(TextAlignment.CENTER).SetBackgroundColor(Color.WHITE)); - taxTable.AddCell(new Cell().Add(VatAmount).AddStyle(styleCell).AddStyle(style8b).SetTextAlignment(TextAlignment.CENTER).SetBackgroundColor(Color.WHITE)); + taxTable.AddCell(new Cell().Add(TaxAmount).AddStyle(styleCell).AddStyle(style8b).SetTextAlignment(TextAlignment.CENTER).SetBackgroundColor(Color.WHITE)); } footerTable.AddCell(new Cell(1, 5).Add(taxTable).AddStyle(styleCell).SetPadding(0).SetBorderTop(new SolidBorder(0.1f)).SetBorderBottom(new SolidBorder(0.1f))); @@ -992,7 +1007,7 @@ public virtual void PrintOrdersToPdf(Stream stream, IList orders, int lan taxAmountTable.AddCell(new Cell(1, 1).Add(paraPdf).AddStyle(styleCell).SetBackgroundColor(Color.WHITE)); //vat amount head - paraPdf = new Paragraph(_localizationService.GetResource("PDFInvoice.VatAmount", lang.Id)).AddStyle(style8).SetTextAlignment(TextAlignment.CENTER); + paraPdf = new Paragraph(_localizationService.GetResource("PDFInvoice.TaxAmount", lang.Id)).AddStyle(style8).SetTextAlignment(TextAlignment.CENTER); taxAmountTable.AddCell(new Cell(1, 1).Add(paraPdf).AddStyle(styleCell).SetBackgroundColor(Color.LIGHT_GRAY)); //total pay amount diff --git a/src/Libraries/Nop.Services/Customers/CustomerRegistrationService.cs b/src/Libraries/Nop.Services/Customers/CustomerRegistrationService.cs index 3d4cdec7c34..62c13f88f25 100644 --- a/src/Libraries/Nop.Services/Customers/CustomerRegistrationService.cs +++ b/src/Libraries/Nop.Services/Customers/CustomerRegistrationService.cs @@ -1,465 +1,465 @@ -using System; -using System.Linq; -using Nop.Core; -using Nop.Core.Domain.Customers; -using Nop.Services.Common; -using Nop.Services.Events; -using Nop.Services.Localization; -using Nop.Services.Messages; -using Nop.Services.Orders; -using Nop.Services.Security; -using Nop.Services.Stores; - -namespace Nop.Services.Customers -{ - /// - /// 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 IStoreService _storeService; - private readonly IRewardPointService _rewardPointService; - private readonly IGenericAttributeService _genericAttributeService; - private readonly IWorkContext _workContext; - private readonly IWorkflowMessageService _workflowMessageService; - private readonly IEventPublisher _eventPublisher; - private readonly RewardPointsSettings _rewardPointsSettings; - private readonly CustomerSettings _customerSettings; - - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Customer service - /// Encryption service - /// Newsletter subscription service - /// Localization service - /// Store service - /// Reward points service - /// Generic attribute service - /// Work context - /// Workflow message service - /// Event publisher - /// Reward points settings - /// Customer settings - public CustomerRegistrationService(ICustomerService customerService, - IEncryptionService encryptionService, - INewsLetterSubscriptionService newsLetterSubscriptionService, - ILocalizationService localizationService, - IStoreService storeService, - IRewardPointService rewardPointService, - IWorkContext workContext, - IGenericAttributeService genericAttributeService, - IWorkflowMessageService workflowMessageService, - IEventPublisher eventPublisher, - RewardPointsSettings rewardPointsSettings, - CustomerSettings customerSettings) - { - this._customerService = customerService; - this._encryptionService = encryptionService; - this._newsLetterSubscriptionService = newsLetterSubscriptionService; - this._localizationService = localizationService; - this._storeService = storeService; - this._rewardPointService = rewardPointService; - this._genericAttributeService = genericAttributeService; - this._workContext = workContext; - this._workflowMessageService = workflowMessageService; - this._eventPublisher = eventPublisher; - this._rewardPointsSettings = rewardPointsSettings; - this._customerSettings = customerSettings; - } - - #endregion - - #region Utilities - - /// - /// Check whether the entered password matches with saved one - /// - /// Customer password - /// The entered password - /// True if passwords match; otherwise false - protected bool PasswordsMatch(CustomerPassword customerPassword, string enteredPassword) - { - if (customerPassword == null || string.IsNullOrEmpty(enteredPassword)) - return false; - - var savedPassword = string.Empty; - switch (customerPassword.PasswordFormat) - { - case PasswordFormat.Clear: - savedPassword = enteredPassword; - break; - case PasswordFormat.Encrypted: - savedPassword = _encryptionService.EncryptText(enteredPassword); - break; - case PasswordFormat.Hashed: - savedPassword = _encryptionService.CreatePasswordHash(enteredPassword, customerPassword.PasswordSalt, _customerSettings.HashedPasswordFormat); - break; - } - - return customerPassword.Password.Equals(savedPassword); - } - - #endregion - - #region Methods - - /// - /// Validate customer - /// - /// Username or email - /// Password - /// Result - public virtual CustomerLoginResults ValidateCustomer(string usernameOrEmail, string password) - { - var customer = _customerSettings.UsernamesEnabled ? - _customerService.GetCustomerByUsername(usernameOrEmail) : - _customerService.GetCustomerByEmail(usernameOrEmail); - - if (customer == null) - return CustomerLoginResults.CustomerNotExist; - if (customer.Deleted) - return CustomerLoginResults.Deleted; - if (!customer.Active) - return CustomerLoginResults.NotActive; - //only registered can login - if (!customer.IsRegistered()) - return CustomerLoginResults.NotRegistered; - //check whether a customer is locked out - if (customer.CannotLoginUntilDateUtc.HasValue && customer.CannotLoginUntilDateUtc.Value > DateTime.UtcNow) - return CustomerLoginResults.LockedOut; - - if (!PasswordsMatch(_customerService.GetCurrentPassword(customer.Id), password)) - { - //wrong password - customer.FailedLoginAttempts++; - if (_customerSettings.FailedPasswordAllowedAttempts > 0 && - customer.FailedLoginAttempts >= _customerSettings.FailedPasswordAllowedAttempts) - { - //lock out - customer.CannotLoginUntilDateUtc = DateTime.UtcNow.AddMinutes(_customerSettings.FailedPasswordLockoutMinutes); - //reset the counter - customer.FailedLoginAttempts = 0; - } - _customerService.UpdateCustomer(customer); - - return CustomerLoginResults.WrongPassword; - } - - //update login details - customer.FailedLoginAttempts = 0; - customer.CannotLoginUntilDateUtc = null; - customer.RequireReLogin = false; - customer.LastLoginDateUtc = DateTime.UtcNow; - _customerService.UpdateCustomer(customer); - - return CustomerLoginResults.Successful; - } - - /// - /// Register customer - /// - /// Request - /// 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"); - - var result = new CustomerRegistrationResult(); - if (request.Customer.IsSearchEngineAccount()) - { - result.AddError("Search engine can't be registered"); - return result; - } - if (request.Customer.IsBackgroundTaskAccount()) - { - result.AddError("Background task account can't be registered"); - return result; - } - if (request.Customer.IsRegistered()) - { - result.AddError("Current customer is already registered"); - return result; - } - if (String.IsNullOrEmpty(request.Email)) - { - result.AddError(_localizationService.GetResource("Account.Register.Errors.EmailIsNotProvided")); - return result; - } - if (!CommonHelper.IsValidEmail(request.Email)) - { - result.AddError(_localizationService.GetResource("Common.WrongEmail")); - return result; - } - if (String.IsNullOrWhiteSpace(request.Password)) - { - result.AddError(_localizationService.GetResource("Account.Register.Errors.PasswordIsNotProvided")); - return result; - } - if (_customerSettings.UsernamesEnabled) - { - if (String.IsNullOrEmpty(request.Username)) - { - result.AddError(_localizationService.GetResource("Account.Register.Errors.UsernameIsNotProvided")); - return result; - } - } - - //validate unique user - if (_customerService.GetCustomerByEmail(request.Email) != null) - { - result.AddError(_localizationService.GetResource("Account.Register.Errors.EmailAlreadyExists")); - return result; - } - if (_customerSettings.UsernamesEnabled) - { - if (_customerService.GetCustomerByUsername(request.Username) != null) - { - result.AddError(_localizationService.GetResource("Account.Register.Errors.UsernameAlreadyExists")); - return result; - } - } - - //at this point request is valid - request.Customer.Username = request.Username; - request.Customer.Email = request.Email; - - var customerPassword = new CustomerPassword - { - Customer = request.Customer, - PasswordFormat = request.PasswordFormat, - CreatedOnUtc = DateTime.UtcNow - }; - switch (request.PasswordFormat) - { - case PasswordFormat.Clear: - customerPassword.Password = request.Password; - break; - case PasswordFormat.Encrypted: - customerPassword.Password = _encryptionService.EncryptText(request.Password); - break; - case PasswordFormat.Hashed: - { - var saltKey = _encryptionService.CreateSaltKey(5); - customerPassword.PasswordSalt = saltKey; - customerPassword.Password = _encryptionService.CreatePasswordHash(request.Password, saltKey, _customerSettings.HashedPasswordFormat); - } - break; - } - _customerService.InsertCustomerPassword(customerPassword); - - request.Customer.Active = request.IsApproved; - - //add to 'Registered' role - var registeredRole = _customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.Registered); - if (registeredRole == null) - throw new NopException("'Registered' role could not be loaded"); - 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); - - //Add reward points for customer registration (if enabled) - if (_rewardPointsSettings.Enabled && - _rewardPointsSettings.PointsForRegistration > 0) - { - _rewardPointService.AddRewardPointsHistoryEntry(request.Customer, - _rewardPointsSettings.PointsForRegistration, - request.StoreId, - _localizationService.GetResource("RewardPoints.Message.EarnedForRegistration")); - } - - _customerService.UpdateCustomer(request.Customer); - - //publish event - _eventPublisher.Publish(new CustomerPasswordChangedEvent(customerPassword)); - - return result; - } - - /// - /// Change password - /// - /// Request - /// Result - public virtual ChangePasswordResult ChangePassword(ChangePasswordRequest request) - { - if (request == null) - throw new ArgumentNullException("request"); - - var result = new ChangePasswordResult(); - if (String.IsNullOrWhiteSpace(request.Email)) - { - result.AddError(_localizationService.GetResource("Account.ChangePassword.Errors.EmailIsNotProvided")); - return result; - } - if (String.IsNullOrWhiteSpace(request.NewPassword)) - { - result.AddError(_localizationService.GetResource("Account.ChangePassword.Errors.PasswordIsNotProvided")); - return result; - } - - var customer = _customerService.GetCustomerByEmail(request.Email); - if (customer == null) - { - result.AddError(_localizationService.GetResource("Account.ChangePassword.Errors.EmailNotFound")); - return result; - } - - if (request.ValidateRequest) - { - //request isn't valid - if (!PasswordsMatch(_customerService.GetCurrentPassword(customer.Id), request.OldPassword)) - { - result.AddError(_localizationService.GetResource("Account.ChangePassword.Errors.OldPasswordDoesntMatch")); - return result; - } - } - - //check for duplicates - if (_customerSettings.UnduplicatedPasswordsNumber > 0) - { - //get some of previous passwords - var previousPasswords = _customerService.GetCustomerPasswords(customer.Id, passwordsToReturn: _customerSettings.UnduplicatedPasswordsNumber); - - var newPasswordMatchesWithPrevious = previousPasswords.Any(password => PasswordsMatch(password, request.NewPassword)); - if (newPasswordMatchesWithPrevious) - { - result.AddError(_localizationService.GetResource("Account.ChangePassword.Errors.PasswordMatchesWithPrevious")); - return result; - } - } - - //at this point request is valid - var customerPassword = new CustomerPassword - { - Customer = customer, - PasswordFormat = request.NewPasswordFormat, - CreatedOnUtc = DateTime.UtcNow - }; - switch (request.NewPasswordFormat) - { - case PasswordFormat.Clear: - customerPassword.Password = request.NewPassword; - break; - case PasswordFormat.Encrypted: - customerPassword.Password = _encryptionService.EncryptText(request.NewPassword); - break; - case PasswordFormat.Hashed: - { - var saltKey = _encryptionService.CreateSaltKey(5); - customerPassword.PasswordSalt = saltKey; - customerPassword.Password = _encryptionService.CreatePasswordHash(request.NewPassword, saltKey, _customerSettings.HashedPasswordFormat); - } - break; - } - _customerService.InsertCustomerPassword(customerPassword); - - //publish event - _eventPublisher.Publish(new CustomerPasswordChangedEvent(customerPassword)); - - return result; - } - - /// - /// Sets a user email - /// - /// Customer - /// New email - /// Require validation of new email address - public virtual void SetEmail(Customer customer, string newEmail, bool requireValidation) - { - if (customer == null) - throw new ArgumentNullException("customer"); - - if (newEmail == null) - throw new NopException("Email cannot be null"); - - newEmail = newEmail.Trim(); - string oldEmail = customer.Email; - - if (!CommonHelper.IsValidEmail(newEmail)) - throw new NopException(_localizationService.GetResource("Account.EmailUsernameErrors.NewEmailIsNotValid")); - - if (newEmail.Length > 100) - throw new NopException(_localizationService.GetResource("Account.EmailUsernameErrors.EmailTooLong")); - - var customer2 = _customerService.GetCustomerByEmail(newEmail); - if (customer2 != null && customer.Id != customer2.Id) - throw new NopException(_localizationService.GetResource("Account.EmailUsernameErrors.EmailAlreadyExists")); - - if (requireValidation) - { - //re-validate email - customer.EmailToRevalidate = newEmail; - _customerService.UpdateCustomer(customer); - - //email re-validation message - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.EmailRevalidationToken, Guid.NewGuid().ToString()); - _workflowMessageService.SendCustomerEmailRevalidationMessage(customer, _workContext.WorkingLanguage.Id); - } - else - { - customer.Email = newEmail; - _customerService.UpdateCustomer(customer); - - //update newsletter subscription (if required) - if (!String.IsNullOrEmpty(oldEmail) && !oldEmail.Equals(newEmail, StringComparison.InvariantCultureIgnoreCase)) - { - foreach (var store in _storeService.GetAllStores()) - { - var subscriptionOld = _newsLetterSubscriptionService.GetNewsLetterSubscriptionByEmailAndStoreId(oldEmail, store.Id); - if (subscriptionOld != null) - { - subscriptionOld.Email = newEmail; - _newsLetterSubscriptionService.UpdateNewsLetterSubscription(subscriptionOld); - } - } - } - } - } - - /// - /// Sets a customer username - /// - /// Customer - /// New Username - public virtual void SetUsername(Customer customer, string newUsername) - { - if (customer == null) - throw new ArgumentNullException("customer"); - - if (!_customerSettings.UsernamesEnabled) - throw new NopException("Usernames are disabled"); - - newUsername = newUsername.Trim(); - - if (newUsername.Length > 100) - throw new NopException(_localizationService.GetResource("Account.EmailUsernameErrors.UsernameTooLong")); - - var user2 = _customerService.GetCustomerByUsername(newUsername); - if (user2 != null && customer.Id != user2.Id) - throw new NopException(_localizationService.GetResource("Account.EmailUsernameErrors.UsernameAlreadyExists")); - - customer.Username = newUsername; - _customerService.UpdateCustomer(customer); - } - - #endregion - } +using System; +using System.Linq; +using Nop.Core; +using Nop.Core.Domain.Customers; +using Nop.Services.Common; +using Nop.Services.Events; +using Nop.Services.Localization; +using Nop.Services.Messages; +using Nop.Services.Orders; +using Nop.Services.Security; +using Nop.Services.Stores; + +namespace Nop.Services.Customers +{ + /// + /// 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 IStoreService _storeService; + private readonly IRewardPointService _rewardPointService; + private readonly IGenericAttributeService _genericAttributeService; + private readonly IWorkContext _workContext; + private readonly IWorkflowMessageService _workflowMessageService; + private readonly IEventPublisher _eventPublisher; + private readonly RewardPointsSettings _rewardPointsSettings; + private readonly CustomerSettings _customerSettings; + + #endregion + + #region Ctor + + /// + /// Ctor + /// + /// Customer service + /// Encryption service + /// Newsletter subscription service + /// Localization service + /// Store service + /// Reward points service + /// Generic attribute service + /// Work context + /// Workflow message service + /// Event publisher + /// Reward points settings + /// Customer settings + public CustomerRegistrationService(ICustomerService customerService, + IEncryptionService encryptionService, + INewsLetterSubscriptionService newsLetterSubscriptionService, + ILocalizationService localizationService, + IStoreService storeService, + IRewardPointService rewardPointService, + IWorkContext workContext, + IGenericAttributeService genericAttributeService, + IWorkflowMessageService workflowMessageService, + IEventPublisher eventPublisher, + RewardPointsSettings rewardPointsSettings, + CustomerSettings customerSettings) + { + this._customerService = customerService; + this._encryptionService = encryptionService; + this._newsLetterSubscriptionService = newsLetterSubscriptionService; + this._localizationService = localizationService; + this._storeService = storeService; + this._rewardPointService = rewardPointService; + this._genericAttributeService = genericAttributeService; + this._workContext = workContext; + this._workflowMessageService = workflowMessageService; + this._eventPublisher = eventPublisher; + this._rewardPointsSettings = rewardPointsSettings; + this._customerSettings = customerSettings; + } + + #endregion + + #region Utilities + + /// + /// Check whether the entered password matches with saved one + /// + /// Customer password + /// The entered password + /// True if passwords match; otherwise false + protected bool PasswordsMatch(CustomerPassword customerPassword, string enteredPassword) + { + if (customerPassword == null || string.IsNullOrEmpty(enteredPassword)) + return false; + + var savedPassword = string.Empty; + switch (customerPassword.PasswordFormat) + { + case PasswordFormat.Clear: + savedPassword = enteredPassword; + break; + case PasswordFormat.Encrypted: + savedPassword = _encryptionService.EncryptText(enteredPassword); + break; + case PasswordFormat.Hashed: + savedPassword = _encryptionService.CreatePasswordHash(enteredPassword, customerPassword.PasswordSalt, _customerSettings.HashedPasswordFormat); + break; + } + + return customerPassword.Password.Equals(savedPassword); + } + + #endregion + + #region Methods + + /// + /// Validate customer + /// + /// Username or email + /// Password + /// Result + public virtual CustomerLoginResults ValidateCustomer(string usernameOrEmail, string password) + { + var customer = _customerSettings.UsernamesEnabled ? + _customerService.GetCustomerByUsername(usernameOrEmail) : + _customerService.GetCustomerByEmail(usernameOrEmail); + + if (customer == null) + return CustomerLoginResults.CustomerNotExist; + if (customer.Deleted) + return CustomerLoginResults.Deleted; + if (!customer.Active) + return CustomerLoginResults.NotActive; + //only registered can login + if (!customer.IsRegistered()) + return CustomerLoginResults.NotRegistered; + //check whether a customer is locked out + if (customer.CannotLoginUntilDateUtc.HasValue && customer.CannotLoginUntilDateUtc.Value > DateTime.UtcNow) + return CustomerLoginResults.LockedOut; + + if (!PasswordsMatch(_customerService.GetCurrentPassword(customer.Id), password)) + { + //wrong password + customer.FailedLoginAttempts++; + if (_customerSettings.FailedPasswordAllowedAttempts > 0 && + customer.FailedLoginAttempts >= _customerSettings.FailedPasswordAllowedAttempts) + { + //lock out + customer.CannotLoginUntilDateUtc = DateTime.UtcNow.AddMinutes(_customerSettings.FailedPasswordLockoutMinutes); + //reset the counter + customer.FailedLoginAttempts = 0; + } + _customerService.UpdateCustomer(customer); + + return CustomerLoginResults.WrongPassword; + } + + //update login details + customer.FailedLoginAttempts = 0; + customer.CannotLoginUntilDateUtc = null; + customer.RequireReLogin = false; + customer.LastLoginDateUtc = DateTime.UtcNow; + _customerService.UpdateCustomer(customer); + + return CustomerLoginResults.Successful; + } + + /// + /// Register customer + /// + /// Request + /// 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"); + + var result = new CustomerRegistrationResult(); + if (request.Customer.IsSearchEngineAccount()) + { + result.AddError("Search engine can't be registered"); + return result; + } + if (request.Customer.IsBackgroundTaskAccount()) + { + result.AddError("Background task account can't be registered"); + return result; + } + if (request.Customer.IsRegistered()) + { + result.AddError("Current customer is already registered"); + return result; + } + if (String.IsNullOrEmpty(request.Email)) + { + result.AddError(_localizationService.GetResource("Account.Register.Errors.EmailIsNotProvided")); + return result; + } + if (!CommonHelper.IsValidEmail(request.Email)) + { + result.AddError(_localizationService.GetResource("Common.WrongEmail")); + return result; + } + if (String.IsNullOrWhiteSpace(request.Password)) + { + result.AddError(_localizationService.GetResource("Account.Register.Errors.PasswordIsNotProvided")); + return result; + } + if (_customerSettings.UsernamesEnabled) + { + if (String.IsNullOrEmpty(request.Username)) + { + result.AddError(_localizationService.GetResource("Account.Register.Errors.UsernameIsNotProvided")); + return result; + } + } + + //validate unique user + if (_customerService.GetCustomerByEmail(request.Email) != null) + { + result.AddError(_localizationService.GetResource("Account.Register.Errors.EmailAlreadyExists")); + return result; + } + if (_customerSettings.UsernamesEnabled) + { + if (_customerService.GetCustomerByUsername(request.Username) != null) + { + result.AddError(_localizationService.GetResource("Account.Register.Errors.UsernameAlreadyExists")); + return result; + } + } + + //at this point request is valid + request.Customer.Username = request.Username; + request.Customer.Email = request.Email; + + var customerPassword = new CustomerPassword + { + Customer = request.Customer, + PasswordFormat = request.PasswordFormat, + CreatedOnUtc = DateTime.UtcNow + }; + switch (request.PasswordFormat) + { + case PasswordFormat.Clear: + customerPassword.Password = request.Password; + break; + case PasswordFormat.Encrypted: + customerPassword.Password = _encryptionService.EncryptText(request.Password); + break; + case PasswordFormat.Hashed: + { + var saltKey = _encryptionService.CreateSaltKey(5); + customerPassword.PasswordSalt = saltKey; + customerPassword.Password = _encryptionService.CreatePasswordHash(request.Password, saltKey, _customerSettings.HashedPasswordFormat); + } + break; + } + _customerService.InsertCustomerPassword(customerPassword); + + request.Customer.Active = request.IsApproved; + + //add to 'Registered' role + var registeredRole = _customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.Registered); + if (registeredRole == null) + throw new NopException("'Registered' role could not be loaded"); + 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); + + //Add reward points for customer registration (if enabled) + if (_rewardPointsSettings.Enabled && + _rewardPointsSettings.PointsForRegistration > 0) + { + _rewardPointService.AddRewardPointsHistoryEntry(request.Customer, + new RewardPoints(_rewardPointService) { Points = _rewardPointsSettings.PointsForRegistration }, + request.StoreId, + _localizationService.GetResource("RewardPoints.Message.EarnedForRegistration")); + } + + _customerService.UpdateCustomer(request.Customer); + + //publish event + _eventPublisher.Publish(new CustomerPasswordChangedEvent(customerPassword)); + + return result; + } + + /// + /// Change password + /// + /// Request + /// Result + public virtual ChangePasswordResult ChangePassword(ChangePasswordRequest request) + { + if (request == null) + throw new ArgumentNullException("request"); + + var result = new ChangePasswordResult(); + if (String.IsNullOrWhiteSpace(request.Email)) + { + result.AddError(_localizationService.GetResource("Account.ChangePassword.Errors.EmailIsNotProvided")); + return result; + } + if (String.IsNullOrWhiteSpace(request.NewPassword)) + { + result.AddError(_localizationService.GetResource("Account.ChangePassword.Errors.PasswordIsNotProvided")); + return result; + } + + var customer = _customerService.GetCustomerByEmail(request.Email); + if (customer == null) + { + result.AddError(_localizationService.GetResource("Account.ChangePassword.Errors.EmailNotFound")); + return result; + } + + if (request.ValidateRequest) + { + //request isn't valid + if (!PasswordsMatch(_customerService.GetCurrentPassword(customer.Id), request.OldPassword)) + { + result.AddError(_localizationService.GetResource("Account.ChangePassword.Errors.OldPasswordDoesntMatch")); + return result; + } + } + + //check for duplicates + if (_customerSettings.UnduplicatedPasswordsNumber > 0) + { + //get some of previous passwords + var previousPasswords = _customerService.GetCustomerPasswords(customer.Id, passwordsToReturn: _customerSettings.UnduplicatedPasswordsNumber); + + var newPasswordMatchesWithPrevious = previousPasswords.Any(password => PasswordsMatch(password, request.NewPassword)); + if (newPasswordMatchesWithPrevious) + { + result.AddError(_localizationService.GetResource("Account.ChangePassword.Errors.PasswordMatchesWithPrevious")); + return result; + } + } + + //at this point request is valid + var customerPassword = new CustomerPassword + { + Customer = customer, + PasswordFormat = request.NewPasswordFormat, + CreatedOnUtc = DateTime.UtcNow + }; + switch (request.NewPasswordFormat) + { + case PasswordFormat.Clear: + customerPassword.Password = request.NewPassword; + break; + case PasswordFormat.Encrypted: + customerPassword.Password = _encryptionService.EncryptText(request.NewPassword); + break; + case PasswordFormat.Hashed: + { + var saltKey = _encryptionService.CreateSaltKey(5); + customerPassword.PasswordSalt = saltKey; + customerPassword.Password = _encryptionService.CreatePasswordHash(request.NewPassword, saltKey, _customerSettings.HashedPasswordFormat); + } + break; + } + _customerService.InsertCustomerPassword(customerPassword); + + //publish event + _eventPublisher.Publish(new CustomerPasswordChangedEvent(customerPassword)); + + return result; + } + + /// + /// Sets a user email + /// + /// Customer + /// New email + /// Require validation of new email address + public virtual void SetEmail(Customer customer, string newEmail, bool requireValidation) + { + if (customer == null) + throw new ArgumentNullException("customer"); + + if (newEmail == null) + throw new NopException("Email cannot be null"); + + newEmail = newEmail.Trim(); + string oldEmail = customer.Email; + + if (!CommonHelper.IsValidEmail(newEmail)) + throw new NopException(_localizationService.GetResource("Account.EmailUsernameErrors.NewEmailIsNotValid")); + + if (newEmail.Length > 100) + throw new NopException(_localizationService.GetResource("Account.EmailUsernameErrors.EmailTooLong")); + + var customer2 = _customerService.GetCustomerByEmail(newEmail); + if (customer2 != null && customer.Id != customer2.Id) + throw new NopException(_localizationService.GetResource("Account.EmailUsernameErrors.EmailAlreadyExists")); + + if (requireValidation) + { + //re-validate email + customer.EmailToRevalidate = newEmail; + _customerService.UpdateCustomer(customer); + + //email re-validation message + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.EmailRevalidationToken, Guid.NewGuid().ToString()); + _workflowMessageService.SendCustomerEmailRevalidationMessage(customer, _workContext.WorkingLanguage.Id); + } + else + { + customer.Email = newEmail; + _customerService.UpdateCustomer(customer); + + //update newsletter subscription (if required) + if (!String.IsNullOrEmpty(oldEmail) && !oldEmail.Equals(newEmail, StringComparison.InvariantCultureIgnoreCase)) + { + foreach (var store in _storeService.GetAllStores()) + { + var subscriptionOld = _newsLetterSubscriptionService.GetNewsLetterSubscriptionByEmailAndStoreId(oldEmail, store.Id); + if (subscriptionOld != null) + { + subscriptionOld.Email = newEmail; + _newsLetterSubscriptionService.UpdateNewsLetterSubscription(subscriptionOld); + } + } + } + } + } + + /// + /// Sets a customer username + /// + /// Customer + /// New Username + public virtual void SetUsername(Customer customer, string newUsername) + { + if (customer == null) + throw new ArgumentNullException("customer"); + + if (!_customerSettings.UsernamesEnabled) + throw new NopException("Usernames are disabled"); + + newUsername = newUsername.Trim(); + + if (newUsername.Length > 100) + throw new NopException(_localizationService.GetResource("Account.EmailUsernameErrors.UsernameTooLong")); + + var user2 = _customerService.GetCustomerByUsername(newUsername); + if (user2 != null && customer.Id != user2.Id) + throw new NopException(_localizationService.GetResource("Account.EmailUsernameErrors.UsernameAlreadyExists")); + + customer.Username = newUsername; + _customerService.UpdateCustomer(customer); + } + + #endregion + } } \ No newline at end of file diff --git a/src/Libraries/Nop.Services/Discounts/DiscountService.cs b/src/Libraries/Nop.Services/Discounts/DiscountService.cs index 5b6bb0f809f..16d908afc83 100644 --- a/src/Libraries/Nop.Services/Discounts/DiscountService.cs +++ b/src/Libraries/Nop.Services/Discounts/DiscountService.cs @@ -1,726 +1,733 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Nop.Core; -using Nop.Core.Caching; -using Nop.Core.Data; -using Nop.Core.Domain.Customers; -using Nop.Core.Domain.Discounts; -using Nop.Core.Domain.Orders; -using Nop.Core.Plugins; -using Nop.Services.Catalog; -using Nop.Services.Customers; -using Nop.Services.Discounts.Cache; -using Nop.Services.Events; -using Nop.Services.Localization; -using Nop.Services.Orders; - -namespace Nop.Services.Discounts -{ - /// - /// Discount service - /// - public partial class DiscountService : IDiscountService - { - #region Fields - - private readonly IRepository _discountRepository; - private readonly IRepository _discountRequirementRepository; - private readonly IRepository _discountUsageHistoryRepository; - private readonly ICacheManager _cacheManager; - private readonly IStoreContext _storeContext; - private readonly ILocalizationService _localizationService; - private readonly ICategoryService _categoryService; - private readonly IPluginFinder _pluginFinder; - private readonly IEventPublisher _eventPublisher; - private readonly IWorkContext _workContext; - - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Cache manager - /// Discount repository - /// Discount requirement repository - /// Discount usage history repository - /// Store context - /// Localization service - /// Category service - /// Plugin finder - /// Event published - /// work context - public DiscountService(ICacheManager cacheManager, - IRepository discountRepository, - IRepository discountRequirementRepository, - IRepository discountUsageHistoryRepository, - IStoreContext storeContext, - ILocalizationService localizationService, - ICategoryService categoryService, - IPluginFinder pluginFinder, - IEventPublisher eventPublisher, - IWorkContext workContext) - { - this._cacheManager = cacheManager; - this._discountRepository = discountRepository; - this._discountRequirementRepository = discountRequirementRepository; - this._discountUsageHistoryRepository = discountUsageHistoryRepository; - this._storeContext = storeContext; - this._localizationService = localizationService; - this._categoryService = categoryService; - this._pluginFinder = pluginFinder; - this._eventPublisher = eventPublisher; - this._workContext = workContext; - } - - #endregion - - #region Nested classes - - [Serializable] - public class DiscountRequirementForCaching - { - public DiscountRequirementForCaching() - { - ChildRequirements = new List(); - } - - public int Id { get; set; } - public string SystemName { get; set; } - public bool IsGroup { get; set; } - public RequirementGroupInteractionType? InteractionType { get; set; } - public IList ChildRequirements { get; set; } - } - - #endregion - - #region Utilities - - /// - /// Get requirements for caching - /// - /// Collection of discount requirement - /// List of DiscountRequirementForCaching - protected IList GetReqirementsForCaching(IEnumerable requirements) - { - var requirementForCaching = requirements.Select(requirement => new DiscountRequirementForCaching - { - Id = requirement.Id, - IsGroup = requirement.IsGroup, - SystemName = requirement.DiscountRequirementRuleSystemName, - InteractionType = requirement.InteractionType, - ChildRequirements = GetReqirementsForCaching(requirement.ChildRequirements) - }); - - return requirementForCaching.ToList(); - } - - /// - /// Get discount validation result - /// - /// Collection of discount requirement - /// Interaction type within the group of requirements - /// Customer - /// Errors - /// True if result is valid; otherwise false - protected bool GetValidationResult(IEnumerable requirements, - RequirementGroupInteractionType groupInteractionType, Customer customer, List errors) - { - var result = false; - - foreach (var requirement in requirements) - { - if (requirement.IsGroup) - { - //get child requirements for the group - var interactionType = requirement.InteractionType.HasValue - ? requirement.InteractionType.Value : RequirementGroupInteractionType.And; - result = GetValidationResult(requirement.ChildRequirements, interactionType, customer, errors); - } - else - { - //or try to get validation result for the requirement - var requirementRulePlugin = LoadDiscountRequirementRuleBySystemName(requirement.SystemName); - if (requirementRulePlugin == null) - continue; - - if (!_pluginFinder.AuthorizedForUser(requirementRulePlugin.PluginDescriptor, customer)) - continue; - - if (!_pluginFinder.AuthenticateStore(requirementRulePlugin.PluginDescriptor, _storeContext.CurrentStore.Id)) - continue; - - var ruleResult = requirementRulePlugin.CheckRequirement(new DiscountRequirementValidationRequest - { - DiscountRequirementId = requirement.Id, - Customer = customer, - Store = _storeContext.CurrentStore - }); - - //add validation error - if (!ruleResult.IsValid) - errors.Add(ruleResult.UserError); - - result = ruleResult.IsValid; - } - - //all requirements must be met, so return false - if (!result && groupInteractionType == RequirementGroupInteractionType.And) - return result; - - //any of requirements must be met, so return true - if (result && groupInteractionType == RequirementGroupInteractionType.Or) - return result; - } - - return result; - } - - #endregion - - #region Methods - - #region Discounts - - /// - /// Delete discount - /// - /// Discount - public virtual void DeleteDiscount(Discount discount) - { - if (discount == null) - throw new ArgumentNullException("discount"); - - _discountRepository.Delete(discount); - - //event notification - _eventPublisher.EntityDeleted(discount); - } - - /// - /// Gets a discount - /// - /// Discount identifier - /// Discount - public virtual Discount GetDiscountById(int discountId) - { - if (discountId == 0) - return null; - - return _discountRepository.GetById(discountId); - } - - /// - /// Gets all discounts - /// - /// Discount type; null to load all discount - /// Coupon code to find (exact match) - /// Discount name - /// A value indicating whether to show hidden records - /// Discounts - public virtual IList GetAllDiscounts(DiscountType? discountType = null, - string couponCode = "", string discountName = "", bool showHidden = false) - { - var query = _discountRepository.Table; - if (!showHidden) - { - //The function 'CurrentUtcDateTime' is not supported by SQL Server Compact. - //That's why we pass the date value - var nowUtc = DateTime.UtcNow; - query = query.Where(d => - (!d.StartDateUtc.HasValue || d.StartDateUtc <= nowUtc) - && (!d.EndDateUtc.HasValue || d.EndDateUtc >= nowUtc)); - } - if (!String.IsNullOrEmpty(couponCode)) - { - query = query.Where(d => d.CouponCode == couponCode); - } - if (!String.IsNullOrEmpty(discountName)) - { - query = query.Where(d => d.Name.Contains(discountName)); - } - if (discountType.HasValue) - { - int discountTypeId = (int) discountType.Value; - query = query.Where(d => d.DiscountTypeId == discountTypeId); - } - - query = query.OrderBy(d => d.Name); - - var discounts = query.ToList(); - return discounts; - } - - /// - /// Inserts a discount - /// - /// Discount - public virtual void InsertDiscount(Discount discount) - { - if (discount == null) - throw new ArgumentNullException("discount"); - - _discountRepository.Insert(discount); - - //event notification - _eventPublisher.EntityInserted(discount); - } - - /// - /// Updates the discount - /// - /// Discount - public virtual void UpdateDiscount(Discount discount) - { - if (discount == null) - throw new ArgumentNullException("discount"); - - _discountRepository.Update(discount); - - //event notification - _eventPublisher.EntityUpdated(discount); - } - - #endregion - - #region Discounts (caching) - - /// - /// Gets all discounts (cachable models) - /// - /// Discount type; null to load all discount - /// Coupon code to find (exact match) - /// Discount name - /// A value indicating whether to show hidden records - /// Discounts - public virtual IList GetAllDiscountsForCaching(DiscountType? discountType = null, - string couponCode = "", string discountName = "", bool showHidden = false) - { - //we cache discounts between requests. Otherwise, they will be loaded for almost each HTTP request - //we have to use the following workaround with cachable model (DiscountForCaching) because - //Entity Framework doesn't support 2-level caching - - //we load all discounts, and filter them using "discountType" parameter later (in memory) - //we do it because we know that this method is invoked several times per HTTP request with distinct "discountType" parameter - //that's why let's access the database only once - string key = string.Format(DiscountEventConsumer.DISCOUNT_ALL_KEY, showHidden, couponCode, discountName); - var result = _cacheManager.Get(key, () => - { - var discounts = GetAllDiscounts(null, couponCode, discountName, showHidden); - return discounts.Select(d => d.MapDiscount()).ToList(); - }); - //we know that this method is usually inkoved multiple times - //that's why we filter discounts by type on the application layer - if (discountType.HasValue) - { - result = result.Where(d => d.DiscountType == discountType.Value).ToList(); - } - return result; - } - - /// - /// Get category identifiers to which a discount is applied - /// - /// Discount - /// Customer - /// Category identifiers - public virtual IList GetAppliedCategoryIds(DiscountForCaching discount, Customer customer) - { - if (discount == null) - throw new ArgumentNullException("discount"); - - var discountId = discount.Id; - var cacheKey = string.Format(DiscountEventConsumer.DISCOUNT_CATEGORY_IDS_MODEL_KEY, - discountId, - string.Join(",", customer.GetCustomerRoleIds()), - _storeContext.CurrentStore.Id); - var result = _cacheManager.Get(cacheKey, () => - { - var ids = new List(); - var rootCategoryIds = _discountRepository.Table.Where(x => x.Id == discountId) - .SelectMany(x => x.AppliedToCategories.Select(c => c.Id)) - .ToList(); - foreach (var categoryId in rootCategoryIds) - { - if (!ids.Contains(categoryId)) - ids.Add(categoryId); - if (discount.AppliedToSubCategories) - { - //include subcategories - foreach (var childCategoryId in _categoryService - .GetAllCategoriesByParentCategoryId(categoryId, false, true) - .Select(x => x.Id)) - { - if (!ids.Contains(childCategoryId)) - ids.Add(childCategoryId); - } - } - } - return ids; - }); - - return result; - } - - /// - /// Get manufacturer identifiers to which a discount is applied - /// - /// Discount - /// Customer - /// Manufacturer identifiers - public virtual IList GetAppliedManufacturerIds(DiscountForCaching discount, Customer customer) - { - if (discount == null) - throw new ArgumentNullException("discount"); - - var discountId = discount.Id; - var cacheKey = string.Format(DiscountEventConsumer.DISCOUNT_MANUFACTURER_IDS_MODEL_KEY, - discountId, - string.Join(",", customer.GetCustomerRoleIds()), - _storeContext.CurrentStore.Id); - var result = _cacheManager.Get(cacheKey, () => - { - return _discountRepository.Table.Where(x => x.Id == discountId) - .SelectMany(x => x.AppliedToManufacturers.Select(c => c.Id)) - .ToList(); - }); - - return result; - } - - #endregion - - #region Discount requirements - - /// - /// Get all discount requirements - /// - /// Discont identifier - /// Whether to load top-level requirements only (without parent identifier) - /// Requirements - public virtual IList GetAllDiscountRequirements(int discountId = 0, bool topLevelOnly = false) - { - var query = _discountRequirementRepository.Table; - - //filter by discount - if (discountId > 0) - query = query.Where(requirement => requirement.DiscountId == discountId); - - //filter by top-level - if (topLevelOnly) - query = query.Where(requirement => !requirement.ParentId.HasValue); - - query = query.OrderBy(requirement => requirement.Id); - - return query.ToList(); - } - - /// - /// Delete discount requirement - /// - /// Discount requirement - public virtual void DeleteDiscountRequirement(DiscountRequirement discountRequirement) - { - if (discountRequirement == null) - throw new ArgumentNullException("discountRequirement"); - - _discountRequirementRepository.Delete(discountRequirement); - - //event notification - _eventPublisher.EntityDeleted(discountRequirement); - } - - /// - /// Load discount requirement rule by system name - /// - /// System name - /// Found discount requirement rule - public virtual IDiscountRequirementRule LoadDiscountRequirementRuleBySystemName(string systemName) - { - var descriptor = _pluginFinder.GetPluginDescriptorBySystemName(systemName); - if (descriptor != null) - return descriptor.Instance(); - - return null; - } - - /// - /// Load all discount requirement rules - /// - /// Load records allowed only to a specified customer; pass null to ignore ACL permissions - /// Discount requirement rules - public virtual IList LoadAllDiscountRequirementRules(Customer customer = null) - { - return _pluginFinder.GetPlugins(customer: customer).ToList(); - } - - #endregion - - #region Validation - - /// - /// Validate discount - /// - /// Discount - /// Customer - /// Discount validation result - public virtual DiscountValidationResult ValidateDiscount(Discount discount, Customer customer) - { - if (discount == null) - throw new ArgumentNullException("discount"); - - return ValidateDiscount(discount.MapDiscount(), customer); - } - - /// - /// Validate discount - /// - /// Discount - /// Customer - /// Coupon codes to validate - /// Discount validation result - public virtual DiscountValidationResult ValidateDiscount(Discount discount, Customer customer, string[] couponCodesToValidate) - { - if (discount == null) - throw new ArgumentNullException("discount"); - - return ValidateDiscount(discount.MapDiscount(), customer, couponCodesToValidate); - } - - /// - /// Validate discount - /// - /// Discount - /// Customer - /// Discount validation result - public virtual DiscountValidationResult ValidateDiscount(DiscountForCaching discount, Customer customer) - { - if (discount == null) - throw new ArgumentNullException("discount"); - - if (customer == null) - throw new ArgumentNullException("customer"); - - string[] couponCodesToValidate = customer.ParseAppliedDiscountCouponCodes(); - return ValidateDiscount(discount, customer, couponCodesToValidate); - } - - /// - /// Validate discount - /// - /// Discount - /// Customer - /// Coupon codes to validate - /// Discount validation result - public virtual DiscountValidationResult ValidateDiscount(DiscountForCaching discount, Customer customer, string[] couponCodesToValidate) - { - if (discount == null) - throw new ArgumentNullException("discount"); - - if (customer == null) - throw new ArgumentNullException("customer"); - - //invalid by default - var result = new DiscountValidationResult(); - - //check coupon code - if (discount.RequiresCouponCode) - { - if (String.IsNullOrEmpty(discount.CouponCode)) - return result; - - if (couponCodesToValidate == null) - return result; - - if (!couponCodesToValidate.Any(x => x.Equals(discount.CouponCode, StringComparison.InvariantCultureIgnoreCase))) - return result; - } - - //Do not allow discounts applied to order subtotal or total when a customer has gift cards in the cart. - //Otherwise, this customer can purchase gift cards with discount and get more than paid ("free money"). - if (discount.DiscountType == DiscountType.AssignedToOrderSubTotal || - discount.DiscountType == DiscountType.AssignedToOrderTotal) - { - var cart = customer.ShoppingCartItems - .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) - .LimitPerStore(_storeContext.CurrentStore.Id) - .ToList(); - - var hasGiftCards = cart.Any(x => x.Product.IsGiftCard); - if (hasGiftCards) - { - result.Errors = new List { _localizationService.GetResource("ShoppingCart.Discount.CannotBeUsedWithGiftCards") }; - return result; - } - } - - //check date range - DateTime now = DateTime.UtcNow; - if (discount.StartDateUtc.HasValue) - { - DateTime startDate = DateTime.SpecifyKind(discount.StartDateUtc.Value, DateTimeKind.Utc); - if (startDate.CompareTo(now) > 0) - { - result.Errors = new List { _localizationService.GetResource("ShoppingCart.Discount.NotStartedYet") }; - return result; - } - } - if (discount.EndDateUtc.HasValue) - { - DateTime endDate = DateTime.SpecifyKind(discount.EndDateUtc.Value, DateTimeKind.Utc); - if (endDate.CompareTo(now) < 0) - { - result.Errors = new List { _localizationService.GetResource("ShoppingCart.Discount.Expired") }; - return result; - } - } - - //discount limitation - switch (discount.DiscountLimitation) - { - case DiscountLimitationType.NTimesOnly: - { - var usedTimes = GetAllDiscountUsageHistory(discount.Id, null, null, 0, 1).TotalCount; - if (usedTimes >= discount.LimitationTimes) - return result; - } - break; - case DiscountLimitationType.NTimesPerCustomer: - { - if (customer.IsRegistered()) - { - var usedTimes = GetAllDiscountUsageHistory(discount.Id, customer.Id, null, 0, 1).TotalCount; - if (usedTimes >= discount.LimitationTimes) - { - result.Errors = new List { _localizationService.GetResource("ShoppingCart.Discount.CannotBeUsedAnymore") }; - return result; - } - } - } - break; - case DiscountLimitationType.Unlimited: - default: - break; - } - - //discount requirements - string key = string.Format(DiscountEventConsumer.DISCOUNT_REQUIREMENT_MODEL_KEY, discount.Id); - var requirementsForCaching = _cacheManager.Get(key, () => - { - var requirements = GetAllDiscountRequirements(discount.Id, true); - return GetReqirementsForCaching(requirements); - }); - - //get top-level group - var topLevelGroup = requirementsForCaching.FirstOrDefault(); - if (topLevelGroup == null || (topLevelGroup.IsGroup && !topLevelGroup.ChildRequirements.Any()) || !topLevelGroup.InteractionType.HasValue) - { - //there are no requirements, so discount is valid - result.IsValid = true; - return result; - } - - //requirements are exist, let's check them - var errors = new List(); - result.IsValid = GetValidationResult(requirementsForCaching, topLevelGroup.InteractionType.Value, customer, errors); - - //set errors if result is not valid - if (!result.IsValid) - result.Errors = errors; - - return result; - } - - #endregion - - #region Discount usage history - - /// - /// Gets a discount usage history record - /// - /// Discount usage history record identifier - /// Discount usage history - public virtual DiscountUsageHistory GetDiscountUsageHistoryById(int discountUsageHistoryId) - { - if (discountUsageHistoryId == 0) - return null; - - return _discountUsageHistoryRepository.GetById(discountUsageHistoryId); - } - - /// - /// Gets all discount usage history records - /// - /// Discount identifier; null to load all records - /// Customer identifier; null to load all records - /// Order identifier; null to load all records - /// Page index - /// Page size - /// Discount usage history records - public virtual IPagedList GetAllDiscountUsageHistory(int? discountId = null, - int? customerId = null, int? orderId = null, - int pageIndex = 0, int pageSize = int.MaxValue) - { - var query = _discountUsageHistoryRepository.Table; - if (discountId.HasValue && discountId.Value > 0) - query = query.Where(duh => duh.DiscountId == discountId.Value); - if (customerId.HasValue && customerId.Value > 0) - query = query.Where(duh => duh.Order != null && duh.Order.CustomerId == customerId.Value); - if (orderId.HasValue && orderId.Value > 0) - query = query.Where(duh => duh.OrderId == orderId.Value); - query = query.OrderByDescending(c => c.CreatedOnUtc); - return new PagedList(query, pageIndex, pageSize); - } - - /// - /// Insert discount usage history record - /// - /// Discount usage history record - public virtual void InsertDiscountUsageHistory(DiscountUsageHistory discountUsageHistory) - { - if (discountUsageHistory == null) - throw new ArgumentNullException("discountUsageHistory"); - - _discountUsageHistoryRepository.Insert(discountUsageHistory); - - //event notification - _eventPublisher.EntityInserted(discountUsageHistory); - } - - /// - /// Update discount usage history record - /// - /// Discount usage history record - public virtual void UpdateDiscountUsageHistory(DiscountUsageHistory discountUsageHistory) - { - if (discountUsageHistory == null) - throw new ArgumentNullException("discountUsageHistory"); - - _discountUsageHistoryRepository.Update(discountUsageHistory); - - //event notification - _eventPublisher.EntityUpdated(discountUsageHistory); - } - - /// - /// Delete discount usage history record - /// - /// Discount usage history record - public virtual void DeleteDiscountUsageHistory(DiscountUsageHistory discountUsageHistory) - { - if (discountUsageHistory == null) - throw new ArgumentNullException("discountUsageHistory"); - - _discountUsageHistoryRepository.Delete(discountUsageHistory); - - //event notification - _eventPublisher.EntityDeleted(discountUsageHistory); - } - - #endregion - - #endregion - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using Nop.Core; +using Nop.Core.Caching; +using Nop.Core.Data; +using Nop.Core.Domain.Customers; +using Nop.Core.Domain.Discounts; +using Nop.Core.Domain.Orders; +using Nop.Core.Plugins; +using Nop.Services.Catalog; +using Nop.Services.Customers; +using Nop.Services.Discounts.Cache; +using Nop.Services.Events; +using Nop.Services.Localization; +using Nop.Services.Orders; + +namespace Nop.Services.Discounts +{ + /// + /// Discount service + /// + public partial class DiscountService : IDiscountService + { + #region Fields + + private readonly IRepository _discountRepository; + private readonly IRepository _discountRequirementRepository; + private readonly IRepository _discountUsageHistoryRepository; + private readonly ICacheManager _cacheManager; + private readonly IStoreContext _storeContext; + private readonly ILocalizationService _localizationService; + private readonly ICategoryService _categoryService; + private readonly IPluginFinder _pluginFinder; + private readonly IEventPublisher _eventPublisher; + private readonly IWorkContext _workContext; + + #endregion + + #region Ctor + + /// + /// Ctor + /// + /// Cache manager + /// Discount repository + /// Discount requirement repository + /// Discount usage history repository + /// Store context + /// Localization service + /// Category service + /// Plugin finder + /// Event published + /// work context + public DiscountService(ICacheManager cacheManager, + IRepository discountRepository, + IRepository discountRequirementRepository, + IRepository discountUsageHistoryRepository, + IStoreContext storeContext, + ILocalizationService localizationService, + ICategoryService categoryService, + IPluginFinder pluginFinder, + IEventPublisher eventPublisher, + IWorkContext workContext) + { + this._cacheManager = cacheManager; + this._discountRepository = discountRepository; + this._discountRequirementRepository = discountRequirementRepository; + this._discountUsageHistoryRepository = discountUsageHistoryRepository; + this._storeContext = storeContext; + this._localizationService = localizationService; + this._categoryService = categoryService; + this._pluginFinder = pluginFinder; + this._eventPublisher = eventPublisher; + this._workContext = workContext; + } + + #endregion + + #region Nested classes + + [Serializable] + public class DiscountRequirementForCaching + { + public DiscountRequirementForCaching() + { + ChildRequirements = new List(); + } + + public int Id { get; set; } + public string SystemName { get; set; } + public bool IsGroup { get; set; } + public RequirementGroupInteractionType? InteractionType { get; set; } + public IList ChildRequirements { get; set; } + } + + #endregion + + #region Utilities + + /// + /// Get requirements for caching + /// + /// Collection of discount requirement + /// List of DiscountRequirementForCaching + protected IList GetReqirementsForCaching(IEnumerable requirements) + { + var requirementForCaching = requirements.Select(requirement => new DiscountRequirementForCaching + { + Id = requirement.Id, + IsGroup = requirement.IsGroup, + SystemName = requirement.DiscountRequirementRuleSystemName, + InteractionType = requirement.InteractionType, + ChildRequirements = GetReqirementsForCaching(requirement.ChildRequirements) + }); + + return requirementForCaching.ToList(); + } + + /// + /// Get discount validation result + /// + /// Collection of discount requirement + /// Interaction type within the group of requirements + /// Customer + /// Errors + /// True if result is valid; otherwise false + protected bool GetValidationResult(IEnumerable requirements, + RequirementGroupInteractionType groupInteractionType, Customer customer, List errors) + { + var result = false; + + foreach (var requirement in requirements) + { + if (requirement.IsGroup) + { + //get child requirements for the group + var interactionType = requirement.InteractionType.HasValue + ? requirement.InteractionType.Value : RequirementGroupInteractionType.And; + result = GetValidationResult(requirement.ChildRequirements, interactionType, customer, errors); + } + else + { + //or try to get validation result for the requirement + var requirementRulePlugin = LoadDiscountRequirementRuleBySystemName(requirement.SystemName); + if (requirementRulePlugin == null) + continue; + + if (!_pluginFinder.AuthorizedForUser(requirementRulePlugin.PluginDescriptor, customer)) + continue; + + if (!_pluginFinder.AuthenticateStore(requirementRulePlugin.PluginDescriptor, _storeContext.CurrentStore.Id)) + continue; + + var ruleResult = requirementRulePlugin.CheckRequirement(new DiscountRequirementValidationRequest + { + DiscountRequirementId = requirement.Id, + Customer = customer, + Store = _storeContext.CurrentStore + }); + + //add validation error + if (!ruleResult.IsValid) + errors.Add(ruleResult.UserError); + + result = ruleResult.IsValid; + } + + //all requirements must be met, so return false + if (!result && groupInteractionType == RequirementGroupInteractionType.And) + return result; + + //any of requirements must be met, so return true + if (result && groupInteractionType == RequirementGroupInteractionType.Or) + return result; + } + + return result; + } + + #endregion + + #region Methods + + #region Discounts + + /// + /// Delete discount + /// + /// Discount + public virtual void DeleteDiscount(Discount discount) + { + if (discount == null) + throw new ArgumentNullException("discount"); + + _discountRepository.Delete(discount); + + //event notification + _eventPublisher.EntityDeleted(discount); + } + + /// + /// Gets a discount + /// + /// Discount identifier + /// Discount + public virtual Discount GetDiscountById(int discountId) + { + if (discountId == 0) + return null; + + return _discountRepository.GetById(discountId); + } + + /// + /// Gets all discounts + /// + /// Discount type; null to load all discount + /// Coupon code to find (exact match) + /// Discount name + /// A value indicating whether to show hidden records + /// Discounts + public virtual IList GetAllDiscounts(DiscountType? discountType = null, + string couponCode = "", string discountName = "", bool showHidden = false) + { + var query = _discountRepository.Table; + if (!showHidden) + { + //The function 'CurrentUtcDateTime' is not supported by SQL Server Compact. + //That's why we pass the date value + var nowUtc = DateTime.UtcNow; + query = query.Where(d => + (!d.StartDateUtc.HasValue || d.StartDateUtc <= nowUtc) + && (!d.EndDateUtc.HasValue || d.EndDateUtc >= nowUtc)); + } + if (!String.IsNullOrEmpty(couponCode)) + { + query = query.Where(d => d.CouponCode == couponCode); + } + if (!String.IsNullOrEmpty(discountName)) + { + query = query.Where(d => d.Name.Contains(discountName)); + } + if (discountType.HasValue) + { + int discountTypeId = (int) discountType.Value; + query = query.Where(d => d.DiscountTypeId == discountTypeId); + } + + query = query.OrderBy(d => d.Name); + + var discounts = query.ToList(); + return discounts; + } + + /// + /// Inserts a discount + /// + /// Discount + public virtual void InsertDiscount(Discount discount) + { + if (discount == null) + throw new ArgumentNullException("discount"); + + _discountRepository.Insert(discount); + + //event notification + _eventPublisher.EntityInserted(discount); + } + + /// + /// Updates the discount + /// + /// Discount + public virtual void UpdateDiscount(Discount discount) + { + if (discount == null) + throw new ArgumentNullException("discount"); + + _discountRepository.Update(discount); + + //event notification + _eventPublisher.EntityUpdated(discount); + } + + #endregion + + #region Discounts (caching) + + /// + /// Gets all discounts (cachable models) + /// + /// Discount type; null to load all discount + /// Coupon code to find (exact match) + /// Discount name + /// A value indicating whether to show hidden records + /// Discounts + public virtual IList GetAllDiscountsForCaching(DiscountType? discountType = null, + string couponCode = "", string discountName = "", bool showHidden = false) + { + //we cache discounts between requests. Otherwise, they will be loaded for almost each HTTP request + //we have to use the following workaround with cachable model (DiscountForCaching) because + //Entity Framework doesn't support 2-level caching + + //we load all discounts, and filter them using "discountType" parameter later (in memory) + //we do it because we know that this method is invoked several times per HTTP request with distinct "discountType" parameter + //that's why let's access the database only once + string key = string.Format(DiscountEventConsumer.DISCOUNT_ALL_KEY, showHidden, couponCode, discountName); + var result = _cacheManager.Get(key, () => + { + var discounts = GetAllDiscounts(null, couponCode, discountName, showHidden); + return discounts.Select(d => d.MapDiscount()).ToList(); + }); + //we know that this method is usually inkoved multiple times + //that's why we filter discounts by type on the application layer + if (discountType.HasValue) + { + result = result.Where(d => d.DiscountType == discountType.Value).ToList(); + } + return result; + } + + /// + /// Get category identifiers to which a discount is applied + /// + /// Discount + /// Customer + /// Category identifiers + public virtual IList GetAppliedCategoryIds(DiscountForCaching discount, Customer customer) + { + if (discount == null) + throw new ArgumentNullException("discount"); + + var discountId = discount.Id; + var cacheKey = string.Format(DiscountEventConsumer.DISCOUNT_CATEGORY_IDS_MODEL_KEY, + discountId, + string.Join(",", customer.GetCustomerRoleIds()), + _storeContext.CurrentStore.Id); + var result = _cacheManager.Get(cacheKey, () => + { + var ids = new List(); + var rootCategoryIds = _discountRepository.Table.Where(x => x.Id == discountId) + .SelectMany(x => x.AppliedToCategories.Select(c => c.Id)) + .ToList(); + foreach (var categoryId in rootCategoryIds) + { + if (!ids.Contains(categoryId)) + ids.Add(categoryId); + if (discount.AppliedToSubCategories) + { + //include subcategories + foreach (var childCategoryId in _categoryService + .GetAllCategoriesByParentCategoryId(categoryId, false, true) + .Select(x => x.Id)) + { + if (!ids.Contains(childCategoryId)) + ids.Add(childCategoryId); + } + } + } + return ids; + }); + + return result; + } + + /// + /// Get manufacturer identifiers to which a discount is applied + /// + /// Discount + /// Customer + /// Manufacturer identifiers + public virtual IList GetAppliedManufacturerIds(DiscountForCaching discount, Customer customer) + { + if (discount == null) + throw new ArgumentNullException("discount"); + + var discountId = discount.Id; + var cacheKey = string.Format(DiscountEventConsumer.DISCOUNT_MANUFACTURER_IDS_MODEL_KEY, + discountId, + string.Join(",", customer.GetCustomerRoleIds()), + _storeContext.CurrentStore.Id); + var result = _cacheManager.Get(cacheKey, () => + { + return _discountRepository.Table.Where(x => x.Id == discountId) + .SelectMany(x => x.AppliedToManufacturers.Select(c => c.Id)) + .ToList(); + }); + + return result; + } + + #endregion + + #region Discount requirements + + /// + /// Get all discount requirements + /// + /// Discont identifier + /// Whether to load top-level requirements only (without parent identifier) + /// Requirements + public virtual IList GetAllDiscountRequirements(int discountId = 0, bool topLevelOnly = false) + { + var query = _discountRequirementRepository.Table; + + //filter by discount + if (discountId > 0) + query = query.Where(requirement => requirement.DiscountId == discountId); + + //filter by top-level + if (topLevelOnly) + query = query.Where(requirement => !requirement.ParentId.HasValue); + + query = query.OrderBy(requirement => requirement.Id); + + return query.ToList(); + } + + /// + /// Delete discount requirement + /// + /// Discount requirement + public virtual void DeleteDiscountRequirement(DiscountRequirement discountRequirement) + { + if (discountRequirement == null) + throw new ArgumentNullException("discountRequirement"); + + _discountRequirementRepository.Delete(discountRequirement); + + //event notification + _eventPublisher.EntityDeleted(discountRequirement); + } + + /// + /// Load discount requirement rule by system name + /// + /// System name + /// Found discount requirement rule + public virtual IDiscountRequirementRule LoadDiscountRequirementRuleBySystemName(string systemName) + { + var descriptor = _pluginFinder.GetPluginDescriptorBySystemName(systemName); + if (descriptor != null) + return descriptor.Instance(); + + return null; + } + + /// + /// Load all discount requirement rules + /// + /// Load records allowed only to a specified customer; pass null to ignore ACL permissions + /// Discount requirement rules + public virtual IList LoadAllDiscountRequirementRules(Customer customer = null) + { + return _pluginFinder.GetPlugins(customer: customer).ToList(); + } + + #endregion + + #region Validation + + /// + /// Validate discount + /// + /// Discount + /// Customer + /// Discount validation result + public virtual DiscountValidationResult ValidateDiscount(Discount discount, Customer customer) + { + if (discount == null) + throw new ArgumentNullException("discount"); + + return ValidateDiscount(discount.MapDiscount(), customer); + } + + /// + /// Validate discount + /// + /// Discount + /// Customer + /// Coupon codes to validate + /// Discount validation result + public virtual DiscountValidationResult ValidateDiscount(Discount discount, Customer customer, string[] couponCodesToValidate) + { + if (discount == null) + throw new ArgumentNullException("discount"); + + return ValidateDiscount(discount.MapDiscount(), customer, couponCodesToValidate); + } + + /// + /// Validate discount + /// + /// Discount + /// Customer + /// Discount validation result + public virtual DiscountValidationResult ValidateDiscount(DiscountForCaching discount, Customer customer) + { + if (discount == null) + throw new ArgumentNullException("discount"); + + if (customer == null) + throw new ArgumentNullException("customer"); + + string[] couponCodesToValidate = customer.ParseAppliedDiscountCouponCodes(); + return ValidateDiscount(discount, customer, couponCodesToValidate); + } + + /// + /// Validate discount + /// + /// Discount + /// Customer + /// Coupon codes to validate + /// Discount validation result + public virtual DiscountValidationResult ValidateDiscount(DiscountForCaching discount, Customer customer, string[] couponCodesToValidate) + { + if (discount == null) + throw new ArgumentNullException("discount"); + + if (customer == null) + throw new ArgumentNullException("customer"); + + //invalid by default + var result = new DiscountValidationResult(); + + //check coupon code + if (discount.RequiresCouponCode) + { + if (String.IsNullOrEmpty(discount.CouponCode)) + return result; + + if (couponCodesToValidate == null) + return result; + + if (!couponCodesToValidate.Any(x => x.Equals(discount.CouponCode, StringComparison.InvariantCultureIgnoreCase))) + return result; + } + + //Do not allow discounts applied to order subtotal or total when a customer has gift cards in the cart. + //Otherwise, this customer can purchase gift cards with discount and get more than paid ("free money"). + if (discount.DiscountType == DiscountType.AssignedToOrderSubTotal || + discount.DiscountType == DiscountType.AssignedToOrderTotal) + { + var cart = customer.ShoppingCartItems + .Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) + .LimitPerStore(_storeContext.CurrentStore.Id) + .ToList(); + + var hasGiftCards = cart.Any(x => x.Product.IsGiftCard); + if (hasGiftCards) + { + result.Errors = new List { _localizationService.GetResource("ShoppingCart.Discount.CannotBeUsedWithGiftCards") }; + return result; + } + + var hasRewardPoints = cart.HasRewardPointsProduct(); // cart.Any(x => x.Product.IsRewardPoints); + if (hasRewardPoints) + { + result.Errors = new List { _localizationService.GetResource("ShoppingCart.Discount.CannotBeUsedWithRewardPoints") }; + return result; + } + } + + //check date range + DateTime now = DateTime.UtcNow; + if (discount.StartDateUtc.HasValue) + { + DateTime startDate = DateTime.SpecifyKind(discount.StartDateUtc.Value, DateTimeKind.Utc); + if (startDate.CompareTo(now) > 0) + { + result.Errors = new List { _localizationService.GetResource("ShoppingCart.Discount.NotStartedYet") }; + return result; + } + } + if (discount.EndDateUtc.HasValue) + { + DateTime endDate = DateTime.SpecifyKind(discount.EndDateUtc.Value, DateTimeKind.Utc); + if (endDate.CompareTo(now) < 0) + { + result.Errors = new List { _localizationService.GetResource("ShoppingCart.Discount.Expired") }; + return result; + } + } + + //discount limitation + switch (discount.DiscountLimitation) + { + case DiscountLimitationType.NTimesOnly: + { + var usedTimes = GetAllDiscountUsageHistory(discount.Id, null, null, 0, 1).TotalCount; + if (usedTimes >= discount.LimitationTimes) + return result; + } + break; + case DiscountLimitationType.NTimesPerCustomer: + { + if (customer.IsRegistered()) + { + var usedTimes = GetAllDiscountUsageHistory(discount.Id, customer.Id, null, 0, 1).TotalCount; + if (usedTimes >= discount.LimitationTimes) + { + result.Errors = new List { _localizationService.GetResource("ShoppingCart.Discount.CannotBeUsedAnymore") }; + return result; + } + } + } + break; + case DiscountLimitationType.Unlimited: + default: + break; + } + + //discount requirements + string key = string.Format(DiscountEventConsumer.DISCOUNT_REQUIREMENT_MODEL_KEY, discount.Id); + var requirementsForCaching = _cacheManager.Get(key, () => + { + var requirements = GetAllDiscountRequirements(discount.Id, true); + return GetReqirementsForCaching(requirements); + }); + + //get top-level group + var topLevelGroup = requirementsForCaching.FirstOrDefault(); + if (topLevelGroup == null || (topLevelGroup.IsGroup && !topLevelGroup.ChildRequirements.Any()) || !topLevelGroup.InteractionType.HasValue) + { + //there are no requirements, so discount is valid + result.IsValid = true; + return result; + } + + //requirements are exist, let's check them + var errors = new List(); + result.IsValid = GetValidationResult(requirementsForCaching, topLevelGroup.InteractionType.Value, customer, errors); + + //set errors if result is not valid + if (!result.IsValid) + result.Errors = errors; + + return result; + } + + #endregion + + #region Discount usage history + + /// + /// Gets a discount usage history record + /// + /// Discount usage history record identifier + /// Discount usage history + public virtual DiscountUsageHistory GetDiscountUsageHistoryById(int discountUsageHistoryId) + { + if (discountUsageHistoryId == 0) + return null; + + return _discountUsageHistoryRepository.GetById(discountUsageHistoryId); + } + + /// + /// Gets all discount usage history records + /// + /// Discount identifier; null to load all records + /// Customer identifier; null to load all records + /// Order identifier; null to load all records + /// Page index + /// Page size + /// Discount usage history records + public virtual IPagedList GetAllDiscountUsageHistory(int? discountId = null, + int? customerId = null, int? orderId = null, + int pageIndex = 0, int pageSize = int.MaxValue) + { + var query = _discountUsageHistoryRepository.Table; + if (discountId.HasValue && discountId.Value > 0) + query = query.Where(duh => duh.DiscountId == discountId.Value); + if (customerId.HasValue && customerId.Value > 0) + query = query.Where(duh => duh.Order != null && duh.Order.CustomerId == customerId.Value); + if (orderId.HasValue && orderId.Value > 0) + query = query.Where(duh => duh.OrderId == orderId.Value); + query = query.OrderByDescending(c => c.CreatedOnUtc); + return new PagedList(query, pageIndex, pageSize); + } + + /// + /// Insert discount usage history record + /// + /// Discount usage history record + public virtual void InsertDiscountUsageHistory(DiscountUsageHistory discountUsageHistory) + { + if (discountUsageHistory == null) + throw new ArgumentNullException("discountUsageHistory"); + + _discountUsageHistoryRepository.Insert(discountUsageHistory); + + //event notification + _eventPublisher.EntityInserted(discountUsageHistory); + } + + /// + /// Update discount usage history record + /// + /// Discount usage history record + public virtual void UpdateDiscountUsageHistory(DiscountUsageHistory discountUsageHistory) + { + if (discountUsageHistory == null) + throw new ArgumentNullException("discountUsageHistory"); + + _discountUsageHistoryRepository.Update(discountUsageHistory); + + //event notification + _eventPublisher.EntityUpdated(discountUsageHistory); + } + + /// + /// Delete discount usage history record + /// + /// Discount usage history record + public virtual void DeleteDiscountUsageHistory(DiscountUsageHistory discountUsageHistory) + { + if (discountUsageHistory == null) + throw new ArgumentNullException("discountUsageHistory"); + + _discountUsageHistoryRepository.Delete(discountUsageHistory); + + //event notification + _eventPublisher.EntityDeleted(discountUsageHistory); + } + + #endregion + + #endregion + } +} diff --git a/src/Libraries/Nop.Services/ExportImport/ExportManager.cs b/src/Libraries/Nop.Services/ExportImport/ExportManager.cs index 0196b4f1c88..b740deb63b1 100644 --- a/src/Libraries/Nop.Services/ExportImport/ExportManager.cs +++ b/src/Libraries/Nop.Services/ExportImport/ExportManager.cs @@ -436,15 +436,17 @@ private byte[] ExportOrderToXlsxWithProducts(PropertyByName[] properties, { var orderItemProperties = new[] { - new PropertyByName("Name", oi => oi.Product.Name), new PropertyByName("Sku", oi => oi.Product.Sku), + new PropertyByName("Name", oi => oi.Product.Name), + new PropertyByName("AttributeDescription", oi => oi.AttributeDescription), new PropertyByName("PriceExclTax", oi => oi.UnitPriceExclTax), new PropertyByName("PriceInclTax", oi => oi.UnitPriceInclTax), new PropertyByName("Quantity", oi => oi.Quantity), new PropertyByName("DiscountExclTax", oi => oi.DiscountAmountExclTax), new PropertyByName("DiscountInclTax", oi => oi.DiscountAmountInclTax), new PropertyByName("TotalExclTax", oi => oi.PriceExclTax), - new PropertyByName("TotalInclTax", oi => oi.PriceInclTax) + new PropertyByName("TotalInclTax", oi => oi.PriceInclTax), + new PropertyByName("TaxRate", oi => oi.TaxRate) }; var orderItemsManager = new PropertyManager(orderItemProperties); @@ -473,23 +475,23 @@ private byte[] ExportOrderToXlsxWithProducts(PropertyByName[] properties, manager.WriteToXlsx(worksheet, row++, _catalogSettings.ExportImportUseDropdownlistsForAssociatedEntities); //products - var orederItems = order.OrderItems.ToList(); + var orderItems = order.OrderItems.ToList(); //a vendor should have access only to his products if (_workContext.CurrentVendor != null) - orederItems = orederItems.Where(p => p.Product.VendorId == _workContext.CurrentVendor.Id).ToList(); + orderItems = orderItems.Where(p => p.Product.VendorId == _workContext.CurrentVendor.Id).ToList(); - if (!orederItems.Any()) + if (!orderItems.Any()) continue; orderItemsManager.WriteCaption(worksheet, SetCaptionStyle, row, 2); worksheet.Row(row).OutlineLevel = 1; worksheet.Row(row).Collapsed = true; - foreach (var orederItem in orederItems) + foreach (var orderItem in orderItems) { row++; - orderItemsManager.CurrentObject = orederItem; + orderItemsManager.CurrentObject = orderItem; orderItemsManager.WriteToXlsx(worksheet, row, _catalogSettings.ExportImportUseDropdownlistsForAssociatedEntities, 2, fpWorksheet); worksheet.Row(row).OutlineLevel = 1; worksheet.Row(row).Collapsed = true; @@ -701,6 +703,9 @@ public virtual string ExportProductsToXml(IList products) xmlWriter.WriteString("IsGiftCard", product.IsGiftCard, IgnoreExportPoductProperty(p => p.IsGiftCard)); xmlWriter.WriteString("GiftCardType", product.GiftCardType, IgnoreExportPoductProperty(p => p.IsGiftCard)); xmlWriter.WriteString("OverriddenGiftCardAmount", product.OverriddenGiftCardAmount, IgnoreExportPoductProperty(p => p.IsGiftCard)); + xmlWriter.WriteString("IsRewardPoints", product.IsRewardPoints, IgnoreExportPoductProperty(p => p.IsRewardPoints)); + xmlWriter.WriteString("OverriddenRPExchangeRate", product.OverriddenRPExchangeRate, IgnoreExportPoductProperty(p => p.IsRewardPoints)); + xmlWriter.WriteString("ExcludeFromRewardPoints", product.ExcludeFromRewardPoints, IgnoreExportPoductProperty(p => p.IsRewardPoints)); xmlWriter.WriteString("RequireOtherProducts", product.RequireOtherProducts, IgnoreExportPoductProperty(p => p.RequireOtherProductsAddedToTheCart)); xmlWriter.WriteString("RequiredProductIds", product.RequiredProductIds, IgnoreExportPoductProperty(p => p.RequireOtherProductsAddedToTheCart)); xmlWriter.WriteString("AutomaticallyAddRequiredProducts", product.AutomaticallyAddRequiredProducts, IgnoreExportPoductProperty(p => p.RequireOtherProductsAddedToTheCart)); @@ -1009,6 +1014,9 @@ public virtual byte[] ExportProductsToXlsx(IEnumerable products) DropDownElements = GiftCardType.Virtual.ToSelectList(useLocalization: false) }, new PropertyByName("OverriddenGiftCardAmount", p => p.OverriddenGiftCardAmount, IgnoreExportPoductProperty(p => p.IsGiftCard)), + new PropertyByName("IsRewardPoints", p => p.IsRewardPoints, IgnoreExportPoductProperty(p => p.IsRewardPoints)), + new PropertyByName("OverriddenRPExchangeRate", p => p.OverriddenRPExchangeRate, IgnoreExportPoductProperty(p => p.IsRewardPoints)), + new PropertyByName("ExcludeFromRewardPoints", p => p.ExcludeFromRewardPoints, IgnoreExportPoductProperty(p => p.IsRewardPoints)), new PropertyByName("RequireOtherProducts", p => p.RequireOtherProducts, IgnoreExportPoductProperty(p => p.RequireOtherProductsAddedToTheCart)), new PropertyByName("RequiredProductIds", p => p.RequiredProductIds, IgnoreExportPoductProperty(p => p.RequireOtherProductsAddedToTheCart)), new PropertyByName("AutomaticallyAddRequiredProducts", p => p.AutomaticallyAddRequiredProducts, IgnoreExportPoductProperty(p => p.RequireOtherProductsAddedToTheCart)), @@ -1173,16 +1181,22 @@ public virtual string ExportOrdersToXml(IList orders) xmlWriter.WriteString("OrderSubTotalDiscountExclTax", order.OrderSubTotalDiscountExclTax, ignore); xmlWriter.WriteString("OrderShippingInclTax", order.OrderShippingInclTax, ignore); xmlWriter.WriteString("OrderShippingExclTax", order.OrderShippingExclTax, ignore); + xmlWriter.WriteString("OrderShippingNonTaxable", order.OrderShippingNonTaxable, ignore); xmlWriter.WriteString("PaymentMethodAdditionalFeeInclTax", order.PaymentMethodAdditionalFeeInclTax, ignore); xmlWriter.WriteString("PaymentMethodAdditionalFeeExclTax", order.PaymentMethodAdditionalFeeExclTax, ignore); + xmlWriter.WriteString("PaymentMethodAdditionalFeeNonTaxable", order.PaymentMethodAdditionalFeeNonTaxable, ignore); xmlWriter.WriteString("TaxRates", order.TaxRates, ignore); xmlWriter.WriteString("OrderTax", order.OrderTax, ignore); xmlWriter.WriteString("OrderTotal", order.OrderTotal, ignore); xmlWriter.WriteString("OrderAmount", order.OrderAmount, ignore); - xmlWriter.WriteString("OrderAmountIncl", order.OrderAmountIncl, ignore); - + xmlWriter.WriteString("OrderAmountIncl", order.OrderAmount, ignore); + xmlWriter.WriteString("OrderDiscountIncl", order.OrderAmount, ignore); + xmlWriter.WriteString("EarnedRewardPointsBaseAmountIncl", order.OrderAmount, ignore); + xmlWriter.WriteString("EarnedRewardPointsBaseAmountExcl", order.OrderAmount, ignore); xmlWriter.WriteString("RefundedAmount", order.RefundedAmount, ignore); xmlWriter.WriteString("OrderDiscount", order.OrderDiscount, ignore); + xmlWriter.WriteString("OrderShippingNonTaxable", order.OrderAmount, ignore); + xmlWriter.WriteString("PaymentMethodAdditionalFeeNonTaxable", order.OrderAmount, ignore); xmlWriter.WriteString("CurrencyRate", order.CurrencyRate); xmlWriter.WriteString("CustomerCurrencyCode", order.CustomerCurrencyCode); xmlWriter.WriteString("AffiliateId", order.AffiliateId, ignore); @@ -1235,7 +1249,7 @@ public virtual string ExportOrdersToXml(IList orders) xmlWriter.WriteString("DiscountInclTax", orderItem.DiscountAmountInclTax); xmlWriter.WriteString("TotalExclTax", orderItem.PriceExclTax); xmlWriter.WriteString("TotalInclTax", orderItem.PriceInclTax); - xmlWriter.WriteString("VatRate", orderItem.VatRate); + xmlWriter.WriteString("TaxRate", orderItem.TaxRate.ToString()); xmlWriter.WriteEndElement(); } xmlWriter.WriteEndElement(); @@ -1297,15 +1311,22 @@ public virtual byte[] ExportOrdersToXlsx(IList orders) new PropertyByName("OrderSubTotalDiscountExclTax", p => p.OrderSubTotalDiscountExclTax, ignore), new PropertyByName("OrderShippingInclTax", p => p.OrderShippingInclTax, ignore), new PropertyByName("OrderShippingExclTax", p => p.OrderShippingExclTax, ignore), + new PropertyByName("OrderShippingNonTaxable", p => p.OrderShippingNonTaxable, ignore), new PropertyByName("PaymentMethodAdditionalFeeInclTax", p => p.PaymentMethodAdditionalFeeInclTax, ignore), new PropertyByName("PaymentMethodAdditionalFeeExclTax", p => p.PaymentMethodAdditionalFeeExclTax, ignore), + new PropertyByName("PaymentMethodAdditionalFeeNonTaxable", p => p.PaymentMethodAdditionalFeeNonTaxable, ignore), new PropertyByName("TaxRates", p => p.TaxRates, ignore), new PropertyByName("OrderTax", p => p.OrderTax, ignore), new PropertyByName("OrderTotal", p => p.OrderTotal, ignore), new PropertyByName("OrderAmount", p => p.OrderAmount, ignore), new PropertyByName("OrderAmountIncl", p => p.OrderAmountIncl, ignore), + new PropertyByName("OrderDiscountIncl", p => p.OrderAmountIncl, ignore), + new PropertyByName("EarnedRewardPointsBaseAmountIncl", p => p.OrderAmountIncl, ignore), + new PropertyByName("EarnedRewardPointsBaseAmountExcl", p => p.OrderAmountIncl, ignore), new PropertyByName("RefundedAmount", p => p.RefundedAmount, ignore), new PropertyByName("OrderDiscount", p => p.OrderDiscount, ignore), + new PropertyByName("OrderShippingNonTaxable", p => p.OrderShippingNonTaxable, ignore), + new PropertyByName("PaymentMethodAdditionalFeeNonTaxable", p => p.PaymentMethodAdditionalFeeNonTaxable, ignore), new PropertyByName("CurrencyRate", p => p.CurrencyRate), new PropertyByName("CustomerCurrencyCode", p => p.CustomerCurrencyCode), new PropertyByName("AffiliateId", p => p.AffiliateId, ignore), diff --git a/src/Libraries/Nop.Services/ExportImport/ImportManager.cs b/src/Libraries/Nop.Services/ExportImport/ImportManager.cs index e195a1c9997..021ae74a1bb 100644 --- a/src/Libraries/Nop.Services/ExportImport/ImportManager.cs +++ b/src/Libraries/Nop.Services/ExportImport/ImportManager.cs @@ -330,7 +330,7 @@ public virtual void ImportProductsFromXlsx(Stream stream) //the columns var properties = GetPropertiesByExcelCells(worksheet); - + var manager = new PropertyManager(properties); var attributProperties = new[] @@ -343,7 +343,7 @@ public virtual void ImportProductsFromXlsx(Stream stream) { DropDownElements = AttributeControlType.TextBox.ToSelectList(useLocalization: false) }, - new PropertyByName("AttributeDisplayOrder"), + new PropertyByName("AttributeDisplayOrder"), new PropertyByName("ProductAttributeValueId"), new PropertyByName("ValueName"), new PropertyByName("AttributeValueType") @@ -371,7 +371,7 @@ public virtual void ImportProductsFromXlsx(Stream stream) var tempProperty = manager.GetProperty("Categories"); var categoryCellNum = tempProperty.Return(p => p.PropertyOrderPosition, -1); - + tempProperty = manager.GetProperty("SKU"); var skuCellNum = tempProperty.Return(p => p.PropertyOrderPosition, -1); @@ -444,7 +444,7 @@ public virtual void ImportProductsFromXlsx(Stream stream) } if (categoryCellNum > 0) - { + { var categoryIds = worksheet.Cells[endRow, categoryCellNum].Value.Return(p => p.ToString(), string.Empty); if (!categoryIds.IsEmpty()) @@ -460,7 +460,7 @@ public virtual void ImportProductsFromXlsx(Stream stream) } if (manufacturerCellNum > 0) - { + { var manufacturerIds = worksheet.Cells[endRow, manufacturerCellNum].Value.Return(p => p.ToString(), string.Empty); if (!manufacturerIds.IsEmpty()) allManufacturersNames.AddRange(manufacturerIds.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim())); @@ -536,7 +536,7 @@ public virtual void ImportProductsFromXlsx(Stream stream) var productAttributeId = managerProductAttribute.GetProperty("AttributeId").IntValue; var attributeControlTypeId = managerProductAttribute.GetProperty("AttributeControlType").IntValue; - + var productAttributeValueId = managerProductAttribute.GetProperty("ProductAttributeValueId").IntValue; var associatedProductId = managerProductAttribute.GetProperty("AssociatedProductId").IntValue; var valueName = managerProductAttribute.GetProperty("ValueName").StringValue; @@ -556,7 +556,7 @@ public virtual void ImportProductsFromXlsx(Stream stream) var attributeDisplayOrder = managerProductAttribute.GetProperty("AttributeDisplayOrder").IntValue; var productAttributeMapping = lastLoadedProduct.ProductAttributeMappings.FirstOrDefault(pam => pam.ProductAttributeId == productAttributeId); - + if (productAttributeMapping == null) { //insert mapping @@ -720,6 +720,15 @@ public virtual void ImportProductsFromXlsx(Stream stream) case "OverriddenGiftCardAmount": product.OverriddenGiftCardAmount = property.DecimalValue; break; + case "IsRewardPoints": + product.IsRewardPoints = property.BooleanValue; + break; + case "OverriddenRPExchangeRate": + product.OverriddenRPExchangeRate = property.DecimalValue; + break; + case "ExcludeFromRewardPoints": + product.ExcludeFromRewardPoints = property.BooleanValue; + break; case "RequireOtherProducts": product.RequireOtherProducts = property.BooleanValue; break; @@ -935,7 +944,7 @@ public virtual void ImportProductsFromXlsx(Stream stream) //sets the current vendor for the new product if (isNew && _workContext.CurrentVendor != null) product.VendorId = _workContext.CurrentVendor.Id; - + product.UpdatedOnUtc = DateTime.UtcNow; if (isNew) @@ -953,7 +962,7 @@ public virtual void ImportProductsFromXlsx(Stream stream) _productService.AddStockQuantityHistoryEntry(product, product.StockQuantity - previousStockQuantity, product.StockQuantity, product.WarehouseId, _localizationService.GetResource("Admin.StockQuantityHistory.Messages.ImportProduct.Edit")); } - //warehouse is changed + //warehouse is changed else { //compose a message @@ -989,7 +998,7 @@ public virtual void ImportProductsFromXlsx(Stream stream) tempProperty = manager.GetProperty("Categories"); if (tempProperty != null) - { + { var categoryNames = tempProperty.StringValue; //category mappings @@ -999,7 +1008,7 @@ public virtual void ImportProductsFromXlsx(Stream stream) { if (categories.Any(c => c == categoryId)) continue; - + var productCategory = new ProductCategory { ProductId = product.Id, @@ -1079,7 +1088,7 @@ public virtual void ImportProductsFromXlsx(Stream stream) //_productService.UpdateHasTierPricesProperty(product); //_productService.UpdateHasDiscountsApplied(product); } - + if (_mediaSettings.ImportProductImagesUsingHash && _pictureService.StoreInDb && _dataProvider.SupportedLengthOfBinaryHash() > 0) ImportProductImagesUsingHash(productPictureMetadata, allProductsBySku); else @@ -1089,7 +1098,7 @@ public virtual void ImportProductsFromXlsx(Stream stream) _customerActivityService.InsertActivity("ImportProducts", _localizationService.GetResource("ActivityLog.ImportProducts"), countProductsInFile); } } - + /// /// Import newsletter subscribers from TXT file /// diff --git a/src/Libraries/Nop.Services/Installation/CodeFirstInstallationService.cs b/src/Libraries/Nop.Services/Installation/CodeFirstInstallationService.cs index 986420f155f..00d8e26c00b 100644 --- a/src/Libraries/Nop.Services/Installation/CodeFirstInstallationService.cs +++ b/src/Libraries/Nop.Services/Installation/CodeFirstInstallationService.cs @@ -4092,7 +4092,7 @@ protected virtual void InstallCustomersAndUsers(string defaultUserEmail, string }; _customerRoleRepository.Insert(customerRoles); - //default store + //default store var defaultStore = _storeRepository.Table.FirstOrDefault(); if (defaultStore == null) @@ -4445,15 +4445,20 @@ protected virtual void InstallOrders() OrderSubTotalDiscountExclTax = decimal.Zero, OrderShippingInclTax = decimal.Zero, OrderShippingExclTax = decimal.Zero, + OrderShippingNonTaxable = decimal.Zero, PaymentMethodAdditionalFeeInclTax = decimal.Zero, PaymentMethodAdditionalFeeExclTax = decimal.Zero, + PaymentMethodAdditionalFeeNonTaxable = decimal.Zero, TaxRates = "0:0;", OrderTax = decimal.Zero, OrderTotal = 1855M, OrderAmount = 1855M, OrderAmountIncl = 1855M, + OrderDiscountIncl = decimal.Zero, + EarnedRewardPointsBaseAmountIncl = 1855M, + EarnedRewardPointsBaseAmountExcl = 1855M, RefundedAmount = decimal.Zero, - OrderDiscount = decimal.Zero, + OrderDiscount = decimal.Zero, CheckoutAttributeDescription = string.Empty, CheckoutAttributesXml = string.Empty, CustomerCurrencyCode = "USD", @@ -4514,7 +4519,7 @@ protected virtual void InstallOrders() ItemWeight = null, RentalStartDateUtc = null, RentalEndDateUtc = null, - VatRate = 0 + TaxRate = 0 }; _orderItemRepository.Insert(firstOrderItem1); @@ -4540,7 +4545,7 @@ protected virtual void InstallOrders() ItemWeight = null, RentalStartDateUtc = null, RentalEndDateUtc = null, - VatRate = 0 + TaxRate = 0 }; _orderItemRepository.Insert(fierstOrderItem2); @@ -4566,7 +4571,7 @@ protected virtual void InstallOrders() ItemWeight = null, RentalStartDateUtc = null, RentalEndDateUtc = null, - VatRate = 0 + TaxRate = 0 }; _orderItemRepository.Insert(firstOrderItem3); @@ -4617,13 +4622,18 @@ protected virtual void InstallOrders() OrderSubTotalDiscountExclTax = decimal.Zero, OrderShippingInclTax = decimal.Zero, OrderShippingExclTax = decimal.Zero, + OrderShippingNonTaxable = decimal.Zero, PaymentMethodAdditionalFeeInclTax = decimal.Zero, PaymentMethodAdditionalFeeExclTax = decimal.Zero, + PaymentMethodAdditionalFeeNonTaxable = decimal.Zero, TaxRates = "0:0;", OrderTax = decimal.Zero, OrderTotal = 2460M, OrderAmount = 2460M, OrderAmountIncl = 2460M, + OrderDiscountIncl = decimal.Zero, + EarnedRewardPointsBaseAmountIncl = 2460M, + EarnedRewardPointsBaseAmountExcl = 2460M, RefundedAmount = decimal.Zero, OrderDiscount = decimal.Zero, CheckoutAttributeDescription = string.Empty, @@ -4694,7 +4704,7 @@ protected virtual void InstallOrders() ItemWeight = null, RentalStartDateUtc = null, RentalEndDateUtc = null, - VatRate = 0 + TaxRate = 0 }; _orderItemRepository.Insert(secondOrderItem1); @@ -4720,7 +4730,7 @@ protected virtual void InstallOrders() ItemWeight = null, RentalStartDateUtc = null, RentalEndDateUtc = null, - VatRate = 0 + TaxRate = 0 }; _orderItemRepository.Insert(secondOrderItem2); @@ -4740,15 +4750,20 @@ protected virtual void InstallOrders() OrderSubTotalDiscountExclTax = decimal.Zero, OrderShippingInclTax = decimal.Zero, OrderShippingExclTax = decimal.Zero, + OrderShippingNonTaxable = decimal.Zero, PaymentMethodAdditionalFeeInclTax = decimal.Zero, PaymentMethodAdditionalFeeExclTax = decimal.Zero, + PaymentMethodAdditionalFeeNonTaxable = decimal.Zero, TaxRates = "0:0;", OrderTax = decimal.Zero, OrderTotal = 8.80M, OrderAmount = 8.80M, OrderAmountIncl = 8.80M, + OrderDiscountIncl = decimal.Zero, + EarnedRewardPointsBaseAmountIncl = 8.80M, + EarnedRewardPointsBaseAmountExcl = 8.80M, RefundedAmount = decimal.Zero, - OrderDiscount = decimal.Zero, + OrderDiscount = decimal.Zero, CheckoutAttributeDescription = string.Empty, CheckoutAttributesXml = string.Empty, CustomerCurrencyCode = "USD", @@ -4817,7 +4832,7 @@ protected virtual void InstallOrders() ItemWeight = null, RentalStartDateUtc = null, RentalEndDateUtc = null, - VatRate = 0 + TaxRate = 0 }; _orderItemRepository.Insert(thirdOrderItem1); @@ -4843,7 +4858,7 @@ protected virtual void InstallOrders() ItemWeight = null, RentalStartDateUtc = null, RentalEndDateUtc = null, - VatRate = 0 + TaxRate = 0 }; _orderItemRepository.Insert(thirdOrderItem2); @@ -4869,7 +4884,7 @@ protected virtual void InstallOrders() ItemWeight = null, RentalStartDateUtc = null, RentalEndDateUtc = null, - VatRate = 0 + TaxRate = 0 }; _orderItemRepository.Insert(thirdOrderItem3); @@ -4889,13 +4904,18 @@ protected virtual void InstallOrders() OrderSubTotalDiscountExclTax = decimal.Zero, OrderShippingInclTax = decimal.Zero, OrderShippingExclTax = decimal.Zero, + OrderShippingNonTaxable = decimal.Zero, PaymentMethodAdditionalFeeInclTax = decimal.Zero, PaymentMethodAdditionalFeeExclTax = decimal.Zero, + PaymentMethodAdditionalFeeNonTaxable = decimal.Zero, TaxRates = "0:0;", OrderTax = decimal.Zero, OrderTotal = 102M, OrderAmount = 102M, OrderAmountIncl = 102M, + OrderDiscountIncl = decimal.Zero, + EarnedRewardPointsBaseAmountIncl = 102M, + EarnedRewardPointsBaseAmountExcl = 102M, RefundedAmount = decimal.Zero, OrderDiscount = decimal.Zero, CheckoutAttributeDescription = string.Empty, @@ -4979,7 +4999,7 @@ protected virtual void InstallOrders() ItemWeight = null, RentalStartDateUtc = null, RentalEndDateUtc = null, - VatRate = 0 + TaxRate = 0 }; _orderItemRepository.Insert(fourthOrderItem1); @@ -5005,7 +5025,7 @@ protected virtual void InstallOrders() ItemWeight = null, RentalStartDateUtc = null, RentalEndDateUtc = null, - VatRate = 0 + TaxRate = 0 }; _orderItemRepository.Insert(fourthOrderItem2); @@ -5031,7 +5051,7 @@ protected virtual void InstallOrders() ItemWeight = null, RentalStartDateUtc = null, RentalEndDateUtc = null, - VatRate = 0 + TaxRate = 0 }; _orderItemRepository.Insert(fourthOrderItem3); @@ -5107,13 +5127,18 @@ protected virtual void InstallOrders() OrderSubTotalDiscountExclTax = decimal.Zero, OrderShippingInclTax = decimal.Zero, OrderShippingExclTax = decimal.Zero, + OrderShippingNonTaxable = decimal.Zero, PaymentMethodAdditionalFeeInclTax = decimal.Zero, PaymentMethodAdditionalFeeExclTax = decimal.Zero, + PaymentMethodAdditionalFeeNonTaxable = decimal.Zero, TaxRates = "0:0;", OrderTax = decimal.Zero, OrderTotal = 43.50M, OrderAmount = 43.50M, OrderAmountIncl = 43.50M, + OrderDiscountIncl = decimal.Zero, + EarnedRewardPointsBaseAmountIncl = 43.50M, + EarnedRewardPointsBaseAmountExcl = 43.50M, RefundedAmount = decimal.Zero, OrderDiscount = decimal.Zero, CheckoutAttributeDescription = string.Empty, @@ -5202,7 +5227,7 @@ protected virtual void InstallOrders() ItemWeight = null, RentalStartDateUtc = null, RentalEndDateUtc = null, - VatRate = 0 + TaxRate = 0 }; _orderItemRepository.Insert(fifthOrderItem1); @@ -6194,7 +6219,13 @@ protected virtual void InstallSettings(bool installSampleData) ActivationDelayPeriodId = 0, DisplayHowMuchWillBeEarned = true, PointsAccumulatedForAllStores = true, - PageSize = 10 + PageSize = 10, + EarnedRewardPointsAreTaxable = false, + AwardPointsIncludeShipping = true, + AwardPointsIncludePaymentMethodAdditionalFee = true, + AwardPointsExcludeGiftCard = true, + AwardPointsExcludePurchasedRewardPoints = true, + EarnRewardPointsOnlyWhenUsingPurchasedRewardPoints = false }); settingService.SaveSetting(new CurrencySettings @@ -6242,7 +6273,8 @@ protected virtual void InstallSettings(bool installSampleData) RoundPricesDuringCalculation = true, GroupTierPricesForDistinctShoppingCartItems = false, AllowCartItemEditing = true, - RenderAssociatedAttributeValueQuantity = true + RenderAssociatedAttributeValueQuantity = true, + RenderProductAttributePrices = true }); settingService.SaveSetting(new OrderSettings @@ -11198,6 +11230,12 @@ protected virtual void InstallActivityLogTypes() Name = "Add a new customer role" }, new ActivityLogType + { + SystemKeyword = "AddCustomerRewardPoints", + Enabled = true, + Name = "Add customer reward points" + }, + new ActivityLogType { SystemKeyword = "AddNewDiscount", Enabled = true, @@ -11618,6 +11656,12 @@ protected virtual void InstallActivityLogTypes() Name = "Edit a customer role" }, new ActivityLogType + { + SystemKeyword = "EditCustomerRewardPoints", + Enabled = true, + Name = "Edit customer reward points" + }, + new ActivityLogType { SystemKeyword = "EditDiscount", Enabled = true, diff --git a/src/Libraries/Nop.Services/Messages/MessageTokenProvider.cs b/src/Libraries/Nop.Services/Messages/MessageTokenProvider.cs index bb893b4f3a7..e3bd8d63cc9 100644 --- a/src/Libraries/Nop.Services/Messages/MessageTokenProvider.cs +++ b/src/Libraries/Nop.Services/Messages/MessageTokenProvider.cs @@ -1,1373 +1,1374 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Web; -using Nop.Core; -using Nop.Core.Domain; -using Nop.Core.Domain.Blogs; -using Nop.Core.Domain.Catalog; -using Nop.Core.Domain.Customers; -using Nop.Core.Domain.Directory; -using Nop.Core.Domain.Forums; -using Nop.Core.Domain.Messages; -using Nop.Core.Domain.News; -using Nop.Core.Domain.Orders; -using Nop.Core.Domain.Payments; -using Nop.Core.Domain.Shipping; -using Nop.Core.Domain.Stores; -using Nop.Core.Domain.Tax; -using Nop.Core.Domain.Vendors; -using Nop.Core.Html; -using Nop.Core.Infrastructure; -using Nop.Services.Catalog; -using Nop.Services.Common; -using Nop.Services.Customers; -using Nop.Services.Directory; -using Nop.Services.Events; -using Nop.Services.Forums; -using Nop.Services.Helpers; -using Nop.Services.Localization; -using Nop.Services.Media; -using Nop.Services.Orders; -using Nop.Services.Payments; -using Nop.Services.Seo; -using Nop.Services.Shipping; -using Nop.Services.Shipping.Tracking; -using Nop.Services.Stores; - -namespace Nop.Services.Messages -{ - public partial class MessageTokenProvider : IMessageTokenProvider - { - #region Fields - - private readonly ILanguageService _languageService; - private readonly ILocalizationService _localizationService; - private readonly IDateTimeHelper _dateTimeHelper; - private readonly IPriceFormatter _priceFormatter; - private readonly ICurrencyService _currencyService; - private readonly IWorkContext _workContext; - private readonly IDownloadService _downloadService; - private readonly IOrderService _orderService; - private readonly IPaymentService _paymentService; - private readonly IProductAttributeParser _productAttributeParser; - private readonly IAddressAttributeFormatter _addressAttributeFormatter; - private readonly ICustomerAttributeFormatter _customerAttributeFormatter; - private readonly IStoreService _storeService; - private readonly IStoreContext _storeContext; - - private readonly MessageTemplatesSettings _templatesSettings; - private readonly CatalogSettings _catalogSettings; - private readonly TaxSettings _taxSettings; - private readonly CurrencySettings _currencySettings; - private readonly ShippingSettings _shippingSettings; - private readonly PaymentSettings _paymentSettings; - - private readonly IEventPublisher _eventPublisher; - private readonly StoreInformationSettings _storeInformationSettings; - - #endregion - - #region Ctor - - public MessageTokenProvider(ILanguageService languageService, - ILocalizationService localizationService, - IDateTimeHelper dateTimeHelper, - IPriceFormatter priceFormatter, - ICurrencyService currencyService, - IWorkContext workContext, - IDownloadService downloadService, - IOrderService orderService, - IPaymentService paymentService, - IStoreService storeService, - IStoreContext storeContext, - IProductAttributeParser productAttributeParser, - IAddressAttributeFormatter addressAttributeFormatter, - ICustomerAttributeFormatter customerAttributeFormatter, - MessageTemplatesSettings templatesSettings, - CatalogSettings catalogSettings, - TaxSettings taxSettings, - CurrencySettings currencySettings, - ShippingSettings shippingSettings, - PaymentSettings paymentSettings, - IEventPublisher eventPublisher, - StoreInformationSettings storeInformationSettings) - { - this._languageService = languageService; - this._localizationService = localizationService; - this._dateTimeHelper = dateTimeHelper; - this._priceFormatter = priceFormatter; - this._currencyService = currencyService; - this._workContext = workContext; - this._downloadService = downloadService; - this._orderService = orderService; - this._paymentService = paymentService; - this._productAttributeParser = productAttributeParser; - this._addressAttributeFormatter = addressAttributeFormatter; - this._customerAttributeFormatter = customerAttributeFormatter; - this._storeService = storeService; - this._storeContext = storeContext; - - this._templatesSettings = templatesSettings; - this._catalogSettings = catalogSettings; - this._taxSettings = taxSettings; - this._currencySettings = currencySettings; - this._shippingSettings = shippingSettings; - this._paymentSettings = paymentSettings; - this._eventPublisher = eventPublisher; - this._storeInformationSettings = storeInformationSettings; - } - - #endregion - - #region Allowed tokens - - private Dictionary> _allowedTokens; - /// - /// Get all available tokens by token groups - /// - protected Dictionary> AllowedTokens - { - get - { - if (_allowedTokens != null) - return _allowedTokens; - - _allowedTokens = new Dictionary>(); - - //store tokens - _allowedTokens.Add(TokenGroupNames.StoreTokens, new[] - { - "%Store.Name%", - "%Store.URL%", - "%Store.Email%", - "%Store.CompanyName%", - "%Store.CompanyAddress%", - "%Store.CompanyPhoneNumber%", - "%Store.CompanyVat%", - "%Facebook.URL%", - "%Twitter.URL%", - "%YouTube.URL%", - "%GooglePlus.URL%" - }); - - //customer tokens - _allowedTokens.Add(TokenGroupNames.CustomerTokens, new[] - { - "%Customer.Email%", - "%Customer.Username%", - "%Customer.FullName%", - "%Customer.FirstName%", - "%Customer.LastName%", - "%Customer.VatNumber%", - "%Customer.VatNumberStatus%", - "%Customer.CustomAttributes%", - "%Customer.PasswordRecoveryURL%", - "%Customer.AccountActivationURL%", - "%Customer.EmailRevalidationURL%", - "%Wishlist.URLForCustomer%" - }); - - //order tokens - _allowedTokens.Add(TokenGroupNames.OrderTokens, new[] - { - "%Order.OrderNumber%", - "%Order.CustomerFullName%", - "%Order.CustomerEmail%", - "%Order.BillingFirstName%", - "%Order.BillingLastName%", - "%Order.BillingPhoneNumber%", - "%Order.BillingEmail%", - "%Order.BillingFaxNumber%", - "%Order.BillingCompany%", - "%Order.BillingAddress1%", - "%Order.BillingAddress2%", - "%Order.BillingCity%", - "%Order.BillingStateProvince%", - "%Order.BillingZipPostalCode%", - "%Order.BillingCountry%", - "%Order.BillingCustomAttributes%", - "%Order.Shippable%", - "%Order.ShippingMethod%", - "%Order.ShippingFirstName%", - "%Order.ShippingLastName%", - "%Order.ShippingPhoneNumber%", - "%Order.ShippingEmail%", - "%Order.ShippingFaxNumber%", - "%Order.ShippingCompany%", - "%Order.ShippingAddress1%", - "%Order.ShippingAddress2%", - "%Order.ShippingCity%", - "%Order.ShippingStateProvince%", - "%Order.ShippingZipPostalCode%", - "%Order.ShippingCountry%", - "%Order.ShippingCustomAttributes%", - "%Order.PaymentMethod%", - "%Order.VatNumber%", - "%Order.CustomValues%", - "%Order.Product(s)%", - "%Order.CreatedOn%", - "%Order.OrderURLForCustomer%" - }); - - //shipment tokens - _allowedTokens.Add(TokenGroupNames.ShipmentTokens, new[] - { - "%Shipment.ShipmentNumber%", - "%Shipment.TrackingNumber%", - "%Shipment.TrackingNumberURL%", - "%Shipment.Product(s)%", - "%Shipment.URLForCustomer%" - }); - - //refunded order tokens - _allowedTokens.Add(TokenGroupNames.RefundedOrderTokens, new[] - { - "%Order.AmountRefunded%" - }); - - //order note tokens - _allowedTokens.Add(TokenGroupNames.OrderNoteTokens, new[] - { - "%Order.NewNoteText%", - "%Order.OrderNoteAttachmentUrl%" - }); - - //recurring payment tokens - _allowedTokens.Add(TokenGroupNames.RecurringPaymentTokens, new[] - { - "%RecurringPayment.ID%", - "%RecurringPayment.CancelAfterFailedPayment%", - "%RecurringPayment.RecurringPaymentType%" - }); - - //newsletter subscription tokens - _allowedTokens.Add(TokenGroupNames.SubscriptionTokens, new[] - { - "%NewsLetterSubscription.Email%", - "%NewsLetterSubscription.ActivationUrl%", - "%NewsLetterSubscription.DeactivationUrl%" - }); - - //product tokens - _allowedTokens.Add(TokenGroupNames.ProductTokens, new[] - { - "%Product.ID%", - "%Product.Name%", - "%Product.ShortDescription%", - "%Product.ProductURLForCustomer%", - "%Product.SKU%", - "%Product.StockQuantity%" - }); - - //return request tokens - _allowedTokens.Add(TokenGroupNames.ReturnRequestTokens, new[] - { - "%ReturnRequest.CustomNumber%", - "%ReturnRequest.OrderId%", - "%ReturnRequest.Product.Quantity%", - "%ReturnRequest.Product.Name%", - "%ReturnRequest.Reason%", - "%ReturnRequest.RequestedAction%", - "%ReturnRequest.CustomerComment%", - "%ReturnRequest.StaffNotes%", - "%ReturnRequest.Status%" - }); - - //forum tokens - _allowedTokens.Add(TokenGroupNames.ForumTokens, new[] - { - "%Forums.ForumURL%", - "%Forums.ForumName%" - }); - - //forum topic tokens - _allowedTokens.Add(TokenGroupNames.ForumTopicTokens, new[] - { - "%Forums.TopicURL%", - "%Forums.TopicName%" - }); - - //forum post tokens - _allowedTokens.Add(TokenGroupNames.ForumPostTokens, new[] - { - "%Forums.PostAuthor%", - "%Forums.PostBody%" - }); - - //private message tokens - _allowedTokens.Add(TokenGroupNames.PrivateMessageTokens, new[] - { - "%PrivateMessage.Subject%", - "%PrivateMessage.Text%" - }); - - //vendor tokens - _allowedTokens.Add(TokenGroupNames.VendorTokens, new[] - { - "%Vendor.Name%", - "%Vendor.Email%" - }); - - //gift card tokens - _allowedTokens.Add(TokenGroupNames.GiftCardTokens, new[] - { - "%GiftCard.SenderName%", - "%GiftCard.SenderEmail%", - "%GiftCard.RecipientName%", - "%GiftCard.RecipientEmail%", - "%GiftCard.Amount%", - "%GiftCard.CouponCode%", - "%GiftCard.Message%" - }); - - //product review tokens - _allowedTokens.Add(TokenGroupNames.ProductReviewTokens, new[] - { - "%ProductReview.ProductName%" - }); - - //attribute combination tokens - _allowedTokens.Add(TokenGroupNames.AttributeCombinationTokens, new[] - { - "%AttributeCombination.Formatted%", - "%AttributeCombination.SKU%", - "%AttributeCombination.StockQuantity%" - }); - - //blog comment tokens - _allowedTokens.Add(TokenGroupNames.BlogCommentTokens, new[] - { - "%BlogComment.BlogPostTitle%" - }); - - //news comment tokens - _allowedTokens.Add(TokenGroupNames.NewsCommentTokens, new[] - { - "%NewsComment.NewsTitle%" - }); - - //product back in stock tokens - _allowedTokens.Add(TokenGroupNames.ProductBackInStockTokens, new[] - { - "%BackInStockSubscription.ProductName%", - "%BackInStockSubscription.ProductUrl%" - }); - - //email a friend tokens - _allowedTokens.Add(TokenGroupNames.EmailAFriendTokens, new[] - { - "%EmailAFriend.PersonalMessage%", - "%EmailAFriend.Email%" - }); - - //wishlist to friend tokens - _allowedTokens.Add(TokenGroupNames.WishlistToFriendTokens, new[] - { - "%Wishlist.PersonalMessage%", - "%Wishlist.Email%" - }); - - //VAT validation tokens - _allowedTokens.Add(TokenGroupNames.VatValidation, new[] - { - "%VatValidationResult.Name%", - "%VatValidationResult.Address%" - }); - - //contact us tokens - _allowedTokens.Add(TokenGroupNames.ContactUs, new[] - { - "%ContactUs.SenderEmail%", - "%ContactUs.SenderName%", - "%ContactUs.Body%" - }); - - //contact vendor tokens - _allowedTokens.Add(TokenGroupNames.ContactVendor, new[] - { - "%ContactUs.SenderEmail%", - "%ContactUs.SenderName%", - "%ContactUs.Body%" - }); - - return _allowedTokens; - } - } - - #endregion - - #region Utilities - - /// - /// Convert a collection to a HTML table - /// - /// Order - /// Language identifier - /// Vendor identifier (used to limit products by vendor - /// HTML table of products - protected virtual string ProductListToHtmlTable(Order order, int languageId, int vendorId) - { - string result; - var includingTax = order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax; - var language = _languageService.GetLanguageById(languageId); - - 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).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(""); - - var table = order.OrderItems.ToList(); - for (int i = 0; i <= table.Count - 1; i++) - { - var orderItem = table[i]; - var product = orderItem.Product; - if (product == null) - continue; - - if (vendorId > 0 && product.VendorId != vendorId) - continue; - - sb.AppendLine(string.Format("", _templatesSettings.Color2)); - //product name - string productName = product.GetLocalized(x => x.Name, languageId); - - sb.AppendLine(""); - - string unitPriceStr; - if (includingTax) - { - //including tax - var unitPriceInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(orderItem.UnitPriceInclTax, order.CurrencyRate); - unitPriceStr = _priceFormatter.FormatPrice(unitPriceInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, true); - } - else - { - //excluding tax - var unitPriceExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(orderItem.UnitPriceExclTax, order.CurrencyRate); - unitPriceStr = _priceFormatter.FormatPrice(unitPriceExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, false); - } - sb.AppendLine(string.Format("", unitPriceStr)); - - sb.AppendLine(string.Format("", orderItem.Quantity)); - - string priceStr; - if (includingTax) - { - //including tax - var priceInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(orderItem.PriceInclTax, order.CurrencyRate); - priceStr = _priceFormatter.FormatPrice(priceInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, true); - } - else - { - //excluding tax - var priceExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(orderItem.PriceExclTax, order.CurrencyRate); - priceStr = _priceFormatter.FormatPrice(priceExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, false); - } - sb.AppendLine(string.Format("", priceStr)); - - sb.AppendLine(""); - } - #endregion - - if (vendorId == 0) - { - //we render checkout attributes and totals only for store owners (hide for vendors) - - #region Checkout Attributes - - if (!String.IsNullOrEmpty(order.CheckoutAttributeDescription)) - { - sb.AppendLine(""); - } - - #endregion - - #region Totals - - //subtotal - string cusSubTotal; - bool displaySubTotalDiscount = false; - string cusSubTotalDiscount = string.Empty; - if (includingTax && !_taxSettings.ForceTaxExclusionFromOrderSubtotal) - { - //including tax - - //subtotal - var orderSubtotalInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubtotalInclTax, order.CurrencyRate); - cusSubTotal = _priceFormatter.FormatPrice(orderSubtotalInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, true); - //discount (applied to order subtotal) - var orderSubTotalDiscountInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubTotalDiscountInclTax, order.CurrencyRate); - if (orderSubTotalDiscountInclTaxInCustomerCurrency > decimal.Zero) - { - cusSubTotalDiscount = _priceFormatter.FormatPrice(-orderSubTotalDiscountInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, true); - displaySubTotalDiscount = true; - } - } - else - { - //exсluding tax - - //subtotal - var orderSubtotalExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubtotalExclTax, order.CurrencyRate); - cusSubTotal = _priceFormatter.FormatPrice(orderSubtotalExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, false); - //discount (applied to order subtotal) - var orderSubTotalDiscountExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubTotalDiscountExclTax, order.CurrencyRate); - if (orderSubTotalDiscountExclTaxInCustomerCurrency > decimal.Zero) - { - cusSubTotalDiscount = _priceFormatter.FormatPrice(-orderSubTotalDiscountExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, false); - displaySubTotalDiscount = true; - } - } - - //shipping, payment method fee - string cusShipTotal; - string cusPaymentMethodAdditionalFee; - var taxRates = new SortedDictionary(); - string cusTaxTotal = string.Empty; - string cusDiscount = string.Empty; - string cusTotal; - string cusAmount; - string cusAmountIncl; - if (includingTax) - { - //including tax - - //shipping - var orderShippingInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderShippingInclTax, order.CurrencyRate); - cusShipTotal = _priceFormatter.FormatShippingPrice(orderShippingInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, true); - //payment method additional fee - var paymentMethodAdditionalFeeInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.PaymentMethodAdditionalFeeInclTax, order.CurrencyRate); - cusPaymentMethodAdditionalFee = _priceFormatter.FormatPaymentMethodAdditionalFee(paymentMethodAdditionalFeeInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, true); - } - else - { - //excluding tax - - //shipping - var orderShippingExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderShippingExclTax, order.CurrencyRate); - cusShipTotal = _priceFormatter.FormatShippingPrice(orderShippingExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, false); - //payment method additional fee - var paymentMethodAdditionalFeeExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.PaymentMethodAdditionalFeeExclTax, order.CurrencyRate); - cusPaymentMethodAdditionalFee = _priceFormatter.FormatPaymentMethodAdditionalFee(paymentMethodAdditionalFeeExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, false); - } - - //shipping - bool displayShipping = order.ShippingStatus != ShippingStatus.ShippingNotRequired; - - //payment method fee - bool displayPaymentMethodFee = order.PaymentMethodAdditionalFeeExclTax > decimal.Zero; - - //tax - bool displayTax = true; - bool displayTaxRates = true; - if (_taxSettings.HideTaxInOrderSummary && includingTax) - { - displayTax = false; - displayTaxRates = false; - } - else - { - if (order.OrderTax == 0 && _taxSettings.HideZeroTax) - { - displayTax = false; - displayTaxRates = false; - } - else - { - taxRates = new SortedDictionary(); - foreach (var tr in order.TaxRatesDictionary) - //taxRates.Add(tr.Key, _currencyService.ConvertCurrency(tr.Value, order.CurrencyRate)); - taxRates.Add(tr.Key, new TaxRateRec() - { - VatRate = tr.Key, - Amount = _currencyService.ConvertCurrency(tr.Value.Amount, order.CurrencyRate), - DiscountAmount = _currencyService.ConvertCurrency(tr.Value.DiscountAmount, order.CurrencyRate), - BaseAmount = _currencyService.ConvertCurrency(tr.Value.BaseAmount, order.CurrencyRate), - VatAmount = _currencyService.ConvertCurrency(tr.Value.VatAmount, order.CurrencyRate), - AmountIncludingVAT = _currencyService.ConvertCurrency(tr.Value.AmountIncludingVAT, order.CurrencyRate) - }); - - displayTaxRates = _taxSettings.DisplayTaxRates && taxRates.Any(); - displayTax = !displayTaxRates; - - var orderTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderTax, order.CurrencyRate); - string taxStr = _priceFormatter.FormatPrice(orderTaxInCustomerCurrency, true, order.CustomerCurrencyCode, false, language); - cusTaxTotal = taxStr; - } - } - - //discount - bool displayDiscount = false; - if (order.OrderDiscount > decimal.Zero) - { - var orderDiscountInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderDiscount, order.CurrencyRate); - cusDiscount = _priceFormatter.FormatPrice(-orderDiscountInCustomerCurrency, true, order.CustomerCurrencyCode, false, language); - displayDiscount = true; - } - - //total - var orderTotalInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderTotal, order.CurrencyRate); - cusTotal = _priceFormatter.FormatPrice(orderTotalInCustomerCurrency, true, order.CustomerCurrencyCode, false, language); - - var orderAmountInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderAmount, order.CurrencyRate); - cusAmount = _priceFormatter.FormatPrice(orderAmountInCustomerCurrency, true, order.CustomerCurrencyCode, false, language); - - var orderAmountInclInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderAmountIncl, order.CurrencyRate); - cusAmountIncl = _priceFormatter.FormatPrice(orderAmountInclInCustomerCurrency, true, order.CustomerCurrencyCode, false, language); - - //subtotal - sb.AppendLine(string.Format("", _templatesSettings.Color3, _localizationService.GetResource("Messages.Order.SubTotal", languageId), cusSubTotal)); - - //discount (applied to order subtotal) - if (displaySubTotalDiscount) - { - sb.AppendLine(string.Format("", _templatesSettings.Color3, _localizationService.GetResource("Messages.Order.SubTotalDiscount", languageId), cusSubTotalDiscount)); - } - - - //shipping - if (displayShipping) - { - sb.AppendLine(string.Format("", _templatesSettings.Color3, _localizationService.GetResource("Messages.Order.Shipping", languageId), cusShipTotal)); - } - - //payment method fee - if (displayPaymentMethodFee) - { - string paymentMethodFeeTitle = _localizationService.GetResource("Messages.Order.PaymentMethodAdditionalFee", languageId); - sb.AppendLine(string.Format("", _templatesSettings.Color3, paymentMethodFeeTitle, cusPaymentMethodAdditionalFee)); - } - - //discount - if (displayDiscount) - { - sb.AppendLine(string.Format("", _templatesSettings.Color3, _localizationService.GetResource("Messages.Order.TotalDiscount", languageId), cusDiscount)); - } - - //amount - if (includingTax) - { - sb.AppendLine(string.Format("", _templatesSettings.Color3, _localizationService.GetResource("Messages.Order.OrderAmountIncl", languageId), cusAmountIncl)); - } - - //tax - if (displayTax) - { - sb.AppendLine(string.Format("", _templatesSettings.Color3, _localizationService.GetResource(includingTax ? "Messages.Order.TaxIncl" : "Messages.Order.Tax", languageId), cusTaxTotal)); - } - - //amount - if (!includingTax) - { - sb.AppendLine(string.Format("", _templatesSettings.Color3, _localizationService.GetResource("Messages.Order.OrderAmount", languageId), cusAmount)); - } - if (displayTaxRates) - { - foreach (var item in taxRates) - { - string taxRate = String.Format(_localizationService.GetResource("Messages.Order.TaxRateLine"), _priceFormatter.FormatTaxRate(item.Key)); - string OrderAmount = _priceFormatter.FormatPrice(item.Value.Amount, true, order.CustomerCurrencyCode, false, language); - string DiscountAmount = _priceFormatter.FormatPrice(item.Value.DiscountAmount, true, order.CustomerCurrencyCode, false, language); - string Amount = _priceFormatter.FormatPrice(item.Value.BaseAmount, true, order.CustomerCurrencyCode, false, language); - string VatAmount = _priceFormatter.FormatPrice(item.Value.VatAmount, true, order.CustomerCurrencyCode, false, language); - string AmountIncludingVAT = _priceFormatter.FormatPrice(item.Value.AmountIncludingVAT, true, order.CustomerCurrencyCode, false, language); - - sb.AppendLine(string.Format("", - _templatesSettings.Color3, taxRate, VatAmount, OrderAmount, DiscountAmount, Amount, AmountIncludingVAT)); - } - } - - //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 giftCardAmount = _priceFormatter.FormatPrice(-(_currencyService.ConvertCurrency(gcuh.UsedValue, order.CurrencyRate)), true, order.CustomerCurrencyCode, false, language); - sb.AppendLine(string.Format("", _templatesSettings.Color3, giftCardText, giftCardAmount)); - } - - //reward points - if (order.RedeemedRewardPointsEntry != null) - { - string rpTitle = string.Format(_localizationService.GetResource("Messages.Order.RewardPoints", languageId), -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)); - } - - //total - sb.AppendLine(string.Format("", _templatesSettings.Color3, _localizationService.GetResource("Messages.Order.OrderTotal", languageId), cusTotal)); - #endregion - - } - - sb.AppendLine("
    {0}{0}{0}{0}
    " + 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}", GetStoreUrl(order.StoreId), orderItem.OrderItemGuid); - string downloadLink = string.Format("{1}", downloadUrl, _localizationService.GetResource("Messages.Order.Product(s).Download", languageId)); - sb.AppendLine("
    "); - sb.AppendLine(downloadLink); - } - //add download link - if (_downloadService.IsLicenseDownloadAllowed(orderItem)) - { - //TODO add a method for getting URL (use routing because it handles all SEO friendly URLs) - string licenseUrl = string.Format("{0}download/getlicense/{1}", GetStoreUrl(order.StoreId), orderItem.OrderItemGuid); - string licenseLink = string.Format("{1}", licenseUrl, _localizationService.GetResource("Messages.Order.Product(s).License", languageId)); - sb.AppendLine("
    "); - sb.AppendLine(licenseLink); - } - //attributes - if (!String.IsNullOrEmpty(orderItem.AttributeDescription)) - { - sb.AppendLine("
    "); - sb.AppendLine(orderItem.AttributeDescription); - } - //rental info - if (orderItem.Product.IsRental) - { - var rentalStartDate = orderItem.RentalStartDateUtc.HasValue ? orderItem.Product.FormatRentalDate(orderItem.RentalStartDateUtc.Value) : string.Empty; - var rentalEndDate = orderItem.RentalEndDateUtc.HasValue ? orderItem.Product.FormatRentalDate(orderItem.RentalEndDateUtc.Value) : string.Empty; - var rentalInfo = string.Format(_localizationService.GetResource("Order.Rental.FormattedDate"), - rentalStartDate, rentalEndDate); - sb.AppendLine("
    "); - sb.AppendLine(rentalInfo); - } - //sku - if (_catalogSettings.ShowSkuOnProductDetailsPage) - { - var sku = product.FormatSku(orderItem.AttributesXml, _productAttributeParser); - if (!String.IsNullOrEmpty(sku)) - { - sb.AppendLine("
    "); - sb.AppendLine(string.Format(_localizationService.GetResource("Messages.Order.Product(s).SKU", languageId), HttpUtility.HtmlEncode(sku))); - } - } - sb.AppendLine("
    {0}{0}{0}
     "); - sb.AppendLine(order.CheckoutAttributeDescription); - sb.AppendLine("
     {1} {2}
     {1} {2}
     {1} {2}
     {1} {2}
     {1} {2}
     {1} {2}
     {1} {2}
     {1} {2}
     {1} {2}
     {1} {2}
     {1} {2}
     {1} {2}
    "); - result = sb.ToString(); - return result; - } - - /// - /// Convert a collection to a HTML table - /// - /// Shipment - /// Language identifier - /// HTML table of products - protected virtual string ProductListToHtmlTable(Shipment shipment, int languageId) - { - string 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(""); - - var table = shipment.ShipmentItems.ToList(); - for (int i = 0; i <= table.Count - 1; i++) - { - var si = table[i]; - var orderItem = _orderService.GetOrderItemById(si.OrderItemId); - if (orderItem == null) - continue; - - var product = orderItem.Product; - if (product == null) - continue; - - sb.AppendLine(string.Format("", _templatesSettings.Color2)); - //product name - string productName = product.GetLocalized(x => x.Name, languageId); - - sb.AppendLine(""); - - sb.AppendLine(string.Format("", si.Quantity)); - - sb.AppendLine(""); - } - #endregion - - sb.AppendLine("
    {0}{0}
    " + HttpUtility.HtmlEncode(productName)); - //attributes - if (!String.IsNullOrEmpty(orderItem.AttributeDescription)) - { - sb.AppendLine("
    "); - sb.AppendLine(orderItem.AttributeDescription); - } - //rental info - if (orderItem.Product.IsRental) - { - var rentalStartDate = orderItem.RentalStartDateUtc.HasValue ? orderItem.Product.FormatRentalDate(orderItem.RentalStartDateUtc.Value) : string.Empty; - var rentalEndDate = orderItem.RentalEndDateUtc.HasValue ? orderItem.Product.FormatRentalDate(orderItem.RentalEndDateUtc.Value) : string.Empty; - var rentalInfo = string.Format(_localizationService.GetResource("Order.Rental.FormattedDate"), - rentalStartDate, rentalEndDate); - sb.AppendLine("
    "); - sb.AppendLine(rentalInfo); - } - //sku - if (_catalogSettings.ShowSkuOnProductDetailsPage) - { - var sku = product.FormatSku(orderItem.AttributesXml, _productAttributeParser); - if (!String.IsNullOrEmpty(sku)) - { - sb.AppendLine("
    "); - sb.AppendLine(string.Format(_localizationService.GetResource("Messages.Order.Product(s).SKU", languageId), HttpUtility.HtmlEncode(sku))); - } - } - sb.AppendLine("
    {0}
    "); - result = sb.ToString(); - return result; - } - - /// - /// Get store URL - /// - /// Store identifier; Pass 0 to load URL of the current store - /// - protected virtual string GetStoreUrl(int storeId = 0) - { - var store = _storeService.GetStoreById(storeId) ?? _storeContext.CurrentStore; - - if (store == null) - throw new Exception("No store could be loaded"); - - return store.Url; - } - - #endregion - - #region Methods - - /// - /// Add store tokens - /// - /// List of already added tokens - /// Store - /// Email account - public virtual void AddStoreTokens(IList tokens, Store store, EmailAccount emailAccount) - { - if (emailAccount == null) - throw new ArgumentNullException("emailAccount"); - - tokens.Add(new Token("Store.Name", store.GetLocalized(x => x.Name))); - tokens.Add(new Token("Store.URL", store.Url, true)); - tokens.Add(new Token("Store.Email", emailAccount.Email)); - tokens.Add(new Token("Store.CompanyName", store.CompanyName)); - tokens.Add(new Token("Store.CompanyAddress", store.CompanyAddress)); - tokens.Add(new Token("Store.CompanyPhoneNumber", store.CompanyPhoneNumber)); - tokens.Add(new Token("Store.CompanyVat", store.CompanyVat)); - - tokens.Add(new Token("Facebook.URL", _storeInformationSettings.FacebookLink)); - tokens.Add(new Token("Twitter.URL", _storeInformationSettings.TwitterLink)); - tokens.Add(new Token("YouTube.URL", _storeInformationSettings.YoutubeLink)); - tokens.Add(new Token("GooglePlus.URL", _storeInformationSettings.GooglePlusLink)); - - //event notification - _eventPublisher.EntityTokensAdded(store, tokens); - } - - /// - /// Add order tokens - /// - /// List of already added tokens - /// - /// Language identifier - /// Vendor identifier - public virtual void AddOrderTokens(IList tokens, Order order, int languageId, int vendorId = 0) - { - tokens.Add(new Token("Order.OrderNumber", order.CustomOrderNumber)); - - 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) : string.Empty)); - 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) : string.Empty)); - tokens.Add(new Token("Order.BillingCustomAttributes", _addressAttributeFormatter.FormatAttributes(order.BillingAddress.CustomAttributes), true)); - - tokens.Add(new Token("Order.Shippable", !string.IsNullOrEmpty(order.ShippingMethod))); - tokens.Add(new Token("Order.ShippingMethod", order.ShippingMethod)); - tokens.Add(new Token("Order.ShippingFirstName", order.ShippingAddress != null ? order.ShippingAddress.FirstName : string.Empty)); - tokens.Add(new Token("Order.ShippingLastName", order.ShippingAddress != null ? order.ShippingAddress.LastName : string.Empty)); - tokens.Add(new Token("Order.ShippingPhoneNumber", order.ShippingAddress != null ? order.ShippingAddress.PhoneNumber : string.Empty)); - tokens.Add(new Token("Order.ShippingEmail", order.ShippingAddress != null ? order.ShippingAddress.Email : string.Empty)); - tokens.Add(new Token("Order.ShippingFaxNumber", order.ShippingAddress != null ? order.ShippingAddress.FaxNumber : string.Empty)); - tokens.Add(new Token("Order.ShippingCompany", order.ShippingAddress != null ? order.ShippingAddress.Company : string.Empty)); - tokens.Add(new Token("Order.ShippingAddress1", order.ShippingAddress != null ? order.ShippingAddress.Address1 : string.Empty)); - tokens.Add(new Token("Order.ShippingAddress2", order.ShippingAddress != null ? order.ShippingAddress.Address2 : string.Empty)); - tokens.Add(new Token("Order.ShippingCity", order.ShippingAddress != null ? order.ShippingAddress.City : string.Empty)); - tokens.Add(new Token("Order.ShippingStateProvince", order.ShippingAddress != null && order.ShippingAddress.StateProvince != null ? order.ShippingAddress.StateProvince.GetLocalized(x => x.Name) : string.Empty)); - tokens.Add(new Token("Order.ShippingZipPostalCode", order.ShippingAddress != null ? order.ShippingAddress.ZipPostalCode : string.Empty)); - tokens.Add(new Token("Order.ShippingCountry", order.ShippingAddress != null && order.ShippingAddress.Country != null ? order.ShippingAddress.Country.GetLocalized(x => x.Name) : string.Empty)); - tokens.Add(new Token("Order.ShippingCustomAttributes", _addressAttributeFormatter.FormatAttributes(order.ShippingAddress != null ? order.ShippingAddress.CustomAttributes : string.Empty), true)); - - var paymentMethod = _paymentService.LoadPaymentMethodBySystemName(order.PaymentMethodSystemName); - var paymentMethodName = paymentMethod != null ? paymentMethod.GetLocalizedFriendlyName(_localizationService, _workContext.WorkingLanguage.Id) : order.PaymentMethodSystemName; - tokens.Add(new Token("Order.PaymentMethod", paymentMethodName)); - tokens.Add(new Token("Order.VatNumber", order.VatNumber)); - var sbCustomValues = new StringBuilder(); - var customValues = order.DeserializeCustomValues(); - if (customValues != null) - { - foreach (var item in customValues) - { - sbCustomValues.AppendFormat("{0}: {1}", HttpUtility.HtmlEncode(item.Key), HttpUtility.HtmlEncode(item.Value != null ? item.Value.ToString() : string.Empty)); - sbCustomValues.Append("
    "); - } - } - tokens.Add(new Token("Order.CustomValues", sbCustomValues.ToString(), true)); - - - - tokens.Add(new Token("Order.Product(s)", ProductListToHtmlTable(order, languageId, vendorId), 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)); - tokens.Add(new Token("Order.CreatedOn", createdOn.ToString("D", new CultureInfo(language.LanguageCulture)))); - } - else - { - tokens.Add(new Token("Order.CreatedOn", order.CreatedOnUtc.ToString("D"))); - } - - //TODO add a method for getting URL (use routing because it handles all SEO friendly URLs) - tokens.Add(new Token("Order.OrderURLForCustomer", string.Format("{0}orderdetails/{1}", GetStoreUrl(order.StoreId), order.Id), true)); - - //event notification - _eventPublisher.EntityTokensAdded(order, tokens); - } - - /// - /// Add refunded order tokens - /// - /// List of already added tokens - /// Order - /// Refunded amount of order - public virtual void AddOrderRefundedTokens(IList tokens, Order order, decimal refundedAmount) - { - //should we convert it to customer currency? - //most probably, no. It can cause some rounding or legal issues - //furthermore, exchange rate could be changed - //so let's display it the primary store currency - - var primaryStoreCurrencyCode = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId).CurrencyCode; - var refundedAmountStr = _priceFormatter.FormatPrice(refundedAmount, true, primaryStoreCurrencyCode, false, _workContext.WorkingLanguage); - - tokens.Add(new Token("Order.AmountRefunded", refundedAmountStr)); - - //event notification - _eventPublisher.EntityTokensAdded(order, tokens); - } - - /// - /// Add shipment tokens - /// - /// List of already added tokens - /// Shipment item - /// Language identifier - public virtual void AddShipmentTokens(IList tokens, Shipment shipment, int languageId) - { - tokens.Add(new Token("Shipment.ShipmentNumber", shipment.Id)); - tokens.Add(new Token("Shipment.TrackingNumber", shipment.TrackingNumber)); - var trackingNumberUrl = string.Empty; - if (!String.IsNullOrEmpty(shipment.TrackingNumber)) - { - //we cannot inject IShippingService into constructor because it'll cause circular references. - //that's why we resolve it here this way - var shipmentTracker = shipment.GetShipmentTracker(EngineContext.Current.Resolve(), _shippingSettings); - if (shipmentTracker != null) - trackingNumberUrl = shipmentTracker.GetUrl(shipment.TrackingNumber); - } - tokens.Add(new Token("Shipment.TrackingNumberURL", trackingNumberUrl, true)); - tokens.Add(new Token("Shipment.Product(s)", ProductListToHtmlTable(shipment, languageId), true)); - tokens.Add(new Token("Shipment.URLForCustomer", string.Format("{0}orderdetails/shipment/{1}", GetStoreUrl(shipment.Order.StoreId), shipment.Id), true)); - - //event notification - _eventPublisher.EntityTokensAdded(shipment, tokens); - } - - /// - /// Add order note tokens - /// - /// List of already added tokens - /// Order note - public virtual void AddOrderNoteTokens(IList tokens, OrderNote orderNote) - { - tokens.Add(new Token("Order.NewNoteText", orderNote.FormatOrderNoteText(), true)); - tokens.Add(new Token("Order.OrderNoteAttachmentUrl", string.Format("{0}download/ordernotefile/{1}", GetStoreUrl(orderNote.Order.StoreId), orderNote.Id), true)); - - //event notification - _eventPublisher.EntityTokensAdded(orderNote, tokens); - } - - /// - /// Add recurring payment tokens - /// - /// List of already added tokens - /// Recurring payment - public virtual void AddRecurringPaymentTokens(IList tokens, RecurringPayment recurringPayment) - { - tokens.Add(new Token("RecurringPayment.ID", recurringPayment.Id)); - tokens.Add(new Token("RecurringPayment.CancelAfterFailedPayment", - recurringPayment.LastPaymentFailed && _paymentSettings.CancelRecurringPaymentsAfterFailedPayment)); - if (recurringPayment.InitialOrder != null) - tokens.Add(new Token("RecurringPayment.RecurringPaymentType", _paymentService.GetRecurringPaymentType(recurringPayment.InitialOrder.PaymentMethodSystemName).ToString())); - - //event notification - _eventPublisher.EntityTokensAdded(recurringPayment, tokens); - } - - /// - /// Add return request tokens - /// - /// List of already added tokens - /// Return request - /// Order item - public virtual void AddReturnRequestTokens(IList tokens, ReturnRequest returnRequest, OrderItem orderItem) - { - tokens.Add(new Token("ReturnRequest.CustomNumber", returnRequest.CustomNumber)); - tokens.Add(new Token("ReturnRequest.OrderId", orderItem.OrderId)); - tokens.Add(new Token("ReturnRequest.Product.Quantity", returnRequest.Quantity)); - tokens.Add(new Token("ReturnRequest.Product.Name", orderItem.Product.Name)); - tokens.Add(new Token("ReturnRequest.Reason", returnRequest.ReasonForReturn)); - tokens.Add(new Token("ReturnRequest.RequestedAction", returnRequest.RequestedAction)); - tokens.Add(new Token("ReturnRequest.CustomerComment", HtmlHelper.FormatText(returnRequest.CustomerComments, false, true, false, false, false, false), true)); - tokens.Add(new Token("ReturnRequest.StaffNotes", HtmlHelper.FormatText(returnRequest.StaffNotes, false, true, false, false, false, false), true)); - tokens.Add(new Token("ReturnRequest.Status", returnRequest.ReturnRequestStatus.GetLocalizedEnum(_localizationService, _workContext))); - - //event notification - _eventPublisher.EntityTokensAdded(returnRequest, tokens); - } - - /// - /// Add gift card tokens - /// - /// List of already added tokens - /// Gift card - public virtual void AddGiftCardTokens(IList tokens, GiftCard giftCard) - { - tokens.Add(new Token("GiftCard.SenderName", giftCard.SenderName)); - tokens.Add(new Token("GiftCard.SenderEmail",giftCard.SenderEmail)); - tokens.Add(new Token("GiftCard.RecipientName", giftCard.RecipientName)); - tokens.Add(new Token("GiftCard.RecipientEmail", giftCard.RecipientEmail)); - tokens.Add(new Token("GiftCard.Amount", _priceFormatter.FormatPrice(giftCard.Amount, true, false))); - tokens.Add(new Token("GiftCard.CouponCode", giftCard.GiftCardCouponCode)); - - var giftCardMesage = !String.IsNullOrWhiteSpace(giftCard.Message) ? - HtmlHelper.FormatText(giftCard.Message, false, true, false, false, false, false) : string.Empty; - - tokens.Add(new Token("GiftCard.Message", giftCardMesage, true)); - - //event notification - _eventPublisher.EntityTokensAdded(giftCard, tokens); - } - - /// - /// Add customer tokens - /// - /// List of already added tokens - /// Customer - public virtual void AddCustomerTokens(IList tokens, Customer customer) - { - 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.FirstName", customer.GetAttribute(SystemCustomerAttributeNames.FirstName))); - tokens.Add(new Token("Customer.LastName", customer.GetAttribute(SystemCustomerAttributeNames.LastName))); - tokens.Add(new Token("Customer.VatNumber", customer.GetAttribute(SystemCustomerAttributeNames.VatNumber))); - tokens.Add(new Token("Customer.VatNumberStatus", ((VatNumberStatus)customer.GetAttribute(SystemCustomerAttributeNames.VatNumberStatusId)).ToString())); - - var customAttributesXml = customer.GetAttribute(SystemCustomerAttributeNames.CustomCustomerAttributes); - tokens.Add(new Token("Customer.CustomAttributes", _customerAttributeFormatter.FormatAttributes(customAttributesXml), true)); - - - //note: we do not use SEO friendly URLS because we can get errors caused by having .(dot) in the URL (from the email address) - //TODO add a method for getting URL (use routing because it handles all SEO friendly URLs) - var passwordRecoveryUrl = string.Format("{0}passwordrecovery/confirm?token={1}&email={2}", GetStoreUrl(), customer.GetAttribute(SystemCustomerAttributeNames.PasswordRecoveryToken), HttpUtility.UrlEncode(customer.Email)); - var accountActivationUrl = string.Format("{0}customer/activation?token={1}&email={2}", GetStoreUrl(), customer.GetAttribute(SystemCustomerAttributeNames.AccountActivationToken), HttpUtility.UrlEncode(customer.Email)); - var emailRevalidationUrl = string.Format("{0}customer/revalidateemail?token={1}&email={2}", GetStoreUrl(), customer.GetAttribute(SystemCustomerAttributeNames.EmailRevalidationToken), HttpUtility.UrlEncode(customer.Email)); - var wishlistUrl = string.Format("{0}wishlist/{1}", GetStoreUrl(), customer.CustomerGuid); - - tokens.Add(new Token("Customer.PasswordRecoveryURL", passwordRecoveryUrl, true)); - tokens.Add(new Token("Customer.AccountActivationURL", accountActivationUrl, true)); - tokens.Add(new Token("Customer.EmailRevalidationURL", emailRevalidationUrl, true)); - tokens.Add(new Token("Wishlist.URLForCustomer", wishlistUrl, true)); - - //event notification - _eventPublisher.EntityTokensAdded(customer, tokens); - } - - /// - /// Add vendor tokens - /// - /// List of already added tokens - /// Vendor - public virtual void AddVendorTokens(IList tokens, Vendor vendor) - { - tokens.Add(new Token("Vendor.Name", vendor.Name)); - tokens.Add(new Token("Vendor.Email", vendor.Email)); - - //event notification - _eventPublisher.EntityTokensAdded(vendor, tokens); - } - - /// - /// Add newsletter subscription tokens - /// - /// List of already added tokens - /// Newsletter subscription - public virtual void AddNewsLetterSubscriptionTokens(IList tokens, NewsLetterSubscription subscription) - { - tokens.Add(new Token("NewsLetterSubscription.Email", subscription.Email)); - - - const string urlFormat = "{0}newsletter/subscriptionactivation/{1}/{2}"; - - var activationUrl = String.Format(urlFormat, GetStoreUrl(), subscription.NewsLetterSubscriptionGuid, "true"); - tokens.Add(new Token("NewsLetterSubscription.ActivationUrl", activationUrl, true)); - - var deActivationUrl = String.Format(urlFormat, GetStoreUrl(), subscription.NewsLetterSubscriptionGuid, "false"); - tokens.Add(new Token("NewsLetterSubscription.DeactivationUrl", deActivationUrl, true)); - - //event notification - _eventPublisher.EntityTokensAdded(subscription, tokens); - } - - /// - /// Add product review tokens - /// - /// List of already added tokens - /// Product review - public virtual void AddProductReviewTokens(IList tokens, ProductReview productReview) - { - tokens.Add(new Token("ProductReview.ProductName", productReview.Product.Name)); - - //event notification - _eventPublisher.EntityTokensAdded(productReview, tokens); - } - - /// - /// Add blog comment tokens - /// - /// List of already added tokens - /// Blog post comment - public virtual void AddBlogCommentTokens(IList tokens, BlogComment blogComment) - { - tokens.Add(new Token("BlogComment.BlogPostTitle", blogComment.BlogPost.Title)); - - //event notification - _eventPublisher.EntityTokensAdded(blogComment, tokens); - } - - /// - /// Add news comment tokens - /// - /// List of already added tokens - /// News comment - public virtual void AddNewsCommentTokens(IList tokens, NewsComment newsComment) - { - tokens.Add(new Token("NewsComment.NewsTitle", newsComment.NewsItem.Title)); - - //event notification - _eventPublisher.EntityTokensAdded(newsComment, tokens); - } - - /// - /// Add product tokens - /// - /// List of already added tokens - /// Product - /// Language identifier - public virtual void AddProductTokens(IList tokens, Product product, int languageId) - { - tokens.Add(new Token("Product.ID", product.Id)); - 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.SKU", product.Sku)); - tokens.Add(new Token("Product.StockQuantity", product.GetTotalStockQuantity())); - - //TODO add a method for getting URL (use routing because it handles all SEO friendly URLs) - var productUrl = string.Format("{0}{1}", GetStoreUrl(), product.GetSeName()); - tokens.Add(new Token("Product.ProductURLForCustomer", productUrl, true)); - - //event notification - _eventPublisher.EntityTokensAdded(product, tokens); - } - - /// - /// Add product attribute combination tokens - /// - /// List of already added tokens - /// Product attribute combination - /// Language identifier - public virtual void AddAttributeCombinationTokens(IList tokens, ProductAttributeCombination combination, int languageId) - { - //attributes - //we cannot inject IProductAttributeFormatter into constructor because it'll cause circular references. - //that's why we resolve it here this way - var productAttributeFormatter = EngineContext.Current.Resolve(); - string attributes = productAttributeFormatter.FormatAttributes(combination.Product, - combination.AttributesXml, - _workContext.CurrentCustomer, - renderPrices: false); - - - - tokens.Add(new Token("AttributeCombination.Formatted", attributes, true)); - tokens.Add(new Token("AttributeCombination.SKU", combination.Product.FormatSku(combination.AttributesXml, _productAttributeParser))); - tokens.Add(new Token("AttributeCombination.StockQuantity", combination.StockQuantity)); - - //event notification - _eventPublisher.EntityTokensAdded(combination, tokens); - } - - /// - /// Add forum topic tokens - /// - /// List of already added tokens - /// Forum topic - /// Friendly (starts with 1) forum topic page to use for URL generation - /// Forum post identifier - public virtual void AddForumTopicTokens(IList tokens, ForumTopic forumTopic, - int? friendlyForumTopicPageIndex = null, int? appendedPostIdentifierAnchor = null) - { - //TODO add a method for getting URL (use routing because it handles all SEO friendly URLs) - string topicUrl; - if (friendlyForumTopicPageIndex.HasValue && friendlyForumTopicPageIndex.Value > 1) - topicUrl = string.Format("{0}boards/topic/{1}/{2}/page/{3}", GetStoreUrl(), forumTopic.Id, forumTopic.GetSeName(), friendlyForumTopicPageIndex.Value); - else - topicUrl = string.Format("{0}boards/topic/{1}/{2}", GetStoreUrl(), forumTopic.Id, forumTopic.GetSeName()); - if (appendedPostIdentifierAnchor.HasValue && appendedPostIdentifierAnchor.Value > 0) - topicUrl = string.Format("{0}#{1}", topicUrl, appendedPostIdentifierAnchor.Value); - tokens.Add(new Token("Forums.TopicURL", topicUrl, true)); - tokens.Add(new Token("Forums.TopicName", forumTopic.Subject)); - - //event notification - _eventPublisher.EntityTokensAdded(forumTopic, tokens); - } - - /// - /// Add forum post tokens - /// - /// List of already added tokens - /// Forum post - public virtual void AddForumPostTokens(IList tokens, ForumPost forumPost) - { - tokens.Add(new Token("Forums.PostAuthor", forumPost.Customer.FormatUserName())); - tokens.Add(new Token("Forums.PostBody", forumPost.FormatPostText(), true)); - - //event notification - _eventPublisher.EntityTokensAdded(forumPost, tokens); - } - - /// - /// Add forum tokens - /// - /// List of already added tokens - /// Forum - public virtual void AddForumTokens(IList tokens, Forum forum) - { - //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}", GetStoreUrl(), forum.Id, forum.GetSeName()); - tokens.Add(new Token("Forums.ForumURL", forumUrl, true)); - tokens.Add(new Token("Forums.ForumName", forum.Name)); - - //event notification - _eventPublisher.EntityTokensAdded(forum, tokens); - } - - /// - /// Add private message tokens - /// - /// List of already added tokens - /// Private message - public virtual void AddPrivateMessageTokens(IList tokens, PrivateMessage privateMessage) - { - tokens.Add(new Token("PrivateMessage.Subject", privateMessage.Subject)); - tokens.Add(new Token("PrivateMessage.Text", privateMessage.FormatPrivateMessageText(), true)); - - //event notification - _eventPublisher.EntityTokensAdded(privateMessage, tokens); - } - - /// - /// Add tokens of BackInStock subscription - /// - /// List of already added tokens - /// BackInStock subscription - public virtual void AddBackInStockTokens(IList tokens, BackInStockSubscription subscription) - { - tokens.Add(new Token("BackInStockSubscription.ProductName", subscription.Product.Name)); - //TODO add a method for getting URL (use routing because it handles all SEO friendly URLs) - var productUrl = string.Format("{0}{1}", GetStoreUrl(subscription.StoreId), subscription.Product.GetSeName()); - tokens.Add(new Token("BackInStockSubscription.ProductUrl", productUrl, true)); - - //event notification - _eventPublisher.EntityTokensAdded(subscription, tokens); - } - - /// - /// Get collection of allowed (supported) message tokens for campaigns - /// - /// Collection of allowed (supported) message tokens for campaigns - public virtual IEnumerable GetListOfCampaignAllowedTokens() - { - var additionTokens = new CampaignAdditionTokensAddedEvent(); - _eventPublisher.Publish(additionTokens); - - var allowedTokens = GetListOfAllowedTokens(new[] { TokenGroupNames.StoreTokens, TokenGroupNames.SubscriptionTokens }).ToList(); - allowedTokens.AddRange(additionTokens.AdditionTokens); - - return allowedTokens.Distinct(); - } - - /// - /// Get collection of allowed (supported) message tokens - /// - /// Collection of token groups; pass null to get all available tokens - /// Collection of allowed message tokens - public virtual IEnumerable GetListOfAllowedTokens(IEnumerable tokenGroups = null) - { - var additionTokens = new AdditionTokensAddedEvent(); - _eventPublisher.Publish(additionTokens); - - var allowedTokens = AllowedTokens.Where(x => tokenGroups == null || tokenGroups.Contains(x.Key)) - .SelectMany(x => x.Value).ToList(); - - allowedTokens.AddRange(additionTokens.AdditionTokens); - - return allowedTokens.Distinct(); - } - - #endregion - } -} +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Web; +using Nop.Core; +using Nop.Core.Domain; +using Nop.Core.Domain.Blogs; +using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Customers; +using Nop.Core.Domain.Directory; +using Nop.Core.Domain.Forums; +using Nop.Core.Domain.Messages; +using Nop.Core.Domain.News; +using Nop.Core.Domain.Orders; +using Nop.Core.Domain.Payments; +using Nop.Core.Domain.Shipping; +using Nop.Core.Domain.Stores; +using Nop.Core.Domain.Tax; +using Nop.Core.Domain.Vendors; +using Nop.Core.Html; +using Nop.Core.Infrastructure; +using Nop.Services.Catalog; +using Nop.Services.Common; +using Nop.Services.Customers; +using Nop.Services.Directory; +using Nop.Services.Events; +using Nop.Services.Forums; +using Nop.Services.Helpers; +using Nop.Services.Localization; +using Nop.Services.Media; +using Nop.Services.Orders; +using Nop.Services.Payments; +using Nop.Services.Seo; +using Nop.Services.Shipping; +using Nop.Services.Shipping.Tracking; +using Nop.Services.Stores; + +namespace Nop.Services.Messages +{ + public partial class MessageTokenProvider : IMessageTokenProvider + { + #region Fields + + private readonly ILanguageService _languageService; + private readonly ILocalizationService _localizationService; + private readonly IDateTimeHelper _dateTimeHelper; + private readonly IPriceFormatter _priceFormatter; + private readonly ICurrencyService _currencyService; + private readonly IWorkContext _workContext; + private readonly IDownloadService _downloadService; + private readonly IOrderService _orderService; + private readonly IPaymentService _paymentService; + private readonly IProductAttributeParser _productAttributeParser; + private readonly IAddressAttributeFormatter _addressAttributeFormatter; + private readonly ICustomerAttributeFormatter _customerAttributeFormatter; + private readonly IStoreService _storeService; + private readonly IStoreContext _storeContext; + + private readonly MessageTemplatesSettings _templatesSettings; + private readonly CatalogSettings _catalogSettings; + private readonly TaxSettings _taxSettings; + private readonly CurrencySettings _currencySettings; + private readonly ShippingSettings _shippingSettings; + private readonly PaymentSettings _paymentSettings; + + private readonly IEventPublisher _eventPublisher; + private readonly StoreInformationSettings _storeInformationSettings; + + #endregion + + #region Ctor + + public MessageTokenProvider(ILanguageService languageService, + ILocalizationService localizationService, + IDateTimeHelper dateTimeHelper, + IPriceFormatter priceFormatter, + ICurrencyService currencyService, + IWorkContext workContext, + IDownloadService downloadService, + IOrderService orderService, + IPaymentService paymentService, + IStoreService storeService, + IStoreContext storeContext, + IProductAttributeParser productAttributeParser, + IAddressAttributeFormatter addressAttributeFormatter, + ICustomerAttributeFormatter customerAttributeFormatter, + MessageTemplatesSettings templatesSettings, + CatalogSettings catalogSettings, + TaxSettings taxSettings, + CurrencySettings currencySettings, + ShippingSettings shippingSettings, + PaymentSettings paymentSettings, + IEventPublisher eventPublisher, + StoreInformationSettings storeInformationSettings) + { + this._languageService = languageService; + this._localizationService = localizationService; + this._dateTimeHelper = dateTimeHelper; + this._priceFormatter = priceFormatter; + this._currencyService = currencyService; + this._workContext = workContext; + this._downloadService = downloadService; + this._orderService = orderService; + this._paymentService = paymentService; + this._productAttributeParser = productAttributeParser; + this._addressAttributeFormatter = addressAttributeFormatter; + this._customerAttributeFormatter = customerAttributeFormatter; + this._storeService = storeService; + this._storeContext = storeContext; + + this._templatesSettings = templatesSettings; + this._catalogSettings = catalogSettings; + this._taxSettings = taxSettings; + this._currencySettings = currencySettings; + this._shippingSettings = shippingSettings; + this._paymentSettings = paymentSettings; + this._eventPublisher = eventPublisher; + this._storeInformationSettings = storeInformationSettings; + } + + #endregion + + #region Allowed tokens + + private Dictionary> _allowedTokens; + /// + /// Get all available tokens by token groups + /// + protected Dictionary> AllowedTokens + { + get + { + if (_allowedTokens != null) + return _allowedTokens; + + _allowedTokens = new Dictionary>(); + + //store tokens + _allowedTokens.Add(TokenGroupNames.StoreTokens, new[] + { + "%Store.Name%", + "%Store.URL%", + "%Store.Email%", + "%Store.CompanyName%", + "%Store.CompanyAddress%", + "%Store.CompanyPhoneNumber%", + "%Store.CompanyVat%", + "%Facebook.URL%", + "%Twitter.URL%", + "%YouTube.URL%", + "%GooglePlus.URL%" + }); + + //customer tokens + _allowedTokens.Add(TokenGroupNames.CustomerTokens, new[] + { + "%Customer.Email%", + "%Customer.Username%", + "%Customer.FullName%", + "%Customer.FirstName%", + "%Customer.LastName%", + "%Customer.VatNumber%", + "%Customer.VatNumberStatus%", + "%Customer.CustomAttributes%", + "%Customer.PasswordRecoveryURL%", + "%Customer.AccountActivationURL%", + "%Customer.EmailRevalidationURL%", + "%Wishlist.URLForCustomer%" + }); + + //order tokens + _allowedTokens.Add(TokenGroupNames.OrderTokens, new[] + { + "%Order.OrderNumber%", + "%Order.CustomerFullName%", + "%Order.CustomerEmail%", + "%Order.BillingFirstName%", + "%Order.BillingLastName%", + "%Order.BillingPhoneNumber%", + "%Order.BillingEmail%", + "%Order.BillingFaxNumber%", + "%Order.BillingCompany%", + "%Order.BillingAddress1%", + "%Order.BillingAddress2%", + "%Order.BillingCity%", + "%Order.BillingStateProvince%", + "%Order.BillingZipPostalCode%", + "%Order.BillingCountry%", + "%Order.BillingCustomAttributes%", + "%Order.Shippable%", + "%Order.ShippingMethod%", + "%Order.ShippingFirstName%", + "%Order.ShippingLastName%", + "%Order.ShippingPhoneNumber%", + "%Order.ShippingEmail%", + "%Order.ShippingFaxNumber%", + "%Order.ShippingCompany%", + "%Order.ShippingAddress1%", + "%Order.ShippingAddress2%", + "%Order.ShippingCity%", + "%Order.ShippingStateProvince%", + "%Order.ShippingZipPostalCode%", + "%Order.ShippingCountry%", + "%Order.ShippingCustomAttributes%", + "%Order.PaymentMethod%", + "%Order.VatNumber%", + "%Order.CustomValues%", + "%Order.Product(s)%", + "%Order.CreatedOn%", + "%Order.OrderURLForCustomer%" + }); + + //shipment tokens + _allowedTokens.Add(TokenGroupNames.ShipmentTokens, new[] + { + "%Shipment.ShipmentNumber%", + "%Shipment.TrackingNumber%", + "%Shipment.TrackingNumberURL%", + "%Shipment.Product(s)%", + "%Shipment.URLForCustomer%" + }); + + //refunded order tokens + _allowedTokens.Add(TokenGroupNames.RefundedOrderTokens, new[] + { + "%Order.AmountRefunded%" + }); + + //order note tokens + _allowedTokens.Add(TokenGroupNames.OrderNoteTokens, new[] + { + "%Order.NewNoteText%", + "%Order.OrderNoteAttachmentUrl%" + }); + + //recurring payment tokens + _allowedTokens.Add(TokenGroupNames.RecurringPaymentTokens, new[] + { + "%RecurringPayment.ID%", + "%RecurringPayment.CancelAfterFailedPayment%", + "%RecurringPayment.RecurringPaymentType%" + }); + + //newsletter subscription tokens + _allowedTokens.Add(TokenGroupNames.SubscriptionTokens, new[] + { + "%NewsLetterSubscription.Email%", + "%NewsLetterSubscription.ActivationUrl%", + "%NewsLetterSubscription.DeactivationUrl%" + }); + + //product tokens + _allowedTokens.Add(TokenGroupNames.ProductTokens, new[] + { + "%Product.ID%", + "%Product.Name%", + "%Product.ShortDescription%", + "%Product.ProductURLForCustomer%", + "%Product.SKU%", + "%Product.StockQuantity%" + }); + + //return request tokens + _allowedTokens.Add(TokenGroupNames.ReturnRequestTokens, new[] + { + "%ReturnRequest.CustomNumber%", + "%ReturnRequest.OrderId%", + "%ReturnRequest.Product.Quantity%", + "%ReturnRequest.Product.Name%", + "%ReturnRequest.Reason%", + "%ReturnRequest.RequestedAction%", + "%ReturnRequest.CustomerComment%", + "%ReturnRequest.StaffNotes%", + "%ReturnRequest.Status%" + }); + + //forum tokens + _allowedTokens.Add(TokenGroupNames.ForumTokens, new[] + { + "%Forums.ForumURL%", + "%Forums.ForumName%" + }); + + //forum topic tokens + _allowedTokens.Add(TokenGroupNames.ForumTopicTokens, new[] + { + "%Forums.TopicURL%", + "%Forums.TopicName%" + }); + + //forum post tokens + _allowedTokens.Add(TokenGroupNames.ForumPostTokens, new[] + { + "%Forums.PostAuthor%", + "%Forums.PostBody%" + }); + + //private message tokens + _allowedTokens.Add(TokenGroupNames.PrivateMessageTokens, new[] + { + "%PrivateMessage.Subject%", + "%PrivateMessage.Text%" + }); + + //vendor tokens + _allowedTokens.Add(TokenGroupNames.VendorTokens, new[] + { + "%Vendor.Name%", + "%Vendor.Email%" + }); + + //gift card tokens + _allowedTokens.Add(TokenGroupNames.GiftCardTokens, new[] + { + "%GiftCard.SenderName%", + "%GiftCard.SenderEmail%", + "%GiftCard.RecipientName%", + "%GiftCard.RecipientEmail%", + "%GiftCard.Amount%", + "%GiftCard.CouponCode%", + "%GiftCard.Message%" + }); + + //product review tokens + _allowedTokens.Add(TokenGroupNames.ProductReviewTokens, new[] + { + "%ProductReview.ProductName%" + }); + + //attribute combination tokens + _allowedTokens.Add(TokenGroupNames.AttributeCombinationTokens, new[] + { + "%AttributeCombination.Formatted%", + "%AttributeCombination.SKU%", + "%AttributeCombination.StockQuantity%" + }); + + //blog comment tokens + _allowedTokens.Add(TokenGroupNames.BlogCommentTokens, new[] + { + "%BlogComment.BlogPostTitle%" + }); + + //news comment tokens + _allowedTokens.Add(TokenGroupNames.NewsCommentTokens, new[] + { + "%NewsComment.NewsTitle%" + }); + + //product back in stock tokens + _allowedTokens.Add(TokenGroupNames.ProductBackInStockTokens, new[] + { + "%BackInStockSubscription.ProductName%", + "%BackInStockSubscription.ProductUrl%" + }); + + //email a friend tokens + _allowedTokens.Add(TokenGroupNames.EmailAFriendTokens, new[] + { + "%EmailAFriend.PersonalMessage%", + "%EmailAFriend.Email%" + }); + + //wishlist to friend tokens + _allowedTokens.Add(TokenGroupNames.WishlistToFriendTokens, new[] + { + "%Wishlist.PersonalMessage%", + "%Wishlist.Email%" + }); + + //VAT validation tokens + _allowedTokens.Add(TokenGroupNames.VatValidation, new[] + { + "%VatValidationResult.Name%", + "%VatValidationResult.Address%" + }); + + //contact us tokens + _allowedTokens.Add(TokenGroupNames.ContactUs, new[] + { + "%ContactUs.SenderEmail%", + "%ContactUs.SenderName%", + "%ContactUs.Body%" + }); + + //contact vendor tokens + _allowedTokens.Add(TokenGroupNames.ContactVendor, new[] + { + "%ContactUs.SenderEmail%", + "%ContactUs.SenderName%", + "%ContactUs.Body%" + }); + + return _allowedTokens; + } + } + + #endregion + + #region Utilities + + /// + /// Convert a collection to a HTML table + /// + /// Order + /// Language identifier + /// Vendor identifier (used to limit products by vendor + /// HTML table of products + protected virtual string ProductListToHtmlTable(Order order, int languageId, int vendorId) + { + string result; + var includingTax = order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax; + var language = _languageService.GetLanguageById(languageId); + + 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).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(""); + + var table = order.OrderItems.ToList(); + for (int i = 0; i <= table.Count - 1; i++) + { + var orderItem = table[i]; + var product = orderItem.Product; + if (product == null) + continue; + + if (vendorId > 0 && product.VendorId != vendorId) + continue; + + sb.AppendLine(string.Format("", _templatesSettings.Color2)); + //product name + string productName = product.GetLocalized(x => x.Name, languageId); + + sb.AppendLine(""); + + string unitPriceStr; + if (includingTax) + { + //including tax + var unitPriceInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(orderItem.UnitPriceInclTax, order.CurrencyRate); + unitPriceStr = _priceFormatter.FormatPrice(unitPriceInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, true); + } + else + { + //excluding tax + var unitPriceExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(orderItem.UnitPriceExclTax, order.CurrencyRate); + unitPriceStr = _priceFormatter.FormatPrice(unitPriceExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, false); + } + sb.AppendLine(string.Format("", unitPriceStr)); + + sb.AppendLine(string.Format("", orderItem.Quantity)); + + string priceStr; + if (includingTax) + { + //including tax + var priceInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(orderItem.PriceInclTax, order.CurrencyRate); + priceStr = _priceFormatter.FormatPrice(priceInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, true); + } + else + { + //excluding tax + var priceExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(orderItem.PriceExclTax, order.CurrencyRate); + priceStr = _priceFormatter.FormatPrice(priceExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, false); + } + sb.AppendLine(string.Format("", priceStr)); + + sb.AppendLine(""); + } + #endregion + + if (vendorId == 0) + { + //we render checkout attributes and totals only for store owners (hide for vendors) + + #region Checkout Attributes + + if (!String.IsNullOrEmpty(order.CheckoutAttributeDescription)) + { + sb.AppendLine(""); + } + + #endregion + + #region Totals + + //subtotal + string cusSubTotal; + bool displaySubTotalDiscount = false; + string cusSubTotalDiscount = string.Empty; + if (includingTax && !_taxSettings.ForceTaxExclusionFromOrderSubtotal) + { + //including tax + + //subtotal + var orderSubtotalInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubtotalInclTax, order.CurrencyRate); + cusSubTotal = _priceFormatter.FormatPrice(orderSubtotalInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, true); + //discount (applied to order subtotal) + var orderSubTotalDiscountInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubTotalDiscountInclTax, order.CurrencyRate); + if (orderSubTotalDiscountInclTaxInCustomerCurrency > decimal.Zero) + { + cusSubTotalDiscount = _priceFormatter.FormatPrice(-orderSubTotalDiscountInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, true); + displaySubTotalDiscount = true; + } + } + else + { + //exсluding tax + + //subtotal + var orderSubtotalExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubtotalExclTax, order.CurrencyRate); + cusSubTotal = _priceFormatter.FormatPrice(orderSubtotalExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, false); + //discount (applied to order subtotal) + var orderSubTotalDiscountExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubTotalDiscountExclTax, order.CurrencyRate); + if (orderSubTotalDiscountExclTaxInCustomerCurrency > decimal.Zero) + { + cusSubTotalDiscount = _priceFormatter.FormatPrice(-orderSubTotalDiscountExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, false); + displaySubTotalDiscount = true; + } + } + + //shipping, payment method fee + string cusShipTotal; + string cusPaymentMethodAdditionalFee; + var taxRates = new SortedDictionary(); + string cusTaxTotal = string.Empty; + string cusDiscount = string.Empty; + string cusTotal; + string cusAmount; + string cusAmountIncl; + if (includingTax) + { + //including tax + + //shipping + var orderShippingInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderShippingInclTax + order.OrderShippingNonTaxable, order.CurrencyRate); + cusShipTotal = _priceFormatter.FormatShippingPrice(orderShippingInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, true); + //payment method additional fee + var paymentMethodAdditionalFeeInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.PaymentMethodAdditionalFeeInclTax + order.PaymentMethodAdditionalFeeNonTaxable, order.CurrencyRate); + cusPaymentMethodAdditionalFee = _priceFormatter.FormatPaymentMethodAdditionalFee(paymentMethodAdditionalFeeInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, true); + } + else + { + //excluding tax + + //shipping + var orderShippingExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderShippingExclTax + order.OrderShippingNonTaxable, order.CurrencyRate); + cusShipTotal = _priceFormatter.FormatShippingPrice(orderShippingExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, false); + //payment method additional fee + var paymentMethodAdditionalFeeExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.PaymentMethodAdditionalFeeExclTax + order.PaymentMethodAdditionalFeeNonTaxable, order.CurrencyRate); + cusPaymentMethodAdditionalFee = _priceFormatter.FormatPaymentMethodAdditionalFee(paymentMethodAdditionalFeeExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, language, false); + } + + //shipping + bool displayShipping = order.ShippingStatus != ShippingStatus.ShippingNotRequired; + + //payment method fee + bool displayPaymentMethodFee = order.PaymentMethodAdditionalFeeExclTax + order.PaymentMethodAdditionalFeeNonTaxable != decimal.Zero; //allow negative + + //tax + bool displayTax = true; + bool displayTaxRates = true; + if (_taxSettings.HideTaxInOrderSummary && includingTax) + { + displayTax = false; + displayTaxRates = false; + } + else + { + if (order.OrderTax == 0 && _taxSettings.HideZeroTax) + { + displayTax = false; + displayTaxRates = false; + } + else + { + taxRates = new SortedDictionary(); + foreach (var tr in order.TaxRatesDictionary) + //taxRates.Add(tr.Key, _currencyService.ConvertCurrency(tr.Value, order.CurrencyRate)); + taxRates.Add(tr.Key, new TaxRateRec() + { + TaxRate = tr.Key, + Amount = _currencyService.ConvertCurrency(tr.Value.Amount, order.CurrencyRate), + DiscountAmount = _currencyService.ConvertCurrency(tr.Value.DiscountAmount, order.CurrencyRate), + BaseAmount = _currencyService.ConvertCurrency(tr.Value.BaseAmount, order.CurrencyRate), + TaxAmount = _currencyService.ConvertCurrency(tr.Value.TaxAmount, order.CurrencyRate), + AmountIncludingTax = _currencyService.ConvertCurrency(tr.Value.AmountIncludingTax, order.CurrencyRate) + }); + + displayTaxRates = _taxSettings.DisplayTaxRates && taxRates.Any(); + displayTax = !displayTaxRates; + + var orderTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderTax, order.CurrencyRate); + string taxStr = _priceFormatter.FormatPrice(orderTaxInCustomerCurrency, true, order.CustomerCurrencyCode, false, language); + cusTaxTotal = taxStr; + } + } + + //discount + bool displayDiscount = false; + if (order.OrderDiscount > decimal.Zero) + { + var orderDiscountInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderDiscount, order.CurrencyRate); + cusDiscount = _priceFormatter.FormatPrice(-orderDiscountInCustomerCurrency, true, order.CustomerCurrencyCode, false, language); + displayDiscount = true; + } + + //total + var orderTotalInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderTotal, order.CurrencyRate); + cusTotal = _priceFormatter.FormatPrice(orderTotalInCustomerCurrency, true, order.CustomerCurrencyCode, false, language); + + var orderAmountInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderAmount, order.CurrencyRate); + cusAmount = _priceFormatter.FormatPrice(orderAmountInCustomerCurrency, true, order.CustomerCurrencyCode, false, language); + + var orderAmountInclInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderAmountIncl, order.CurrencyRate); + cusAmountIncl = _priceFormatter.FormatPrice(orderAmountInclInCustomerCurrency, true, order.CustomerCurrencyCode, false, language); + + //subtotal + sb.AppendLine(string.Format("", _templatesSettings.Color3, _localizationService.GetResource("Messages.Order.SubTotal", languageId), cusSubTotal)); + + //discount (applied to order subtotal) + if (displaySubTotalDiscount) + { + sb.AppendLine(string.Format("", _templatesSettings.Color3, _localizationService.GetResource("Messages.Order.SubTotalDiscount", languageId), cusSubTotalDiscount)); + } + + + //shipping + if (displayShipping) + { + sb.AppendLine(string.Format("", _templatesSettings.Color3, _localizationService.GetResource("Messages.Order.Shipping", languageId), cusShipTotal)); + } + + //payment method fee + if (displayPaymentMethodFee) + { + string paymentMethodFeeTitle = _localizationService.GetResource("Messages.Order.PaymentMethodAdditionalFee", languageId); + sb.AppendLine(string.Format("", _templatesSettings.Color3, paymentMethodFeeTitle, cusPaymentMethodAdditionalFee)); + } + + //discount + if (displayDiscount) + { + sb.AppendLine(string.Format("", _templatesSettings.Color3, _localizationService.GetResource("Messages.Order.TotalDiscount", languageId), cusDiscount)); + } + + //amount + if (includingTax) + { + sb.AppendLine(string.Format("", _templatesSettings.Color3, _localizationService.GetResource("Messages.Order.OrderAmountIncl", languageId), cusAmountIncl)); + } + + //tax + if (displayTax) + { + sb.AppendLine(string.Format("", _templatesSettings.Color3, _localizationService.GetResource(includingTax ? "Messages.Order.TaxIncl" : "Messages.Order.Tax", languageId), cusTaxTotal)); + } + + //amount + if (!includingTax) + { + sb.AppendLine(string.Format("", _templatesSettings.Color3, _localizationService.GetResource("Messages.Order.OrderAmount", languageId), cusAmount)); + } + if (displayTaxRates) + { + foreach (var item in taxRates) + { + string taxRate = String.Format(_localizationService.GetResource("Messages.Order.TaxRateLine"), _priceFormatter.FormatTaxRate(item.Key)); + string OrderAmount = _priceFormatter.FormatPrice(item.Value.Amount, true, order.CustomerCurrencyCode, false, language); + string DiscountAmount = _priceFormatter.FormatPrice(item.Value.DiscountAmount, true, order.CustomerCurrencyCode, false, language); + string Amount = _priceFormatter.FormatPrice(item.Value.BaseAmount, true, order.CustomerCurrencyCode, false, language); + string TaxAmount = _priceFormatter.FormatPrice(item.Value.TaxAmount, true, order.CustomerCurrencyCode, false, language); + string AmountIncludingVAT = _priceFormatter.FormatPrice(item.Value.AmountIncludingTax, true, order.CustomerCurrencyCode, false, language); + + //TODO: fill whole tax table + sb.AppendLine(string.Format("", + _templatesSettings.Color3, taxRate, TaxAmount, OrderAmount, DiscountAmount, Amount, AmountIncludingVAT)); + } + } + + //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 giftCardAmount = _priceFormatter.FormatPrice(-(_currencyService.ConvertCurrency(gcuh.UsedValue, order.CurrencyRate)), true, order.CustomerCurrencyCode, false, language); + sb.AppendLine(string.Format("", _templatesSettings.Color3, giftCardText, giftCardAmount)); + } + + //reward points + if (order.RedeemedRewardPointsEntry != null) + { + string rpTitle = string.Format(_localizationService.GetResource("Messages.Order.RewardPoints", languageId), - (order.RedeemedRewardPointsEntry.Points + order.RedeemedRewardPointsEntry.PointsPurchased)); + string rpAmount = _priceFormatter.FormatPrice(-(_currencyService.ConvertCurrency(order.RedeemedRewardPointsEntry.UsedAmount + order.RedeemedRewardPointsEntry.UsedAmountPurchased, order.CurrencyRate)), true, order.CustomerCurrencyCode, false, language); + sb.AppendLine(string.Format("", _templatesSettings.Color3, rpTitle, rpAmount)); + } + + //total + sb.AppendLine(string.Format("", _templatesSettings.Color3, _localizationService.GetResource("Messages.Order.OrderTotal", languageId), cusTotal)); + #endregion + + } + + sb.AppendLine("
    {0}{0}{0}{0}
    " + 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}", GetStoreUrl(order.StoreId), orderItem.OrderItemGuid); + string downloadLink = string.Format("{1}", downloadUrl, _localizationService.GetResource("Messages.Order.Product(s).Download", languageId)); + sb.AppendLine("
    "); + sb.AppendLine(downloadLink); + } + //add download link + if (_downloadService.IsLicenseDownloadAllowed(orderItem)) + { + //TODO add a method for getting URL (use routing because it handles all SEO friendly URLs) + string licenseUrl = string.Format("{0}download/getlicense/{1}", GetStoreUrl(order.StoreId), orderItem.OrderItemGuid); + string licenseLink = string.Format("{1}", licenseUrl, _localizationService.GetResource("Messages.Order.Product(s).License", languageId)); + sb.AppendLine("
    "); + sb.AppendLine(licenseLink); + } + //attributes + if (!String.IsNullOrEmpty(orderItem.AttributeDescription)) + { + sb.AppendLine("
    "); + sb.AppendLine(orderItem.AttributeDescription); + } + //rental info + if (orderItem.Product.IsRental) + { + var rentalStartDate = orderItem.RentalStartDateUtc.HasValue ? orderItem.Product.FormatRentalDate(orderItem.RentalStartDateUtc.Value) : string.Empty; + var rentalEndDate = orderItem.RentalEndDateUtc.HasValue ? orderItem.Product.FormatRentalDate(orderItem.RentalEndDateUtc.Value) : string.Empty; + var rentalInfo = string.Format(_localizationService.GetResource("Order.Rental.FormattedDate"), + rentalStartDate, rentalEndDate); + sb.AppendLine("
    "); + sb.AppendLine(rentalInfo); + } + //sku + if (_catalogSettings.ShowSkuOnProductDetailsPage) + { + var sku = product.FormatSku(orderItem.AttributesXml, _productAttributeParser); + if (!String.IsNullOrEmpty(sku)) + { + sb.AppendLine("
    "); + sb.AppendLine(string.Format(_localizationService.GetResource("Messages.Order.Product(s).SKU", languageId), HttpUtility.HtmlEncode(sku))); + } + } + sb.AppendLine("
    {0}{0}{0}
     "); + sb.AppendLine(order.CheckoutAttributeDescription); + sb.AppendLine("
     {1} {2}
     {1} {2}
     {1} {2}
     {1} {2}
     {1} {2}
     {1} {2}
     {1} {2}
     {1} {2}
     {1} {2}
     {1} {2}
     {1} {2}
     {1} {2}
    "); + result = sb.ToString(); + return result; + } + + /// + /// Convert a collection to a HTML table + /// + /// Shipment + /// Language identifier + /// HTML table of products + protected virtual string ProductListToHtmlTable(Shipment shipment, int languageId) + { + string 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(""); + + var table = shipment.ShipmentItems.ToList(); + for (int i = 0; i <= table.Count - 1; i++) + { + var si = table[i]; + var orderItem = _orderService.GetOrderItemById(si.OrderItemId); + if (orderItem == null) + continue; + + var product = orderItem.Product; + if (product == null) + continue; + + sb.AppendLine(string.Format("", _templatesSettings.Color2)); + //product name + string productName = product.GetLocalized(x => x.Name, languageId); + + sb.AppendLine(""); + + sb.AppendLine(string.Format("", si.Quantity)); + + sb.AppendLine(""); + } + #endregion + + sb.AppendLine("
    {0}{0}
    " + HttpUtility.HtmlEncode(productName)); + //attributes + if (!String.IsNullOrEmpty(orderItem.AttributeDescription)) + { + sb.AppendLine("
    "); + sb.AppendLine(orderItem.AttributeDescription); + } + //rental info + if (orderItem.Product.IsRental) + { + var rentalStartDate = orderItem.RentalStartDateUtc.HasValue ? orderItem.Product.FormatRentalDate(orderItem.RentalStartDateUtc.Value) : string.Empty; + var rentalEndDate = orderItem.RentalEndDateUtc.HasValue ? orderItem.Product.FormatRentalDate(orderItem.RentalEndDateUtc.Value) : string.Empty; + var rentalInfo = string.Format(_localizationService.GetResource("Order.Rental.FormattedDate"), + rentalStartDate, rentalEndDate); + sb.AppendLine("
    "); + sb.AppendLine(rentalInfo); + } + //sku + if (_catalogSettings.ShowSkuOnProductDetailsPage) + { + var sku = product.FormatSku(orderItem.AttributesXml, _productAttributeParser); + if (!String.IsNullOrEmpty(sku)) + { + sb.AppendLine("
    "); + sb.AppendLine(string.Format(_localizationService.GetResource("Messages.Order.Product(s).SKU", languageId), HttpUtility.HtmlEncode(sku))); + } + } + sb.AppendLine("
    {0}
    "); + result = sb.ToString(); + return result; + } + + /// + /// Get store URL + /// + /// Store identifier; Pass 0 to load URL of the current store + /// + protected virtual string GetStoreUrl(int storeId = 0) + { + var store = _storeService.GetStoreById(storeId) ?? _storeContext.CurrentStore; + + if (store == null) + throw new Exception("No store could be loaded"); + + return store.Url; + } + + #endregion + + #region Methods + + /// + /// Add store tokens + /// + /// List of already added tokens + /// Store + /// Email account + public virtual void AddStoreTokens(IList tokens, Store store, EmailAccount emailAccount) + { + if (emailAccount == null) + throw new ArgumentNullException("emailAccount"); + + tokens.Add(new Token("Store.Name", store.GetLocalized(x => x.Name))); + tokens.Add(new Token("Store.URL", store.Url, true)); + tokens.Add(new Token("Store.Email", emailAccount.Email)); + tokens.Add(new Token("Store.CompanyName", store.CompanyName)); + tokens.Add(new Token("Store.CompanyAddress", store.CompanyAddress)); + tokens.Add(new Token("Store.CompanyPhoneNumber", store.CompanyPhoneNumber)); + tokens.Add(new Token("Store.CompanyVat", store.CompanyVat)); + + tokens.Add(new Token("Facebook.URL", _storeInformationSettings.FacebookLink)); + tokens.Add(new Token("Twitter.URL", _storeInformationSettings.TwitterLink)); + tokens.Add(new Token("YouTube.URL", _storeInformationSettings.YoutubeLink)); + tokens.Add(new Token("GooglePlus.URL", _storeInformationSettings.GooglePlusLink)); + + //event notification + _eventPublisher.EntityTokensAdded(store, tokens); + } + + /// + /// Add order tokens + /// + /// List of already added tokens + /// + /// Language identifier + /// Vendor identifier + public virtual void AddOrderTokens(IList tokens, Order order, int languageId, int vendorId = 0) + { + tokens.Add(new Token("Order.OrderNumber", order.CustomOrderNumber)); + + 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) : string.Empty)); + 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) : string.Empty)); + tokens.Add(new Token("Order.BillingCustomAttributes", _addressAttributeFormatter.FormatAttributes(order.BillingAddress.CustomAttributes), true)); + + tokens.Add(new Token("Order.Shippable", !string.IsNullOrEmpty(order.ShippingMethod))); + tokens.Add(new Token("Order.ShippingMethod", order.ShippingMethod)); + tokens.Add(new Token("Order.ShippingFirstName", order.ShippingAddress != null ? order.ShippingAddress.FirstName : string.Empty)); + tokens.Add(new Token("Order.ShippingLastName", order.ShippingAddress != null ? order.ShippingAddress.LastName : string.Empty)); + tokens.Add(new Token("Order.ShippingPhoneNumber", order.ShippingAddress != null ? order.ShippingAddress.PhoneNumber : string.Empty)); + tokens.Add(new Token("Order.ShippingEmail", order.ShippingAddress != null ? order.ShippingAddress.Email : string.Empty)); + tokens.Add(new Token("Order.ShippingFaxNumber", order.ShippingAddress != null ? order.ShippingAddress.FaxNumber : string.Empty)); + tokens.Add(new Token("Order.ShippingCompany", order.ShippingAddress != null ? order.ShippingAddress.Company : string.Empty)); + tokens.Add(new Token("Order.ShippingAddress1", order.ShippingAddress != null ? order.ShippingAddress.Address1 : string.Empty)); + tokens.Add(new Token("Order.ShippingAddress2", order.ShippingAddress != null ? order.ShippingAddress.Address2 : string.Empty)); + tokens.Add(new Token("Order.ShippingCity", order.ShippingAddress != null ? order.ShippingAddress.City : string.Empty)); + tokens.Add(new Token("Order.ShippingStateProvince", order.ShippingAddress != null && order.ShippingAddress.StateProvince != null ? order.ShippingAddress.StateProvince.GetLocalized(x => x.Name) : string.Empty)); + tokens.Add(new Token("Order.ShippingZipPostalCode", order.ShippingAddress != null ? order.ShippingAddress.ZipPostalCode : string.Empty)); + tokens.Add(new Token("Order.ShippingCountry", order.ShippingAddress != null && order.ShippingAddress.Country != null ? order.ShippingAddress.Country.GetLocalized(x => x.Name) : string.Empty)); + tokens.Add(new Token("Order.ShippingCustomAttributes", _addressAttributeFormatter.FormatAttributes(order.ShippingAddress != null ? order.ShippingAddress.CustomAttributes : string.Empty), true)); + + var paymentMethod = _paymentService.LoadPaymentMethodBySystemName(order.PaymentMethodSystemName); + var paymentMethodName = paymentMethod != null ? paymentMethod.GetLocalizedFriendlyName(_localizationService, _workContext.WorkingLanguage.Id) : order.PaymentMethodSystemName; + tokens.Add(new Token("Order.PaymentMethod", paymentMethodName)); + tokens.Add(new Token("Order.VatNumber", order.VatNumber)); + var sbCustomValues = new StringBuilder(); + var customValues = order.DeserializeCustomValues(); + if (customValues != null) + { + foreach (var item in customValues) + { + sbCustomValues.AppendFormat("{0}: {1}", HttpUtility.HtmlEncode(item.Key), HttpUtility.HtmlEncode(item.Value != null ? item.Value.ToString() : string.Empty)); + sbCustomValues.Append("
    "); + } + } + tokens.Add(new Token("Order.CustomValues", sbCustomValues.ToString(), true)); + + + + tokens.Add(new Token("Order.Product(s)", ProductListToHtmlTable(order, languageId, vendorId), 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)); + tokens.Add(new Token("Order.CreatedOn", createdOn.ToString("D", new CultureInfo(language.LanguageCulture)))); + } + else + { + tokens.Add(new Token("Order.CreatedOn", order.CreatedOnUtc.ToString("D"))); + } + + //TODO add a method for getting URL (use routing because it handles all SEO friendly URLs) + tokens.Add(new Token("Order.OrderURLForCustomer", string.Format("{0}orderdetails/{1}", GetStoreUrl(order.StoreId), order.Id), true)); + + //event notification + _eventPublisher.EntityTokensAdded(order, tokens); + } + + /// + /// Add refunded order tokens + /// + /// List of already added tokens + /// Order + /// Refunded amount of order + public virtual void AddOrderRefundedTokens(IList tokens, Order order, decimal refundedAmount) + { + //should we convert it to customer currency? + //most probably, no. It can cause some rounding or legal issues + //furthermore, exchange rate could be changed + //so let's display it the primary store currency + + var primaryStoreCurrencyCode = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId).CurrencyCode; + var refundedAmountStr = _priceFormatter.FormatPrice(refundedAmount, true, primaryStoreCurrencyCode, false, _workContext.WorkingLanguage); + + tokens.Add(new Token("Order.AmountRefunded", refundedAmountStr)); + + //event notification + _eventPublisher.EntityTokensAdded(order, tokens); + } + + /// + /// Add shipment tokens + /// + /// List of already added tokens + /// Shipment item + /// Language identifier + public virtual void AddShipmentTokens(IList tokens, Shipment shipment, int languageId) + { + tokens.Add(new Token("Shipment.ShipmentNumber", shipment.Id)); + tokens.Add(new Token("Shipment.TrackingNumber", shipment.TrackingNumber)); + var trackingNumberUrl = string.Empty; + if (!String.IsNullOrEmpty(shipment.TrackingNumber)) + { + //we cannot inject IShippingService into constructor because it'll cause circular references. + //that's why we resolve it here this way + var shipmentTracker = shipment.GetShipmentTracker(EngineContext.Current.Resolve(), _shippingSettings); + if (shipmentTracker != null) + trackingNumberUrl = shipmentTracker.GetUrl(shipment.TrackingNumber); + } + tokens.Add(new Token("Shipment.TrackingNumberURL", trackingNumberUrl, true)); + tokens.Add(new Token("Shipment.Product(s)", ProductListToHtmlTable(shipment, languageId), true)); + tokens.Add(new Token("Shipment.URLForCustomer", string.Format("{0}orderdetails/shipment/{1}", GetStoreUrl(shipment.Order.StoreId), shipment.Id), true)); + + //event notification + _eventPublisher.EntityTokensAdded(shipment, tokens); + } + + /// + /// Add order note tokens + /// + /// List of already added tokens + /// Order note + public virtual void AddOrderNoteTokens(IList tokens, OrderNote orderNote) + { + tokens.Add(new Token("Order.NewNoteText", orderNote.FormatOrderNoteText(), true)); + tokens.Add(new Token("Order.OrderNoteAttachmentUrl", string.Format("{0}download/ordernotefile/{1}", GetStoreUrl(orderNote.Order.StoreId), orderNote.Id), true)); + + //event notification + _eventPublisher.EntityTokensAdded(orderNote, tokens); + } + + /// + /// Add recurring payment tokens + /// + /// List of already added tokens + /// Recurring payment + public virtual void AddRecurringPaymentTokens(IList tokens, RecurringPayment recurringPayment) + { + tokens.Add(new Token("RecurringPayment.ID", recurringPayment.Id)); + tokens.Add(new Token("RecurringPayment.CancelAfterFailedPayment", + recurringPayment.LastPaymentFailed && _paymentSettings.CancelRecurringPaymentsAfterFailedPayment)); + if (recurringPayment.InitialOrder != null) + tokens.Add(new Token("RecurringPayment.RecurringPaymentType", _paymentService.GetRecurringPaymentType(recurringPayment.InitialOrder.PaymentMethodSystemName).ToString())); + + //event notification + _eventPublisher.EntityTokensAdded(recurringPayment, tokens); + } + + /// + /// Add return request tokens + /// + /// List of already added tokens + /// Return request + /// Order item + public virtual void AddReturnRequestTokens(IList tokens, ReturnRequest returnRequest, OrderItem orderItem) + { + tokens.Add(new Token("ReturnRequest.CustomNumber", returnRequest.CustomNumber)); + tokens.Add(new Token("ReturnRequest.OrderId", orderItem.OrderId)); + tokens.Add(new Token("ReturnRequest.Product.Quantity", returnRequest.Quantity)); + tokens.Add(new Token("ReturnRequest.Product.Name", orderItem.Product.Name)); + tokens.Add(new Token("ReturnRequest.Reason", returnRequest.ReasonForReturn)); + tokens.Add(new Token("ReturnRequest.RequestedAction", returnRequest.RequestedAction)); + tokens.Add(new Token("ReturnRequest.CustomerComment", HtmlHelper.FormatText(returnRequest.CustomerComments, false, true, false, false, false, false), true)); + tokens.Add(new Token("ReturnRequest.StaffNotes", HtmlHelper.FormatText(returnRequest.StaffNotes, false, true, false, false, false, false), true)); + tokens.Add(new Token("ReturnRequest.Status", returnRequest.ReturnRequestStatus.GetLocalizedEnum(_localizationService, _workContext))); + + //event notification + _eventPublisher.EntityTokensAdded(returnRequest, tokens); + } + + /// + /// Add gift card tokens + /// + /// List of already added tokens + /// Gift card + public virtual void AddGiftCardTokens(IList tokens, GiftCard giftCard) + { + tokens.Add(new Token("GiftCard.SenderName", giftCard.SenderName)); + tokens.Add(new Token("GiftCard.SenderEmail",giftCard.SenderEmail)); + tokens.Add(new Token("GiftCard.RecipientName", giftCard.RecipientName)); + tokens.Add(new Token("GiftCard.RecipientEmail", giftCard.RecipientEmail)); + tokens.Add(new Token("GiftCard.Amount", _priceFormatter.FormatPrice(giftCard.Amount, true, false))); + tokens.Add(new Token("GiftCard.CouponCode", giftCard.GiftCardCouponCode)); + + var giftCardMesage = !String.IsNullOrWhiteSpace(giftCard.Message) ? + HtmlHelper.FormatText(giftCard.Message, false, true, false, false, false, false) : string.Empty; + + tokens.Add(new Token("GiftCard.Message", giftCardMesage, true)); + + //event notification + _eventPublisher.EntityTokensAdded(giftCard, tokens); + } + + /// + /// Add customer tokens + /// + /// List of already added tokens + /// Customer + public virtual void AddCustomerTokens(IList tokens, Customer customer) + { + 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.FirstName", customer.GetAttribute(SystemCustomerAttributeNames.FirstName))); + tokens.Add(new Token("Customer.LastName", customer.GetAttribute(SystemCustomerAttributeNames.LastName))); + tokens.Add(new Token("Customer.VatNumber", customer.GetAttribute(SystemCustomerAttributeNames.VatNumber))); + tokens.Add(new Token("Customer.VatNumberStatus", ((VatNumberStatus)customer.GetAttribute(SystemCustomerAttributeNames.VatNumberStatusId)).ToString())); + + var customAttributesXml = customer.GetAttribute(SystemCustomerAttributeNames.CustomCustomerAttributes); + tokens.Add(new Token("Customer.CustomAttributes", _customerAttributeFormatter.FormatAttributes(customAttributesXml), true)); + + + //note: we do not use SEO friendly URLS because we can get errors caused by having .(dot) in the URL (from the email address) + //TODO add a method for getting URL (use routing because it handles all SEO friendly URLs) + var passwordRecoveryUrl = string.Format("{0}passwordrecovery/confirm?token={1}&email={2}", GetStoreUrl(), customer.GetAttribute(SystemCustomerAttributeNames.PasswordRecoveryToken), HttpUtility.UrlEncode(customer.Email)); + var accountActivationUrl = string.Format("{0}customer/activation?token={1}&email={2}", GetStoreUrl(), customer.GetAttribute(SystemCustomerAttributeNames.AccountActivationToken), HttpUtility.UrlEncode(customer.Email)); + var emailRevalidationUrl = string.Format("{0}customer/revalidateemail?token={1}&email={2}", GetStoreUrl(), customer.GetAttribute(SystemCustomerAttributeNames.EmailRevalidationToken), HttpUtility.UrlEncode(customer.Email)); + var wishlistUrl = string.Format("{0}wishlist/{1}", GetStoreUrl(), customer.CustomerGuid); + + tokens.Add(new Token("Customer.PasswordRecoveryURL", passwordRecoveryUrl, true)); + tokens.Add(new Token("Customer.AccountActivationURL", accountActivationUrl, true)); + tokens.Add(new Token("Customer.EmailRevalidationURL", emailRevalidationUrl, true)); + tokens.Add(new Token("Wishlist.URLForCustomer", wishlistUrl, true)); + + //event notification + _eventPublisher.EntityTokensAdded(customer, tokens); + } + + /// + /// Add vendor tokens + /// + /// List of already added tokens + /// Vendor + public virtual void AddVendorTokens(IList tokens, Vendor vendor) + { + tokens.Add(new Token("Vendor.Name", vendor.Name)); + tokens.Add(new Token("Vendor.Email", vendor.Email)); + + //event notification + _eventPublisher.EntityTokensAdded(vendor, tokens); + } + + /// + /// Add newsletter subscription tokens + /// + /// List of already added tokens + /// Newsletter subscription + public virtual void AddNewsLetterSubscriptionTokens(IList tokens, NewsLetterSubscription subscription) + { + tokens.Add(new Token("NewsLetterSubscription.Email", subscription.Email)); + + + const string urlFormat = "{0}newsletter/subscriptionactivation/{1}/{2}"; + + var activationUrl = String.Format(urlFormat, GetStoreUrl(), subscription.NewsLetterSubscriptionGuid, "true"); + tokens.Add(new Token("NewsLetterSubscription.ActivationUrl", activationUrl, true)); + + var deActivationUrl = String.Format(urlFormat, GetStoreUrl(), subscription.NewsLetterSubscriptionGuid, "false"); + tokens.Add(new Token("NewsLetterSubscription.DeactivationUrl", deActivationUrl, true)); + + //event notification + _eventPublisher.EntityTokensAdded(subscription, tokens); + } + + /// + /// Add product review tokens + /// + /// List of already added tokens + /// Product review + public virtual void AddProductReviewTokens(IList tokens, ProductReview productReview) + { + tokens.Add(new Token("ProductReview.ProductName", productReview.Product.Name)); + + //event notification + _eventPublisher.EntityTokensAdded(productReview, tokens); + } + + /// + /// Add blog comment tokens + /// + /// List of already added tokens + /// Blog post comment + public virtual void AddBlogCommentTokens(IList tokens, BlogComment blogComment) + { + tokens.Add(new Token("BlogComment.BlogPostTitle", blogComment.BlogPost.Title)); + + //event notification + _eventPublisher.EntityTokensAdded(blogComment, tokens); + } + + /// + /// Add news comment tokens + /// + /// List of already added tokens + /// News comment + public virtual void AddNewsCommentTokens(IList tokens, NewsComment newsComment) + { + tokens.Add(new Token("NewsComment.NewsTitle", newsComment.NewsItem.Title)); + + //event notification + _eventPublisher.EntityTokensAdded(newsComment, tokens); + } + + /// + /// Add product tokens + /// + /// List of already added tokens + /// Product + /// Language identifier + public virtual void AddProductTokens(IList tokens, Product product, int languageId) + { + tokens.Add(new Token("Product.ID", product.Id)); + 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.SKU", product.Sku)); + tokens.Add(new Token("Product.StockQuantity", product.GetTotalStockQuantity())); + + //TODO add a method for getting URL (use routing because it handles all SEO friendly URLs) + var productUrl = string.Format("{0}{1}", GetStoreUrl(), product.GetSeName()); + tokens.Add(new Token("Product.ProductURLForCustomer", productUrl, true)); + + //event notification + _eventPublisher.EntityTokensAdded(product, tokens); + } + + /// + /// Add product attribute combination tokens + /// + /// List of already added tokens + /// Product attribute combination + /// Language identifier + public virtual void AddAttributeCombinationTokens(IList tokens, ProductAttributeCombination combination, int languageId) + { + //attributes + //we cannot inject IProductAttributeFormatter into constructor because it'll cause circular references. + //that's why we resolve it here this way + var productAttributeFormatter = EngineContext.Current.Resolve(); + string attributes = productAttributeFormatter.FormatAttributes(combination.Product, + combination.AttributesXml, + _workContext.CurrentCustomer, + renderPrices: false); + + + + tokens.Add(new Token("AttributeCombination.Formatted", attributes, true)); + tokens.Add(new Token("AttributeCombination.SKU", combination.Product.FormatSku(combination.AttributesXml, _productAttributeParser))); + tokens.Add(new Token("AttributeCombination.StockQuantity", combination.StockQuantity)); + + //event notification + _eventPublisher.EntityTokensAdded(combination, tokens); + } + + /// + /// Add forum topic tokens + /// + /// List of already added tokens + /// Forum topic + /// Friendly (starts with 1) forum topic page to use for URL generation + /// Forum post identifier + public virtual void AddForumTopicTokens(IList tokens, ForumTopic forumTopic, + int? friendlyForumTopicPageIndex = null, int? appendedPostIdentifierAnchor = null) + { + //TODO add a method for getting URL (use routing because it handles all SEO friendly URLs) + string topicUrl; + if (friendlyForumTopicPageIndex.HasValue && friendlyForumTopicPageIndex.Value > 1) + topicUrl = string.Format("{0}boards/topic/{1}/{2}/page/{3}", GetStoreUrl(), forumTopic.Id, forumTopic.GetSeName(), friendlyForumTopicPageIndex.Value); + else + topicUrl = string.Format("{0}boards/topic/{1}/{2}", GetStoreUrl(), forumTopic.Id, forumTopic.GetSeName()); + if (appendedPostIdentifierAnchor.HasValue && appendedPostIdentifierAnchor.Value > 0) + topicUrl = string.Format("{0}#{1}", topicUrl, appendedPostIdentifierAnchor.Value); + tokens.Add(new Token("Forums.TopicURL", topicUrl, true)); + tokens.Add(new Token("Forums.TopicName", forumTopic.Subject)); + + //event notification + _eventPublisher.EntityTokensAdded(forumTopic, tokens); + } + + /// + /// Add forum post tokens + /// + /// List of already added tokens + /// Forum post + public virtual void AddForumPostTokens(IList tokens, ForumPost forumPost) + { + tokens.Add(new Token("Forums.PostAuthor", forumPost.Customer.FormatUserName())); + tokens.Add(new Token("Forums.PostBody", forumPost.FormatPostText(), true)); + + //event notification + _eventPublisher.EntityTokensAdded(forumPost, tokens); + } + + /// + /// Add forum tokens + /// + /// List of already added tokens + /// Forum + public virtual void AddForumTokens(IList tokens, Forum forum) + { + //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}", GetStoreUrl(), forum.Id, forum.GetSeName()); + tokens.Add(new Token("Forums.ForumURL", forumUrl, true)); + tokens.Add(new Token("Forums.ForumName", forum.Name)); + + //event notification + _eventPublisher.EntityTokensAdded(forum, tokens); + } + + /// + /// Add private message tokens + /// + /// List of already added tokens + /// Private message + public virtual void AddPrivateMessageTokens(IList tokens, PrivateMessage privateMessage) + { + tokens.Add(new Token("PrivateMessage.Subject", privateMessage.Subject)); + tokens.Add(new Token("PrivateMessage.Text", privateMessage.FormatPrivateMessageText(), true)); + + //event notification + _eventPublisher.EntityTokensAdded(privateMessage, tokens); + } + + /// + /// Add tokens of BackInStock subscription + /// + /// List of already added tokens + /// BackInStock subscription + public virtual void AddBackInStockTokens(IList tokens, BackInStockSubscription subscription) + { + tokens.Add(new Token("BackInStockSubscription.ProductName", subscription.Product.Name)); + //TODO add a method for getting URL (use routing because it handles all SEO friendly URLs) + var productUrl = string.Format("{0}{1}", GetStoreUrl(subscription.StoreId), subscription.Product.GetSeName()); + tokens.Add(new Token("BackInStockSubscription.ProductUrl", productUrl, true)); + + //event notification + _eventPublisher.EntityTokensAdded(subscription, tokens); + } + + /// + /// Get collection of allowed (supported) message tokens for campaigns + /// + /// Collection of allowed (supported) message tokens for campaigns + public virtual IEnumerable GetListOfCampaignAllowedTokens() + { + var additionTokens = new CampaignAdditionTokensAddedEvent(); + _eventPublisher.Publish(additionTokens); + + var allowedTokens = GetListOfAllowedTokens(new[] { TokenGroupNames.StoreTokens, TokenGroupNames.SubscriptionTokens }).ToList(); + allowedTokens.AddRange(additionTokens.AdditionTokens); + + return allowedTokens.Distinct(); + } + + /// + /// Get collection of allowed (supported) message tokens + /// + /// Collection of token groups; pass null to get all available tokens + /// Collection of allowed message tokens + public virtual IEnumerable GetListOfAllowedTokens(IEnumerable tokenGroups = null) + { + var additionTokens = new AdditionTokensAddedEvent(); + _eventPublisher.Publish(additionTokens); + + var allowedTokens = AllowedTokens.Where(x => tokenGroups == null || tokenGroups.Contains(x.Key)) + .SelectMany(x => x.Value).ToList(); + + allowedTokens.AddRange(additionTokens.AdditionTokens); + + return allowedTokens.Distinct(); + } + + #endregion + } +} diff --git a/src/Libraries/Nop.Services/Nop.Services.csproj b/src/Libraries/Nop.Services/Nop.Services.csproj index c5ff968b455..158a4ddb16c 100644 --- a/src/Libraries/Nop.Services/Nop.Services.csproj +++ b/src/Libraries/Nop.Services/Nop.Services.csproj @@ -293,6 +293,7 @@ + diff --git a/src/Libraries/Nop.Services/Orders/GiftCardService.cs b/src/Libraries/Nop.Services/Orders/GiftCardService.cs index d8d2e1f3f8b..596927044fd 100644 --- a/src/Libraries/Nop.Services/Orders/GiftCardService.cs +++ b/src/Libraries/Nop.Services/Orders/GiftCardService.cs @@ -1,198 +1,198 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Nop.Core; -using Nop.Core.Data; -using Nop.Core.Domain.Customers; -using Nop.Core.Domain.Orders; -using Nop.Services.Customers; -using Nop.Services.Events; - -namespace Nop.Services.Orders -{ - /// - /// Gift card service - /// - public partial class GiftCardService : IGiftCardService - { - #region Fields - - private readonly IRepository _giftCardRepository; - private readonly IEventPublisher _eventPublisher; - - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Gift card context - /// Event published - public GiftCardService(IRepository giftCardRepository, IEventPublisher eventPublisher) - { - _giftCardRepository = giftCardRepository; - _eventPublisher = eventPublisher; - } - - #endregion - - #region Methods - - /// - /// Deletes a gift card - /// - /// Gift card - public virtual void DeleteGiftCard(GiftCard giftCard) - { - if (giftCard == null) - throw new ArgumentNullException("giftCard"); - - _giftCardRepository.Delete(giftCard); - - //event notification - _eventPublisher.EntityDeleted(giftCard); - } - - /// - /// Gets a gift card - /// - /// Gift card identifier - /// Gift card entry - public virtual GiftCard GetGiftCardById(int giftCardId) - { - if (giftCardId == 0) - return null; - - return _giftCardRepository.GetById(giftCardId); - } - - /// - /// Gets all gift cards - /// - /// Associated order ID; null to load all records - /// The order ID in which the gift card was used; null to load all records - /// Created date from (UTC); null to load all records - /// Created date to (UTC); null to load all records - /// Value indicating whether gift card is activated; null to load all records - /// Gift card coupon code; nullto load all records - /// Recipient name; null to load all records - /// Page index - /// Page size - /// Gift cards - public virtual IPagedList GetAllGiftCards(int? purchasedWithOrderId = null, int? usedWithOrderId = null, - DateTime? createdFromUtc = null, DateTime? createdToUtc = null, - bool? isGiftCardActivated = null, string giftCardCouponCode = null, - string recipientName = null, - int pageIndex = 0, int pageSize = int.MaxValue) - { - var query = _giftCardRepository.Table; - if (purchasedWithOrderId.HasValue) - query = query.Where(gc => gc.PurchasedWithOrderItem != null && gc.PurchasedWithOrderItem.OrderId == purchasedWithOrderId.Value); - if (usedWithOrderId.HasValue) - query = query.Where(gc => gc.GiftCardUsageHistory.Any(history => history.UsedWithOrderId == usedWithOrderId)); - if (createdFromUtc.HasValue) - query = query.Where(gc => createdFromUtc.Value <= gc.CreatedOnUtc); - if (createdToUtc.HasValue) - query = query.Where(gc => createdToUtc.Value >= gc.CreatedOnUtc); - if (isGiftCardActivated.HasValue) - query = query.Where(gc => gc.IsGiftCardActivated == isGiftCardActivated.Value); - if (!String.IsNullOrEmpty(giftCardCouponCode)) - query = query.Where(gc => gc.GiftCardCouponCode == giftCardCouponCode); - if (!String.IsNullOrWhiteSpace(recipientName)) - query = query.Where(c => c.RecipientName.Contains(recipientName)); - query = query.OrderByDescending(gc => gc.CreatedOnUtc); - - var giftCards = new PagedList(query, pageIndex, pageSize); - return giftCards; - } - - /// - /// Inserts a gift card - /// - /// Gift card - public virtual void InsertGiftCard(GiftCard giftCard) - { - if (giftCard == null) - throw new ArgumentNullException("giftCard"); - - _giftCardRepository.Insert(giftCard); - - //event notification - _eventPublisher.EntityInserted(giftCard); - } - - /// - /// Updates the gift card - /// - /// Gift card - public virtual void UpdateGiftCard(GiftCard giftCard) - { - if (giftCard == null) - throw new ArgumentNullException("giftCard"); - - _giftCardRepository.Update(giftCard); - - //event notification - _eventPublisher.EntityUpdated(giftCard); - } - - /// - /// Gets gift cards by 'PurchasedWithOrderItemId' - /// - /// Purchased with order item identifier - /// Gift card entries - public virtual IList GetGiftCardsByPurchasedWithOrderItemId(int purchasedWithOrderItemId) - { - if (purchasedWithOrderItemId == 0) - return new List(); - - var query = _giftCardRepository.Table; - query = query.Where(gc => gc.PurchasedWithOrderItemId.HasValue && gc.PurchasedWithOrderItemId.Value == purchasedWithOrderItemId); - query = query.OrderBy(gc => gc.Id); - - var giftCards = query.ToList(); - return giftCards; - } - - /// - /// Get active gift cards that are applied by a customer - /// - /// Customer - /// Active gift cards - public virtual IList GetActiveGiftCardsAppliedByCustomer(Customer customer) - { - var result = new List(); - if (customer == null) - return result; - - string[] couponCodes = customer.ParseAppliedGiftCardCouponCodes(); - foreach (var couponCode in couponCodes) - { - var giftCards = GetAllGiftCards(isGiftCardActivated: true, giftCardCouponCode: couponCode); - foreach (var gc in giftCards) - { - if (gc.IsGiftCardValid()) - result.Add(gc); - } - } - - return result; - } - - /// - /// Generate new gift card code - /// - /// Result - public virtual string GenerateGiftCardCode() - { - int length = 13; - string result = Guid.NewGuid().ToString(); - if (result.Length > length) - result = result.Substring(0, length); - return result; - } - - #endregion - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using Nop.Core; +using Nop.Core.Data; +using Nop.Core.Domain.Customers; +using Nop.Core.Domain.Orders; +using Nop.Services.Customers; +using Nop.Services.Events; + +namespace Nop.Services.Orders +{ + /// + /// Gift card service + /// + public partial class GiftCardService : IGiftCardService + { + #region Fields + + private readonly IRepository _giftCardRepository; + private readonly IEventPublisher _eventPublisher; + + #endregion + + #region Ctor + + /// + /// Ctor + /// + /// Gift card context + /// Event published + public GiftCardService(IRepository giftCardRepository, IEventPublisher eventPublisher) + { + _giftCardRepository = giftCardRepository; + _eventPublisher = eventPublisher; + } + + #endregion + + #region Methods + + /// + /// Deletes a gift card + /// + /// Gift card + public virtual void DeleteGiftCard(GiftCard giftCard) + { + if (giftCard == null) + throw new ArgumentNullException("giftCard"); + + _giftCardRepository.Delete(giftCard); + + //event notification + _eventPublisher.EntityDeleted(giftCard); + } + + /// + /// Gets a gift card + /// + /// Gift card identifier + /// Gift card entry + public virtual GiftCard GetGiftCardById(int giftCardId) + { + if (giftCardId == 0) + return null; + + return _giftCardRepository.GetById(giftCardId); + } + + /// + /// Gets all gift cards + /// + /// Associated order ID; null to load all records + /// The order ID in which the gift card was used; null to load all records + /// Created date from (UTC); null to load all records + /// Created date to (UTC); null to load all records + /// Value indicating whether gift card is activated; null to load all records + /// Gift card coupon code; nullto load all records + /// Recipient name; null to load all records + /// Page index + /// Page size + /// Gift cards + public virtual IPagedList GetAllGiftCards(int? purchasedWithOrderId = null, int? usedWithOrderId = null, + DateTime? createdFromUtc = null, DateTime? createdToUtc = null, + bool? isGiftCardActivated = null, string giftCardCouponCode = null, + string recipientName = null, + int pageIndex = 0, int pageSize = int.MaxValue) + { + var query = _giftCardRepository.Table; + if (purchasedWithOrderId.HasValue) + query = query.Where(gc => gc.PurchasedWithOrderItem != null && gc.PurchasedWithOrderItem.OrderId == purchasedWithOrderId.Value); + if (usedWithOrderId.HasValue) + query = query.Where(gc => gc.GiftCardUsageHistory.Any(history => history.UsedWithOrderId == usedWithOrderId)); + if (createdFromUtc.HasValue) + query = query.Where(gc => createdFromUtc.Value <= gc.CreatedOnUtc); + if (createdToUtc.HasValue) + query = query.Where(gc => createdToUtc.Value >= gc.CreatedOnUtc); + if (isGiftCardActivated.HasValue) + query = query.Where(gc => gc.IsGiftCardActivated == isGiftCardActivated.Value); + if (!String.IsNullOrEmpty(giftCardCouponCode)) + query = query.Where(gc => gc.GiftCardCouponCode == giftCardCouponCode); + if (!String.IsNullOrWhiteSpace(recipientName)) + query = query.Where(c => c.RecipientName.Contains(recipientName)); + query = query.OrderByDescending(gc => gc.CreatedOnUtc); + + var giftCards = new PagedList(query, pageIndex, pageSize); + return giftCards; + } + + /// + /// Inserts a gift card + /// + /// Gift card + public virtual void InsertGiftCard(GiftCard giftCard) + { + if (giftCard == null) + throw new ArgumentNullException("giftCard"); + + _giftCardRepository.Insert(giftCard); + + //event notification + _eventPublisher.EntityInserted(giftCard); + } + + /// + /// Updates the gift card + /// + /// Gift card + public virtual void UpdateGiftCard(GiftCard giftCard) + { + if (giftCard == null) + throw new ArgumentNullException("giftCard"); + + _giftCardRepository.Update(giftCard); + + //event notification + _eventPublisher.EntityUpdated(giftCard); + } + + /// + /// Gets gift cards by 'PurchasedWithOrderItemId' + /// + /// Purchased with order item identifier + /// Gift card entries + public virtual IList GetGiftCardsByPurchasedWithOrderItemId(int purchasedWithOrderItemId) + { + if (purchasedWithOrderItemId == 0) + return new List(); + + var query = _giftCardRepository.Table; + query = query.Where(gc => gc.PurchasedWithOrderItemId.HasValue && gc.PurchasedWithOrderItemId.Value == purchasedWithOrderItemId); + query = query.OrderBy(gc => gc.Id); + + var giftCards = query.ToList(); + return giftCards; + } + + /// + /// Get active gift cards that are applied by a customer + /// + /// Customer + /// Active gift cards + public virtual IList GetActiveGiftCardsAppliedByCustomer(Customer customer) + { + var result = new List(); + if (customer == null) + return result; + + string[] couponCodes = customer.ParseAppliedGiftCardCouponCodes(); + foreach (var couponCode in couponCodes) + { + var giftCards = GetAllGiftCards(isGiftCardActivated: true, giftCardCouponCode: couponCode); + foreach (var gc in giftCards) + { + if (gc.IsGiftCardValid()) + result.Add(gc); + } + } + + return result; + } + + /// + /// Generate new gift card code + /// + /// Result + public virtual string GenerateGiftCardCode() + { + int length = 13; + string result = Guid.NewGuid().ToString(); + if (result.Length > length) + result = result.Substring(0, length); + return result; + } + + #endregion + } +} diff --git a/src/Libraries/Nop.Services/Orders/IOrderTotalCalculationService.cs b/src/Libraries/Nop.Services/Orders/IOrderTotalCalculationService.cs index 38c2b427dfc..ef53f457ac0 100644 --- a/src/Libraries/Nop.Services/Orders/IOrderTotalCalculationService.cs +++ b/src/Libraries/Nop.Services/Orders/IOrderTotalCalculationService.cs @@ -1,228 +1,205 @@ -using System.Collections.Generic; -using Nop.Core.Domain.Customers; -using Nop.Core.Domain.Orders; -using Nop.Services.Discounts; -using Nop.Services.Tax; - -namespace Nop.Services.Orders -{ - /// - /// Order service interface - /// - public partial interface IOrderTotalCalculationService - { - /// - /// Gets shopping cart subtotal - /// - /// Cart - /// A value indicating whether submitted prices do include tax - /// Applied discount amount - /// Applied discounts - /// Sub total (without discount) - /// Sub total (with discount) - void GetShoppingCartSubTotal(IList cart, - bool includingTax, - out decimal discountAmount, out List appliedDiscounts, - out decimal subTotalWithoutDiscount, out decimal subTotalWithDiscount); - - /// - /// Gets shopping cart subtotal - /// - /// Cart - /// A value indicating whether submitted prices do include tax - /// Applied discount amount - /// Applied discounts - /// Sub total (without discount) - /// Sub total (with discount) - /// Tax rates summary (of order sub total) - void GetShoppingCartSubTotal(IList cart, - bool includingTax, - out decimal discountAmount, out List appliedDiscounts, - out decimal subTotalWithoutDiscount, out decimal subTotalWithDiscount, - out TaxSummary taxSummary); - - - - - - /// - /// Adjust shipping rate (free shipping, additional charges, discounts) - /// - /// Shipping rate to adjust - /// Cart - /// Applied discounts - /// Adjusted shipping rate - decimal AdjustShippingRate(decimal shippingRate, - IList cart, out List appliedDiscounts); - - /// - /// Gets shopping cart additional shipping charge - /// - /// Cart - /// Additional shipping charge - decimal GetShoppingCartAdditionalShippingCharge(IList cart); - - /// - /// Gets a value indicating whether shipping is free - /// - /// Cart - /// Subtotal amount; pass null to calculate subtotal - /// A value indicating whether shipping is free - bool IsFreeShipping(IList cart, decimal? subTotal = null); - - /// - /// Gets shopping cart shipping total - /// - /// Cart - /// Shipping total - decimal? GetShoppingCartShippingTotal(IList cart); - - /// - /// Gets shopping cart shipping total - /// - /// Cart - /// A value indicating whether submitted prices do include tax - /// Shipping total - decimal? GetShoppingCartShippingTotal(IList cart, bool includingTax); - - /// - /// Gets shopping cart shipping total - /// - /// Cart - /// A value indicating whether submitted prices do include tax - /// Applied tax rate - /// Shipping total - decimal? GetShoppingCartShippingTotal(IList cart, bool includingTax, - out decimal taxRate); - - /// - /// Gets shopping cart shipping total - /// - /// Cart - /// A value indicating whether submitted prices do include tax - /// Applied tax rate - /// Applied discounts - /// Shipping total - decimal? GetShoppingCartShippingTotal(IList cart, bool includingTax, - out decimal taxRate, out List appliedDiscounts); - - - - - - - /// - /// Gets tax - /// - /// Shopping cart - /// Tax Summary - /// A value indicating whether we should use payment method additional fee when calculating tax - /// Tax total - decimal GetTaxTotal(IList cart, out TaxSummary taxSummary, bool usePaymentMethodAdditionalFee = true); - - /// - /// Gets tax - /// - /// Shopping cart - /// A value indicating whether submitted prices do include tax - /// Tax rates summary - /// Applied invoice discounts - /// Applied subtotal discounts - /// Applied shipping discounts - /// A value indicating whether we should use payment method additional fee when calculating tax - /// Tax total - decimal GetTaxTotal(IList cart, - bool includingTax, - out TaxSummary taxSummary, - out List appliedDiscounts, - out List subTotalAppliedDiscounts, - out List shippingAppliedDiscounts, - bool usePaymentMethodAdditionalFee = true); - - - - - - /// - /// Gets shopping cart total - /// - /// Cart - /// A value indicating reward points should be used; null to detect current choice of the customer - /// A value indicating whether we should use payment method additional fee when calculating order total - /// Shopping cart total;Null if shopping cart total couldn't be calculated now - decimal? GetShoppingCartTotal(IList cart, - bool? useRewardPoints = null, bool usePaymentMethodAdditionalFee = true); - - /// - /// Gets shopping cart total - /// - /// Cart - /// Applied discount amount - /// Applied discounts - /// Applied subtotal discounts - /// Applied shipping discounts - /// Applied gift cards - /// Reward points to redeem - /// Reward points amount in primary store currency to redeem - /// Tax summary - /// A value indicating whether submitted prices do include tax - /// A value indicating reward points should be used; null to detect current choice of the customer - /// A value indicating whether we should use payment method additional fee when calculating order total - /// Shopping cart total;Null if shopping cart total couldn't be calculated now - decimal? GetShoppingCartTotal(IList cart, - out decimal discountAmount, out List appliedDiscounts, - out List subTotalAppliedDiscounts, - out List shippingAppliedDiscounts, - out List appliedGiftCards, - out int redeemedRewardPoints, out decimal redeemedRewardPointsAmount, - out TaxSummary taxSummary, - bool includingTax, - bool? useRewardPoints = null, bool usePaymentMethodAdditionalFee = true); - - - - - /// - /// Update order totals - /// - /// Parameters for the updating order - /// Shopping cart - void UpdateOrderTotals(UpdateOrderParameters updateOrderParameters, IList restoredCart); - - /// - /// Converts existing reward points to amount - /// - /// Reward points - /// Converted value - decimal ConvertRewardPointsToAmount(int rewardPoints); - - /// - /// Converts an amount to reward points - /// - /// Amount - /// Converted value - int ConvertAmountToRewardPoints(decimal amount); - - /// - /// Gets a value indicating whether a customer has minimum amount of reward points to use (if enabled) - /// - /// Reward points to check - /// true - reward points could use; false - cannot be used. - bool CheckMinimumRewardPointsToUseRequirement(int rewardPoints); - - /// - /// Calculate how order total (maximum amount) for which reward points could be earned/reduced - /// - /// Order shipping (including tax) - /// Order total - /// Applicable order total - decimal CalculateApplicableOrderTotalForRewardPoints(decimal orderShippingInclTax, decimal orderTotal); - /// - /// Calculate how much reward points will be earned/reduced based on certain amount spent - /// - /// Customer - /// Amount (in primary store currency) - /// Number of reward points - int CalculateRewardPoints(Customer customer, decimal amount); - } -} +using System.Collections.Generic; +using Nop.Core.Domain.Customers; +using Nop.Core.Domain.Orders; +using Nop.Services.Discounts; +using Nop.Services.Tax; + +namespace Nop.Services.Orders +{ + /// + /// Order service interface + /// + public partial interface IOrderTotalCalculationService + { + /// + /// Gets shopping cart subtotal + /// + /// Cart + /// A value indicating whether calculated amounts should include tax + /// Applied discount amount + /// Applied discounts + /// Sub total (without discount) + /// Sub total (with discount) + void GetShoppingCartSubTotal(IList cart, + bool includingTax, + out decimal discountAmount, out List appliedDiscounts, + out decimal subTotalWithoutDiscount, out decimal subTotalWithDiscount); + + /// + /// Gets shopping cart subtotal + /// + /// Cart + /// A value indicating whether calculated amounts should include tax + /// Applied discount amount + /// Applied discounts + /// Sub total (without discount) + /// Sub total (with discount) + /// Tax rates summary (of order sub total) + /// Subtotal base amount for earned reward points calculation + void GetShoppingCartSubTotal(IList cart, + bool includingTax, + out decimal discountAmount, out List appliedDiscounts, + out decimal subTotalWithoutDiscount, out decimal subTotalWithDiscount, + out TaxSummary taxSummary, + out decimal subTotalEarnedRewardPointsBaseAmount); + + + + + + /// + /// Adjust shipping rate (free shipping, additional charges, discounts) + /// + /// Shipping rate to adjust + /// Cart + /// Applied discounts + /// Adjusted shipping rate + decimal AdjustShippingRate(decimal shippingRate, + IList cart, out List appliedDiscounts); + + /// + /// Gets shopping cart additional shipping charge + /// + /// Cart + /// Additional shipping charge + decimal GetShoppingCartAdditionalShippingCharge(IList cart); + + /// + /// Gets a value indicating whether shipping is free + /// + /// Cart + /// Subtotal amount; pass null to calculate subtotal + /// A value indicating whether shipping is free + bool IsFreeShipping(IList cart, decimal? subTotal = null); + + /// + /// Gets shopping cart shipping total + /// + /// Cart + /// Shipping total + decimal? GetShoppingCartShippingTotal(IList cart); + + /// + /// Gets shopping cart shipping total + /// + /// Cart + /// A value indicating whether calculated amounts should include tax + /// Shipping total + decimal? GetShoppingCartShippingTotal(IList cart, bool includingTax); + + /// + /// Gets shopping cart shipping total + /// + /// Cart + /// A value indicating whether calculated amounts should include tax + /// Applied tax rate + /// Shipping total + decimal? GetShoppingCartShippingTotal(IList cart, bool includingTax, + out decimal taxRate); + + /// + /// Gets shopping cart shipping total + /// + /// Cart + /// A value indicating whether calculated amounts should include tax + /// Applied tax rate + /// Applied discounts + /// Shipping total + decimal? GetShoppingCartShippingTotal(IList cart, bool includingTax, + out decimal taxRate, out List appliedDiscounts); + + + + + + + /// + /// Gets tax + /// + /// Shopping cart + /// Tax Summary + /// A value indicating whether we should use payment method additional fee when calculating tax + /// Tax total + decimal GetTaxTotal(IList cart, out TaxSummary taxSummary, bool usePaymentMethodAdditionalFee = true); + + /// + /// Gets tax + /// + /// Shopping cart + /// A value indicating whether calculated amounts should include tax + /// Tax rates summary + /// Applied invoice discounts + /// Applied subtotal discounts + /// Applied shipping discounts + /// Applied gift cards + /// Taxable reward points to redeem + /// Reward points base amount for earned points + /// A value indicating whether we should use payment method additional fee when calculating tax + /// A value indicating reward points should be used; null to detect current choice of the customer + /// Only in the case of updating a stored order: give the reward points amount used in the order. + /// Tax total + decimal GetTaxTotal(IList cart, + bool includingTax, + out TaxSummary taxSummary, + out List appliedDiscounts, + out List subTotalAppliedDiscounts, + out List shippingAppliedDiscounts, + out List appliedGiftCards, + out RewardPoints redeemedRewardPoints, + out decimal earnedRewardPointsBaseAmount, + + bool usePaymentMethodAdditionalFee = true, + bool? useRewardPoints = null, + int? rewardPointsOfOrder = null); + + + + /// + /// Gets shopping cart total + /// + /// Cart + /// A value indicating reward points should be used; null to detect current choice of the customer + /// A value indicating whether we should use payment method additional fee when calculating order total + /// Shopping cart total;Null if shopping cart total couldn't be calculated now + decimal? GetShoppingCartTotal(IList cart, + bool? useRewardPoints = null, bool usePaymentMethodAdditionalFee = true); + + /// + /// Gets shopping cart total + /// + /// Cart + /// Applied discount amount + /// Applied discounts + /// Applied subtotal discounts + /// Applied shipping discounts + /// Applied gift cards + /// Array of reward points to redeem: earned, purchased, total + /// Tax summary + /// Base amount for reward points to earn + /// A value indicating whether calculated amounts should include tax + /// A value indicating reward points should be used; null to detect current choice of the customer + /// A value indicating whether we should use payment method additional fee when calculating order total + /// Shopping cart total;Null if shopping cart total couldn't be calculated now + decimal? GetShoppingCartTotal(IList cart, + out decimal discountAmount, out List appliedDiscounts, + out List subTotalAppliedDiscounts, + out List shippingAppliedDiscounts, + out List appliedGiftCards, + out RewardPoints redeemedRewardPoints, + out TaxSummary taxSummary, + out decimal earnedRewardPointsBaseAmount, + bool? includingTax = null, + bool? useRewardPoints = null, bool usePaymentMethodAdditionalFee = true); + + + + + /// + /// Update order totals + /// + /// Parameters for the updating order + /// Shopping cart + void UpdateOrderTotals(UpdateOrderParameters updateOrderParameters, IList restoredCart); + + } +} diff --git a/src/Libraries/Nop.Services/Orders/IRewardPointService.cs b/src/Libraries/Nop.Services/Orders/IRewardPointService.cs index c55cbf79957..98fa364a0d6 100644 --- a/src/Libraries/Nop.Services/Orders/IRewardPointService.cs +++ b/src/Libraries/Nop.Services/Orders/IRewardPointService.cs @@ -19,31 +19,32 @@ public partial interface IRewardPointService /// Page index /// Page size /// Reward point history records - IPagedList GetRewardPointsHistory(int customerId = 0, bool showHidden = false, + IPagedList GetRewardPointsHistory(int customerId = 0, bool showHidden = false, bool showNotActivated = false, int pageIndex = 0, int pageSize = int.MaxValue); /// - /// Add reward points history record + /// Add reward points history record. /// /// Customer - /// Number of points to add + /// Reward points to add /// Store identifier /// Message - /// the order for which points were redeemed as a payment - /// Used amount + /// The order for which points were redeemed (spent) as a payment + /// Flag that indicates if passed amounts in points are used amounts /// Date and time of activating reward points; pass null to immediately activating + /// OrderItem used to purchase reward points /// Reward points history entry identifier int AddRewardPointsHistoryEntry(Customer customer, - int points, int storeId, string message = "", - Order usedWithOrder = null, decimal usedAmount = 0M, DateTime? activatingDate = null); + RewardPoints rewardPoints, int storeId, string message = "", + Order usedWithOrder = null, bool hasUsedAmount = false, DateTime? activatingDate = null, OrderItem orderItem = null); /// /// Gets reward points balance /// /// Customer identifier /// Store identifier; pass - /// Balance - int GetRewardPointsBalance(int customerId, int storeId); + /// RewardPoints class + RewardPoints GetRewardPointsBalance(int customerId, int storeId); /// /// Gets a reward point history entry @@ -57,11 +58,49 @@ int AddRewardPointsHistoryEntry(Customer customer, /// /// Reward point history entry void DeleteRewardPointsHistoryEntry(RewardPointsHistory rewardPointsHistory); - + /// /// Updates the reward point history entry /// /// Reward point history entry void UpdateRewardPointsHistoryEntry(RewardPointsHistory rewardPointsHistory); + + /// + /// Converts existing reward points to amount + /// + /// Reward points + /// Converted value + decimal ConvertRewardPointsToAmount(int rewardPoints); + + /// + /// Converts an amount to reward points + /// + /// Amount + /// overriden exchange rate + /// Converted value + int ConvertAmountToRewardPoints(decimal amount, decimal? overridenExchangeRate = null); + + /// + /// Gets a value indicating whether a customer has minimum amount of reward points to use (if enabled) + /// + /// Reward points to check + /// true - reward points could use; false - cannot be used. + bool CheckMinimumRewardPointsToUseRequirement(int rewardPoints); + + /// + /// Calculate how much reward points will be earned/reduced based on certain amount spent + /// + /// Customer + /// Base amount (in primary store currency) for points calculation + /// Number of reward points + int CalculateRewardPoints(Customer customer, decimal amount); + + /// + /// Calculate base amount for reward points calculation + /// + /// Base amount (in primary store currency) for points calculation + /// Amount of used purchased reward points + /// base amount + decimal GetRewardPointsBaseAmount(decimal amount, decimal purchasedPointsAmount); } } diff --git a/src/Libraries/Nop.Services/Orders/OrderProcessingService.cs b/src/Libraries/Nop.Services/Orders/OrderProcessingService.cs index 8ee2672ff9a..b047155dde7 100644 --- a/src/Libraries/Nop.Services/Orders/OrderProcessingService.cs +++ b/src/Libraries/Nop.Services/Orders/OrderProcessingService.cs @@ -1,3219 +1,3306 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using Nop.Core; -using Nop.Core.Domain.Catalog; -using Nop.Core.Domain.Common; -using Nop.Core.Domain.Customers; -using Nop.Core.Domain.Directory; -using Nop.Core.Domain.Discounts; -using Nop.Core.Domain.Localization; -using Nop.Core.Domain.Logging; -using Nop.Core.Domain.Orders; -using Nop.Core.Domain.Payments; -using Nop.Core.Domain.Shipping; -using Nop.Core.Domain.Tax; -using Nop.Core.Domain.Vendors; -using Nop.Services.Affiliates; -using Nop.Services.Catalog; -using Nop.Services.Common; -using Nop.Services.Customers; -using Nop.Services.Directory; -using Nop.Services.Discounts; -using Nop.Services.Events; -using Nop.Services.Localization; -using Nop.Services.Logging; -using Nop.Services.Messages; -using Nop.Services.Payments; -using Nop.Services.Security; -using Nop.Services.Shipping; -using Nop.Services.Tax; -using Nop.Services.Vendors; -using Nop.Services.Configuration; - -namespace Nop.Services.Orders -{ - /// - /// Order processing service - /// - public partial class OrderProcessingService : IOrderProcessingService - { - #region Fields - - private readonly IOrderService _orderService; - private readonly IWebHelper _webHelper; - private readonly ILocalizationService _localizationService; - private readonly ILanguageService _languageService; - private readonly IProductService _productService; - private readonly IPaymentService _paymentService; - private readonly ILogger _logger; - private readonly IOrderTotalCalculationService _orderTotalCalculationService; - private readonly IPriceCalculationService _priceCalculationService; - private readonly IPriceFormatter _priceFormatter; - private readonly IProductAttributeParser _productAttributeParser; - private readonly IProductAttributeFormatter _productAttributeFormatter; - private readonly IGiftCardService _giftCardService; - private readonly IShoppingCartService _shoppingCartService; - private readonly ICheckoutAttributeFormatter _checkoutAttributeFormatter; - private readonly IShippingService _shippingService; - private readonly IShipmentService _shipmentService; - private readonly ITaxService _taxService; - private readonly ICustomerService _customerService; - private readonly IDiscountService _discountService; - private readonly IEncryptionService _encryptionService; - private readonly IWorkContext _workContext; - private readonly IWorkflowMessageService _workflowMessageService; - private readonly IVendorService _vendorService; - private readonly ICustomerActivityService _customerActivityService; - private readonly ICurrencyService _currencyService; - private readonly IAffiliateService _affiliateService; - private readonly IEventPublisher _eventPublisher; - private readonly IPdfService _pdfService; - private readonly IRewardPointService _rewardPointService; - private readonly IGenericAttributeService _genericAttributeService; - private readonly ICountryService _countryService; - private readonly IStateProvinceService _stateProvinceService; - - private readonly ShippingSettings _shippingSettings; - private readonly PaymentSettings _paymentSettings; - private readonly RewardPointsSettings _rewardPointsSettings; - private readonly OrderSettings _orderSettings; - private readonly TaxSettings _taxSettings; - private readonly LocalizationSettings _localizationSettings; - private readonly CurrencySettings _currencySettings; - private readonly ICustomNumberFormatter _customNumberFormatter; - - private readonly ISettingService _settingService; - - #endregion - - #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 - /// Workflow message service - /// Vendor service - /// Customer activity service - /// Currency service - /// Affiliate service - /// Event published - /// PDF service - /// Reward point service - /// Generic attribute service - /// Country service - /// Payment settings - /// Shipping settings - /// Reward points settings - /// Order settings - /// Tax settings - /// Localization settings - /// Currency settings - /// Setting service - /// Custom number formatter - public OrderProcessingService(IOrderService orderService, - IWebHelper webHelper, - ILocalizationService localizationService, - ILanguageService languageService, - IProductService productService, - IPaymentService paymentService, - ILogger logger, - IOrderTotalCalculationService orderTotalCalculationService, - IPriceCalculationService priceCalculationService, - IPriceFormatter priceFormatter, - IProductAttributeParser productAttributeParser, - IProductAttributeFormatter productAttributeFormatter, - IGiftCardService giftCardService, - IShoppingCartService shoppingCartService, - ICheckoutAttributeFormatter checkoutAttributeFormatter, - IShippingService shippingService, - IShipmentService shipmentService, - ITaxService taxService, - ICustomerService customerService, - IDiscountService discountService, - IEncryptionService encryptionService, - IWorkContext workContext, - IWorkflowMessageService workflowMessageService, - IVendorService vendorService, - ICustomerActivityService customerActivityService, - ICurrencyService currencyService, - IAffiliateService affiliateService, - IEventPublisher eventPublisher, - IPdfService pdfService, - IRewardPointService rewardPointService, - IGenericAttributeService genericAttributeService, - ICountryService countryService, - IStateProvinceService stateProvinceService, - ShippingSettings shippingSettings, - PaymentSettings paymentSettings, - RewardPointsSettings rewardPointsSettings, - OrderSettings orderSettings, - TaxSettings taxSettings, - LocalizationSettings localizationSettings, - CurrencySettings currencySettings, - ISettingService settingService, - ICustomNumberFormatter customNumberFormatter) - { - this._orderService = orderService; - this._webHelper = webHelper; - this._localizationService = localizationService; - this._languageService = languageService; - this._productService = productService; - this._paymentService = paymentService; - this._logger = logger; - this._orderTotalCalculationService = orderTotalCalculationService; - this._priceCalculationService = priceCalculationService; - this._priceFormatter = priceFormatter; - this._productAttributeParser = productAttributeParser; - this._productAttributeFormatter = productAttributeFormatter; - this._giftCardService = giftCardService; - this._shoppingCartService = shoppingCartService; - this._checkoutAttributeFormatter = checkoutAttributeFormatter; - this._workContext = workContext; - this._workflowMessageService = workflowMessageService; - this._vendorService = vendorService; - this._shippingService = shippingService; - this._shipmentService = shipmentService; - this._taxService = taxService; - this._customerService = customerService; - this._discountService = discountService; - this._encryptionService = encryptionService; - this._customerActivityService = customerActivityService; - this._currencyService = currencyService; - this._affiliateService = affiliateService; - this._eventPublisher = eventPublisher; - this._pdfService = pdfService; - this._rewardPointService = rewardPointService; - this._genericAttributeService = genericAttributeService; - this._countryService = countryService; - this._stateProvinceService = stateProvinceService; - - this._paymentSettings = paymentSettings; - this._shippingSettings = shippingSettings; - this._rewardPointsSettings = rewardPointsSettings; - this._orderSettings = orderSettings; - this._taxSettings = taxSettings; - this._localizationSettings = localizationSettings; - this._currencySettings = currencySettings; - this._settingService = settingService; - this._customNumberFormatter = customNumberFormatter; - } - - #endregion - - #region Nested classes - - protected class PlaceOrderContainter - { - public PlaceOrderContainter() - { - this.Cart = new List(); - this.AppliedDiscounts = new List(); - this.AppliedGiftCards = new List(); - } - - public Customer Customer { get; set; } - public Language CustomerLanguage { get; set; } - public int AffiliateId { get; set; } - public TaxDisplayType CustomerTaxDisplayType {get; set; } - public string CustomerCurrencyCode { get; set; } - public decimal CustomerCurrencyRate { get; set; } - - public Address BillingAddress { get; set; } - public Address ShippingAddress {get; set; } - public ShippingStatus ShippingStatus { get; set; } - public string ShippingMethodName { get; set; } - public string ShippingRateComputationMethodSystemName { get; set; } - public bool PickUpInStore { get; set; } - public Address PickupAddress { get; set; } - - public bool IsRecurringShoppingCart { get; set; } - //initial order (used with recurring payments) - public Order InitialOrder { get; set; } - - public string CheckoutAttributeDescription { get; set; } - public string CheckoutAttributesXml { get; set; } - - public IList Cart { get; set; } - public List AppliedDiscounts { get; set; } - public List AppliedGiftCards { get; set; } - - public decimal OrderSubTotalInclTax { get; set; } - public decimal OrderSubTotalExclTax { get; set; } - public decimal OrderSubTotalDiscountInclTax { get; set; } - public decimal OrderSubTotalDiscountExclTax { get; set; } - public decimal OrderShippingTotalInclTax { get; set; } - public decimal OrderShippingTotalExclTax { get; set; } - public decimal PaymentAdditionalFeeInclTax {get; set; } - public decimal PaymentAdditionalFeeExclTax { get; set; } - public decimal OrderTaxTotal {get; set; } - public string VatNumber {get; set; } - public string TaxRates {get; set; } - public decimal OrderDiscountAmount { get; set; } - public int RedeemedRewardPoints { get; set; } - public decimal RedeemedRewardPointsAmount { get; set; } - public decimal OrderTotal { get; set; } - public decimal OrderAmount { get; set; } //MF 09.12.16 - public decimal OrderAmountIncl { get; set; } //MF 09.12.16 - } - - #endregion - - #region Utilities - - /// - /// Prepare details to place an order. It also sets some properties to "processPaymentRequest" - /// - /// Process payment request - /// Details - protected virtual PlaceOrderContainter PreparePlaceOrderDetails(ProcessPaymentRequest processPaymentRequest) - { - var details = new PlaceOrderContainter(); - - //customer - details.Customer = _customerService.GetCustomerById(processPaymentRequest.CustomerId); - if (details.Customer == null) - throw new ArgumentException("Customer is not set"); - - //affiliate - var affiliate = _affiliateService.GetAffiliateById(details.Customer.AffiliateId); - if (affiliate != null && affiliate.Active && !affiliate.Deleted) - details.AffiliateId = affiliate.Id; - - //check whether customer is guest - if (details.Customer.IsGuest() && !_orderSettings.AnonymousCheckoutAllowed) - throw new NopException("Anonymous checkout is not allowed"); - - //customer currency - var currencyTmp = _currencyService.GetCurrencyById( - details.Customer.GetAttribute(SystemCustomerAttributeNames.CurrencyId, processPaymentRequest.StoreId)); - var customerCurrency = (currencyTmp != null && currencyTmp.Published) ? currencyTmp : _workContext.WorkingCurrency; - var primaryStoreCurrency = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId); - details.CustomerCurrencyCode = customerCurrency.CurrencyCode; - details.CustomerCurrencyRate = customerCurrency.Rate / primaryStoreCurrency.Rate; - - //customer language - details.CustomerLanguage = _languageService.GetLanguageById( - details.Customer.GetAttribute(SystemCustomerAttributeNames.LanguageId, processPaymentRequest.StoreId)); - if (details.CustomerLanguage == null || !details.CustomerLanguage.Published) - details.CustomerLanguage = _workContext.WorkingLanguage; - - //billing address - if (details.Customer.BillingAddress == null) - throw new NopException("Billing address is not provided"); - - if (!CommonHelper.IsValidEmail(details.Customer.BillingAddress.Email)) - throw new NopException("Email is not valid"); - - details.BillingAddress = (Address)details.Customer.BillingAddress.Clone(); - if (details.BillingAddress.Country != null && !details.BillingAddress.Country.AllowsBilling) - throw new NopException(string.Format("Country '{0}' is not allowed for billing", details.BillingAddress.Country.Name)); - - //checkout attributes - details.CheckoutAttributesXml = details.Customer.GetAttribute(SystemCustomerAttributeNames.CheckoutAttributes, processPaymentRequest.StoreId); - details.CheckoutAttributeDescription = _checkoutAttributeFormatter.FormatAttributes(details.CheckoutAttributesXml, details.Customer); - - //load shopping cart - details.Cart = details.Customer.ShoppingCartItems.Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) - .LimitPerStore(processPaymentRequest.StoreId).ToList(); - - if (!details.Cart.Any()) - throw new NopException("Cart is empty"); - - //validate the entire shopping cart - var warnings = _shoppingCartService.GetShoppingCartWarnings(details.Cart, details.CheckoutAttributesXml, true); - if (warnings.Any()) - throw new NopException(warnings.Aggregate(string.Empty, (current, next) => string.Format("{0}{1};", current, next))); - - //validate individual cart items - foreach (var sci in details.Cart) - { - var sciWarnings = _shoppingCartService.GetShoppingCartItemWarnings(details.Customer, - sci.ShoppingCartType, sci.Product, processPaymentRequest.StoreId, sci.AttributesXml, - sci.CustomerEnteredPrice, sci.RentalStartDateUtc, sci.RentalEndDateUtc, sci.Quantity, false); - if (sciWarnings.Any()) - throw new NopException(sciWarnings.Aggregate(string.Empty, (current, next) => string.Format("{0}{1};", current, next))); - } - - //min totals validation - if (!ValidateMinOrderSubtotalAmount(details.Cart)) - { - var minOrderSubtotalAmount = _currencyService.ConvertFromPrimaryStoreCurrency(_orderSettings.MinOrderSubtotalAmount, _workContext.WorkingCurrency); - throw new NopException(string.Format(_localizationService.GetResource("Checkout.MinOrderSubtotalAmount"), - _priceFormatter.FormatPrice(minOrderSubtotalAmount, true, false))); - } - - if (!ValidateMinOrderTotalAmount(details.Cart)) - { - var minOrderTotalAmount = _currencyService.ConvertFromPrimaryStoreCurrency(_orderSettings.MinOrderTotalAmount, _workContext.WorkingCurrency); - throw new NopException(string.Format(_localizationService.GetResource("Checkout.MinOrderTotalAmount"), - _priceFormatter.FormatPrice(minOrderTotalAmount, true, false))); - } - - //tax display type - if (_taxSettings.AllowCustomersToSelectTaxDisplayType) - details.CustomerTaxDisplayType = (TaxDisplayType)details.Customer.GetAttribute(SystemCustomerAttributeNames.TaxDisplayTypeId, processPaymentRequest.StoreId); - else - details.CustomerTaxDisplayType = _taxSettings.TaxDisplayType; - - - //order total (and applied discounts, gift cards, reward points) - List appliedGiftCards; - List orderAppliedDiscounts; - List subTotalAppliedDiscounts; - List shippingAppliedDiscounts; - decimal orderDiscountAmount; - int redeemedRewardPoints; - decimal redeemedRewardPointsAmount; - TaxSummary taxSummaryIncl; - TaxSummary taxSummaryExcl; - - //calculate two times to get correct incl./excl. tax - var orderTotalIncl = _orderTotalCalculationService.GetShoppingCartTotal(details.Cart, out orderDiscountAmount, - out orderAppliedDiscounts, out subTotalAppliedDiscounts, out shippingAppliedDiscounts, - out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount, - out taxSummaryIncl, true); - - var orderTotalExcl = _orderTotalCalculationService.GetShoppingCartTotal(details.Cart, out orderDiscountAmount, - out orderAppliedDiscounts, out subTotalAppliedDiscounts, out shippingAppliedDiscounts, - out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount, - out taxSummaryExcl, false); - - //sub total (incl tax) (excl tax) - details.OrderSubTotalInclTax = taxSummaryIncl.TotalSubTotalAmount; - details.OrderSubTotalDiscountInclTax = taxSummaryIncl.TotalSubTotalDiscAmount; - - details.OrderSubTotalExclTax = taxSummaryExcl.TotalSubTotalAmount; - details.OrderSubTotalDiscountExclTax = taxSummaryExcl.TotalSubTotalDiscAmount; - - - //discount history - foreach (var disc in subTotalAppliedDiscounts) - if (!details.AppliedDiscounts.ContainsDiscount(disc)) - details.AppliedDiscounts.Add(disc); - - //shipping info - if (details.Cart.RequiresShipping()) - { - var pickupPoint = details.Customer.GetAttribute(SystemCustomerAttributeNames.SelectedPickupPoint, processPaymentRequest.StoreId); - if (_shippingSettings.AllowPickUpInStore && pickupPoint != null) - { - var country = _countryService.GetCountryByTwoLetterIsoCode(pickupPoint.CountryCode); - var state = _stateProvinceService.GetStateProvinceByAbbreviation(pickupPoint.StateAbbreviation); - - details.PickUpInStore = true; - details.PickupAddress = new Address - { - Address1 = pickupPoint.Address, - City = pickupPoint.City, - Country = country, - StateProvince = state, - ZipPostalCode = pickupPoint.ZipPostalCode, - CreatedOnUtc = DateTime.UtcNow, - }; - } - else - { - if (details.Customer.ShippingAddress == null) - throw new NopException("Shipping address is not provided"); - - if (!CommonHelper.IsValidEmail(details.Customer.ShippingAddress.Email)) - throw new NopException("Email is not valid"); - - //clone shipping address - details.ShippingAddress = (Address)details.Customer.ShippingAddress.Clone(); - if (details.ShippingAddress.Country != null && !details.ShippingAddress.Country.AllowsShipping) - throw new NopException(string.Format("Country '{0}' is not allowed for shipping", details.ShippingAddress.Country.Name)); - } - - var shippingOption = details.Customer.GetAttribute(SystemCustomerAttributeNames.SelectedShippingOption, processPaymentRequest.StoreId); - if (shippingOption != null) - { - details.ShippingMethodName = shippingOption.Name; - details.ShippingRateComputationMethodSystemName = shippingOption.ShippingRateComputationMethodSystemName; - } - - details.ShippingStatus = ShippingStatus.NotYetShipped; - } - else - details.ShippingStatus = ShippingStatus.ShippingNotRequired; - - //shipping total - details.OrderShippingTotalInclTax = taxSummaryIncl.TotalShippingAmount; - details.OrderShippingTotalExclTax = taxSummaryExcl.TotalShippingAmount; - - foreach(var disc in shippingAppliedDiscounts) - if (!details.AppliedDiscounts.ContainsDiscount(disc)) - details.AppliedDiscounts.Add(disc); - - //payment total - details.PaymentAdditionalFeeInclTax = taxSummaryIncl.TotalPaymentFeeAmount; - details.PaymentAdditionalFeeExclTax = taxSummaryExcl.TotalPaymentFeeAmount; - - //tax amount - bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; - details.OrderTaxTotal = includingTax? taxSummaryIncl.TotalAmountVAT : taxSummaryExcl.TotalAmountVAT; - - //VAT number - var customerVatStatus = (VatNumberStatus)details.Customer.GetAttribute(SystemCustomerAttributeNames.VatNumberStatusId); - if (_taxSettings.EuVatEnabled && customerVatStatus == VatNumberStatus.Valid) - details.VatNumber = details.Customer.GetAttribute(SystemCustomerAttributeNames.VatNumber); - - //tax rates - //var taxrates = includingTax ? taxSummaryIncl.GenerateOldTaxrateDict() : taxSummaryExcl.GenerateOldTaxrateDict(); - var taxrates = includingTax ? taxSummaryIncl : taxSummaryExcl; - details.TaxRates = taxrates.GenerateTaxRateString(); - - //order total (and applied discounts, gift cards, reward points) - details.OrderDiscountAmount = includingTax ? taxSummaryIncl.TotalInvDiscAmount : taxSummaryExcl.TotalInvDiscAmount; - details.RedeemedRewardPoints = redeemedRewardPoints; - details.RedeemedRewardPointsAmount = redeemedRewardPointsAmount; - details.AppliedGiftCards = appliedGiftCards; - details.OrderTotal = includingTax ? orderTotalIncl ?? 0 : orderTotalExcl ?? 0; - details.OrderAmount = includingTax ? taxSummaryIncl.TotalAmount : taxSummaryExcl.TotalAmount; - details.OrderAmountIncl = includingTax ? taxSummaryIncl.TotalAmountIncludingVAT : taxSummaryExcl.TotalAmountIncludingVAT; - - //discount history - foreach (var disc in orderAppliedDiscounts) - if (!details.AppliedDiscounts.ContainsDiscount(disc)) - details.AppliedDiscounts.Add(disc); - - processPaymentRequest.OrderTotal = details.OrderTotal; - - //recurring or standard shopping cart? - details.IsRecurringShoppingCart = details.Cart.IsRecurring(); - if (details.IsRecurringShoppingCart) - { - int recurringCycleLength; - RecurringProductCyclePeriod recurringCyclePeriod; - int recurringTotalCycles; - var recurringCyclesError = details.Cart.GetRecurringCycleInfo(_localizationService, - out recurringCycleLength, out recurringCyclePeriod, out recurringTotalCycles); - if (!string.IsNullOrEmpty(recurringCyclesError)) - throw new NopException(recurringCyclesError); - - processPaymentRequest.RecurringCycleLength = recurringCycleLength; - processPaymentRequest.RecurringCyclePeriod = recurringCyclePeriod; - processPaymentRequest.RecurringTotalCycles = recurringTotalCycles; - } - - return details; - } - - /// - /// Prepare details to place order based on the recurring payment. - /// - /// Process payment request - /// Details - protected virtual PlaceOrderContainter PrepareRecurringOrderDetails(ProcessPaymentRequest processPaymentRequest) - { - var details = new PlaceOrderContainter(); - details.IsRecurringShoppingCart = true; - - //Load initial order - details.InitialOrder = _orderService.GetOrderById(processPaymentRequest.InitialOrderId); - if (details.InitialOrder == null) - throw new ArgumentException("Initial order is not set for recurring payment"); - - processPaymentRequest.PaymentMethodSystemName = details.InitialOrder.PaymentMethodSystemName; - - //customer - details.Customer = _customerService.GetCustomerById(processPaymentRequest.CustomerId); - if (details.Customer == null) - throw new ArgumentException("Customer is not set"); - - //affiliate - var affiliate = _affiliateService.GetAffiliateById(details.Customer.AffiliateId); - if (affiliate != null && affiliate.Active && !affiliate.Deleted) - details.AffiliateId = affiliate.Id; - - //check whether customer is guest - if (details.Customer.IsGuest() && !_orderSettings.AnonymousCheckoutAllowed) - throw new NopException("Anonymous checkout is not allowed"); - - //customer currency - details.CustomerCurrencyCode = details.InitialOrder.CustomerCurrencyCode; - details.CustomerCurrencyRate = details.InitialOrder.CurrencyRate; - - //customer language - details.CustomerLanguage = _languageService.GetLanguageById(details.InitialOrder.CustomerLanguageId); - if (details.CustomerLanguage == null || !details.CustomerLanguage.Published) - details.CustomerLanguage = _workContext.WorkingLanguage; - - //billing address - if (details.InitialOrder.BillingAddress == null) - throw new NopException("Billing address is not available"); - - details.BillingAddress = (Address)details.InitialOrder.BillingAddress.Clone(); - if (details.BillingAddress.Country != null && !details.BillingAddress.Country.AllowsBilling) - throw new NopException(string.Format("Country '{0}' is not allowed for billing", details.BillingAddress.Country.Name)); - - //checkout attributes - details.CheckoutAttributesXml = details.InitialOrder.CheckoutAttributesXml; - details.CheckoutAttributeDescription = details.InitialOrder.CheckoutAttributeDescription; - - //tax display type - details.CustomerTaxDisplayType = details.InitialOrder.CustomerTaxDisplayType; - - //sub total - details.OrderSubTotalInclTax = details.InitialOrder.OrderSubtotalInclTax; - details.OrderSubTotalExclTax = details.InitialOrder.OrderSubtotalExclTax; - - //shipping info - if (details.InitialOrder.ShippingStatus != ShippingStatus.ShippingNotRequired) - { - details.PickUpInStore = details.InitialOrder.PickUpInStore; - if (!details.PickUpInStore) - { - if (details.InitialOrder.ShippingAddress == null) - throw new NopException("Shipping address is not available"); - - //clone shipping address - details.ShippingAddress = (Address)details.InitialOrder.ShippingAddress.Clone(); - if (details.ShippingAddress.Country != null && !details.ShippingAddress.Country.AllowsShipping) - throw new NopException(string.Format("Country '{0}' is not allowed for shipping", details.ShippingAddress.Country.Name)); - } - else - if (details.InitialOrder.PickupAddress != null) - details.PickupAddress = (Address)details.InitialOrder.PickupAddress.Clone(); - details.ShippingMethodName = details.InitialOrder.ShippingMethod; - details.ShippingRateComputationMethodSystemName = details.InitialOrder.ShippingRateComputationMethodSystemName; - details.ShippingStatus = ShippingStatus.NotYetShipped; - } - else - details.ShippingStatus = ShippingStatus.ShippingNotRequired; - - //shipping total - details.OrderShippingTotalInclTax = details.InitialOrder.OrderShippingInclTax; - details.OrderShippingTotalExclTax = details.InitialOrder.OrderShippingExclTax; - - //payment total - details.PaymentAdditionalFeeInclTax = details.InitialOrder.PaymentMethodAdditionalFeeInclTax; - details.PaymentAdditionalFeeExclTax = details.InitialOrder.PaymentMethodAdditionalFeeExclTax; - - //tax total - details.OrderTaxTotal = details.InitialOrder.OrderTax; - - //VAT number - details.VatNumber = details.InitialOrder.VatNumber; - - //order total - details.OrderDiscountAmount = details.InitialOrder.OrderDiscount; - details.OrderTotal = details.InitialOrder.OrderTotal; - details.OrderAmount = details.InitialOrder.OrderAmount; - details.OrderAmountIncl = details.InitialOrder.OrderAmountIncl; - processPaymentRequest.OrderTotal = details.OrderTotal; - - return details; - } - - /// - /// Save order and add order notes - /// - /// Process payment request - /// Process payment result - /// Details - /// Order - protected virtual Order SaveOrderDetails(ProcessPaymentRequest processPaymentRequest, - ProcessPaymentResult processPaymentResult, PlaceOrderContainter details) - { - var order = new Order - { - StoreId = processPaymentRequest.StoreId, - OrderGuid = processPaymentRequest.OrderGuid, - CustomerId = details.Customer.Id, - CustomerLanguageId = details.CustomerLanguage.Id, - CustomerTaxDisplayType = details.CustomerTaxDisplayType, - CustomerIp = _webHelper.GetCurrentIpAddress(), - OrderSubtotalInclTax = details.OrderSubTotalInclTax, - OrderSubtotalExclTax = details.OrderSubTotalExclTax, - OrderSubTotalDiscountInclTax = details.OrderSubTotalDiscountInclTax, - OrderSubTotalDiscountExclTax = details.OrderSubTotalDiscountExclTax, - OrderShippingInclTax = details.OrderShippingTotalInclTax, - OrderShippingExclTax = details.OrderShippingTotalExclTax, - PaymentMethodAdditionalFeeInclTax = details.PaymentAdditionalFeeInclTax, - PaymentMethodAdditionalFeeExclTax = details.PaymentAdditionalFeeExclTax, - TaxRates = details.TaxRates, - OrderTax = details.OrderTaxTotal, - OrderTotal = details.OrderTotal, - OrderAmount = details.OrderAmount, - OrderAmountIncl = details.OrderAmountIncl, - RefundedAmount = decimal.Zero, - OrderDiscount = details.OrderDiscountAmount, - CheckoutAttributeDescription = details.CheckoutAttributeDescription, - CheckoutAttributesXml = details.CheckoutAttributesXml, - CustomerCurrencyCode = details.CustomerCurrencyCode, - CurrencyRate = details.CustomerCurrencyRate, - AffiliateId = details.AffiliateId, - OrderStatus = OrderStatus.Pending, - AllowStoringCreditCardNumber = processPaymentResult.AllowStoringCreditCardNumber, - CardType = processPaymentResult.AllowStoringCreditCardNumber ? _encryptionService.EncryptText(processPaymentRequest.CreditCardType) : string.Empty, - CardName = processPaymentResult.AllowStoringCreditCardNumber ? _encryptionService.EncryptText(processPaymentRequest.CreditCardName) : string.Empty, - CardNumber = processPaymentResult.AllowStoringCreditCardNumber ? _encryptionService.EncryptText(processPaymentRequest.CreditCardNumber) : string.Empty, - MaskedCreditCardNumber = _encryptionService.EncryptText(_paymentService.GetMaskedCreditCardNumber(processPaymentRequest.CreditCardNumber)), - CardCvv2 = processPaymentResult.AllowStoringCreditCardNumber ? _encryptionService.EncryptText(processPaymentRequest.CreditCardCvv2) : string.Empty, - CardExpirationMonth = processPaymentResult.AllowStoringCreditCardNumber ? _encryptionService.EncryptText(processPaymentRequest.CreditCardExpireMonth.ToString()) : string.Empty, - CardExpirationYear = processPaymentResult.AllowStoringCreditCardNumber ? _encryptionService.EncryptText(processPaymentRequest.CreditCardExpireYear.ToString()) : string.Empty, - PaymentMethodSystemName = processPaymentRequest.PaymentMethodSystemName, - AuthorizationTransactionId = processPaymentResult.AuthorizationTransactionId, - AuthorizationTransactionCode = processPaymentResult.AuthorizationTransactionCode, - AuthorizationTransactionResult = processPaymentResult.AuthorizationTransactionResult, - CaptureTransactionId = processPaymentResult.CaptureTransactionId, - CaptureTransactionResult = processPaymentResult.CaptureTransactionResult, - SubscriptionTransactionId = processPaymentResult.SubscriptionTransactionId, - PaymentStatus = processPaymentResult.NewPaymentStatus, - PaidDateUtc = null, - BillingAddress = details.BillingAddress, - ShippingAddress = details.ShippingAddress, - ShippingStatus = details.ShippingStatus, - ShippingMethod = details.ShippingMethodName, - PickUpInStore = details.PickUpInStore, - PickupAddress = details.PickupAddress, - ShippingRateComputationMethodSystemName = details.ShippingRateComputationMethodSystemName, - CustomValuesXml = processPaymentRequest.SerializeCustomValues(), - VatNumber = details.VatNumber, - CreatedOnUtc = DateTime.UtcNow, - InvoiceId = null, - InvoiceDateUtc = null, - CustomOrderNumber = string.Empty - }; - - _orderService.InsertOrder(order); - - //generate and set custom order number - order.CustomOrderNumber = _customNumberFormatter.GenerateOrderCustomNumber(order); - _orderService.UpdateOrder(order); - - //reward points history - if (details.RedeemedRewardPointsAmount > decimal.Zero) - { - _rewardPointService.AddRewardPointsHistoryEntry(details.Customer, -details.RedeemedRewardPoints, order.StoreId, - string.Format(_localizationService.GetResource("RewardPoints.Message.RedeemedForOrder", order.CustomerLanguageId), order.CustomOrderNumber), - order, details.RedeemedRewardPointsAmount); - _customerService.UpdateCustomer(details.Customer); - } - - return order; - } - - /// - /// Send "order placed" notifications and save order notes - /// - /// Order - protected virtual void SendNotificationsAndSaveNotes(Order order) - { - //notes, messages - if (_workContext.OriginalCustomerIfImpersonated != null) - //this order is placed by a store administrator impersonating a customer - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("Order placed by a store owner ('{0}'. ID = {1}) impersonating the customer.", - _workContext.OriginalCustomerIfImpersonated.Email, _workContext.OriginalCustomerIfImpersonated.Id), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - else - order.OrderNotes.Add(new OrderNote - { - Note = "Order placed", - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - //send email notifications - var orderPlacedStoreOwnerNotificationQueuedEmailId = _workflowMessageService.SendOrderPlacedStoreOwnerNotification(order, _localizationSettings.DefaultAdminLanguageId); - if (orderPlacedStoreOwnerNotificationQueuedEmailId > 0) - { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("\"Order placed\" email (to store owner) has been queued. Queued email identifier: {0}.", orderPlacedStoreOwnerNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - } - - var orderPlacedAttachmentFilePath = _orderSettings.AttachPdfInvoiceToOrderPlacedEmail ? - _pdfService.PrintOrderToPdf(order) : null; - var orderPlacedAttachmentFileName = _orderSettings.AttachPdfInvoiceToOrderPlacedEmail ? - "order.pdf" : null; - var orderPlacedCustomerNotificationQueuedEmailId = _workflowMessageService - .SendOrderPlacedCustomerNotification(order, order.CustomerLanguageId, orderPlacedAttachmentFilePath, orderPlacedAttachmentFileName); - if (orderPlacedCustomerNotificationQueuedEmailId > 0) - { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("\"Order placed\" email (to customer) has been queued. Queued email identifier: {0}.", orderPlacedCustomerNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - } - - var vendors = GetVendorsInOrder(order); - foreach (var vendor in vendors) - { - var orderPlacedVendorNotificationQueuedEmailId = _workflowMessageService.SendOrderPlacedVendorNotification(order, vendor, _localizationSettings.DefaultAdminLanguageId); - if (orderPlacedVendorNotificationQueuedEmailId > 0) - { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("\"Order placed\" email (to vendor) has been queued. Queued email identifier: {0}.", orderPlacedVendorNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - } - } - } - /// - /// Award (earn) reward points (for placing a new order) - /// - /// Order - protected virtual void AwardRewardPoints(Order order) - { - var totalForRewardPoints = _orderTotalCalculationService.CalculateApplicableOrderTotalForRewardPoints(order.OrderShippingInclTax, order.OrderTotal); - int points = _orderTotalCalculationService.CalculateRewardPoints(order.Customer, totalForRewardPoints); - if (points == 0) - return; - - //Ensure that reward points were not added (earned) before. We should not add reward points if they were already earned for this order - if (order.RewardPointsHistoryEntryId.HasValue) - return; - - //check whether delay is set - DateTime? activatingDate = null; - if (_rewardPointsSettings.ActivationDelay > 0) - { - var delayPeriod = (RewardPointsActivatingDelayPeriod)_rewardPointsSettings.ActivationDelayPeriodId; - var delayInHours = delayPeriod.ToHours(_rewardPointsSettings.ActivationDelay); - activatingDate = DateTime.UtcNow.AddHours(delayInHours); - } - - //add reward points - order.RewardPointsHistoryEntryId = _rewardPointService.AddRewardPointsHistoryEntry(order.Customer, points, order.StoreId, - string.Format(_localizationService.GetResource("RewardPoints.Message.EarnedForOrder"), order.CustomOrderNumber), activatingDate: activatingDate); - - _orderService.UpdateOrder(order); - } - - /// - /// Reduce (cancel) reward points (previously awarded for placing an order) - /// - /// Order - protected virtual void ReduceRewardPoints(Order order) - { - var totalForRewardPoints = _orderTotalCalculationService.CalculateApplicableOrderTotalForRewardPoints(order.OrderShippingInclTax, order.OrderTotal); - int points = _orderTotalCalculationService.CalculateRewardPoints(order.Customer, totalForRewardPoints); - if (points == 0) - return; - - //ensure that reward points were already earned for this order before - if (!order.RewardPointsHistoryEntryId.HasValue) - return; - - //get appropriate history entry - var rewardPointsHistoryEntry = _rewardPointService.GetRewardPointsHistoryEntryById(order.RewardPointsHistoryEntryId.Value); - if (rewardPointsHistoryEntry != null && rewardPointsHistoryEntry.CreatedOnUtc > DateTime.UtcNow) - { - //just delete the upcoming entry (points were not granted yet) - _rewardPointService.DeleteRewardPointsHistoryEntry(rewardPointsHistoryEntry); - } - else - { - //or reduce reward points if the entry already exists - _rewardPointService.AddRewardPointsHistoryEntry(order.Customer, -points, order.StoreId, - string.Format(_localizationService.GetResource("RewardPoints.Message.ReducedForOrder"), order.CustomOrderNumber)); - } - - _orderService.UpdateOrder(order); - } - - /// - /// Return back redeemded reward points to a customer (spent when placing an order) - /// - /// Order - protected virtual void ReturnBackRedeemedRewardPoints(Order order) - { - //were some points redeemed when placing an order? - if (order.RedeemedRewardPointsEntry == null) - return; - - //return back - _rewardPointService.AddRewardPointsHistoryEntry(order.Customer, -order.RedeemedRewardPointsEntry.Points, order.StoreId, - string.Format(_localizationService.GetResource("RewardPoints.Message.ReturnedForOrder"), order.CustomOrderNumber)); - _orderService.UpdateOrder(order); - } - - - /// - /// Set IsActivated value for purchase gift cards for particular order - /// - /// Order - /// A value indicating whether to activate gift cards; true - activate, false - deactivate - protected virtual void SetActivatedValueForPurchasedGiftCards(Order order, bool activate) - { - var giftCards = _giftCardService.GetAllGiftCards(purchasedWithOrderId: order.Id, - isGiftCardActivated: !activate); - foreach (var gc in giftCards) - { - if (activate) - { - //activate - bool isRecipientNotified = gc.IsRecipientNotified; - if (gc.GiftCardType == GiftCardType.Virtual) - { - //send email for virtual gift card - if (!String.IsNullOrEmpty(gc.RecipientEmail) && - !String.IsNullOrEmpty(gc.SenderEmail)) - { - var customerLang = _languageService.GetLanguageById(order.CustomerLanguageId); - if (customerLang == null) - customerLang = _languageService.GetAllLanguages().FirstOrDefault(); - if (customerLang == null) - throw new Exception("No languages could be loaded"); - int 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); - } - } - } - - /// - /// Sets an order status - /// - /// Order - /// New order status - /// True to notify customer - protected virtual void SetOrderStatus(Order order, OrderStatus os, bool notifyCustomer) - { - if (order == null) - throw new ArgumentNullException("order"); - - OrderStatus prevOrderStatus = order.OrderStatus; - if (prevOrderStatus == os) - return; - - //set and save new order status - order.OrderStatusId = (int)os; - _orderService.UpdateOrder(order); - - //order notes, notifications - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("Order status has been changed to {0}", os.ToString()), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - - if (prevOrderStatus != OrderStatus.Complete && - os == OrderStatus.Complete - && notifyCustomer) - { - //notification - var orderCompletedAttachmentFilePath = _orderSettings.AttachPdfInvoiceToOrderCompletedEmail ? - _pdfService.PrintOrderToPdf(order) : null; - var orderCompletedAttachmentFileName = _orderSettings.AttachPdfInvoiceToOrderCompletedEmail ? - "order.pdf" : null; - int orderCompletedCustomerNotificationQueuedEmailId = _workflowMessageService - .SendOrderCompletedCustomerNotification(order, order.CustomerLanguageId, orderCompletedAttachmentFilePath, - orderCompletedAttachmentFileName); - if (orderCompletedCustomerNotificationQueuedEmailId > 0) - { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("\"Order completed\" email (to customer) has been queued. Queued email identifier: {0}.", orderCompletedCustomerNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - } - } - - 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("\"Order cancelled\" email (to customer) has been queued. Queued email identifier: {0}.", orderCancelledCustomerNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - } - } - - //reward points - if (order.OrderStatus == OrderStatus.Complete) - { - AwardRewardPoints(order); - } - if (order.OrderStatus == OrderStatus.Cancelled) - { - ReduceRewardPoints(order); - } - - //gift cards activation - if (_orderSettings.ActivateGiftCardsAfterCompletingOrder && order.OrderStatus == OrderStatus.Complete) - { - SetActivatedValueForPurchasedGiftCards(order, true); - } - - //gift cards deactivation - if (_orderSettings.DeactivateGiftCardsAfterCancellingOrder && order.OrderStatus == OrderStatus.Cancelled) - { - SetActivatedValueForPurchasedGiftCards(order, false); - } - } - - /// - /// Process order paid status - /// - /// Order - protected virtual void ProcessOrderPaid(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - //raise event - _eventPublisher.Publish(new OrderPaidEvent(order)); - - //order paid email notification - if (order.OrderTotal != decimal.Zero) - { - //we should not send it for free ($0 total) orders? - //remove this "if" statement if you want to send it in this case - - var orderPaidAttachmentFilePath = _orderSettings.AttachPdfInvoiceToOrderPaidEmail ? - _pdfService.PrintOrderToPdf(order) : null; - var orderPaidAttachmentFileName = _orderSettings.AttachPdfInvoiceToOrderPaidEmail ? - "order.pdf" : null; - _workflowMessageService.SendOrderPaidCustomerNotification(order, order.CustomerLanguageId, - orderPaidAttachmentFilePath, orderPaidAttachmentFileName); - - _workflowMessageService.SendOrderPaidStoreOwnerNotification(order, _localizationSettings.DefaultAdminLanguageId); - var vendors = GetVendorsInOrder(order); - foreach (var vendor in vendors) - { - _workflowMessageService.SendOrderPaidVendorNotification(order, vendor, _localizationSettings.DefaultAdminLanguageId); - } - //TODO add "order paid email sent" order note - } - - //customer roles with "purchased with product" specified - ProcessCustomerRolesWithPurchasedProductSpecified(order, true); - } - - /// - /// Process customer roles with "Purchased with Product" property configured - /// - /// Order - /// A value indicating whether to add configured customer role; true - add, false - remove - protected virtual void ProcessCustomerRolesWithPurchasedProductSpecified(Order order, bool add) - { - if (order == null) - throw new ArgumentNullException("order"); - - //purchased product identifiers - var purchasedProductIds = new List(); - foreach (var orderItem in order.OrderItems) - { - //standard items - purchasedProductIds.Add(orderItem.ProductId); - - //bundled (associated) products - var attributeValues = _productAttributeParser.ParseProductAttributeValues(orderItem.AttributesXml); - foreach (var attributeValue in attributeValues) - { - if (attributeValue.AttributeValueType == AttributeValueType.AssociatedToProduct) - { - purchasedProductIds.Add(attributeValue.AssociatedProductId); - } - } - } - - //list of customer roles - var customerRoles = _customerService - .GetAllCustomerRoles(true) - .Where(cr => purchasedProductIds.Contains(cr.PurchasedWithProductId)) - .ToList(); - - if (customerRoles.Any()) - { - var customer = order.Customer; - foreach (var customerRole in customerRoles) - { - if (customer.CustomerRoles.Count(cr => cr.Id == customerRole.Id) == 0) - { - //not in the list yet - if (add) - { - //add - customer.CustomerRoles.Add(customerRole); - } - } - else - { - //already in the list - if (!add) - { - //remove - customer.CustomerRoles.Remove(customerRole); - } - } - } - _customerService.UpdateCustomer(customer); - } - } - - /// - /// Get a list of vendors in order (order items) - /// - /// Order - /// Vendors - protected virtual IList GetVendorsInOrder(Order order) - { - var vendors = new List(); - foreach (var orderItem in order.OrderItems) - { - var vendorId = orderItem.Product.VendorId; - //find existing - var vendor = vendors.FirstOrDefault(v => v.Id == vendorId); - if (vendor == null) - { - //not found. load by Id - vendor = _vendorService.GetVendorById(vendorId); - if (vendor != null && !vendor.Deleted && vendor.Active) - { - vendors.Add(vendor); - } - } - } - - return vendors; - } - - #endregion - - #region Methods - - /// - /// Checks order status - /// - /// Order - /// Validated order - public virtual void CheckOrderStatus(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (order.PaymentStatus == PaymentStatus.Paid && !order.PaidDateUtc.HasValue) - { - //ensure that paid date is set - order.PaidDateUtc = DateTime.UtcNow; - _orderService.UpdateOrder(order); - } - - //set invoice id - if (order.PaymentStatus == PaymentStatus.Paid && order.InvoiceId == null) - { - order.InvoiceDateUtc = DateTime.UtcNow; - order.InvoiceId = GetInvoiceId(); - _orderService.UpdateOrder(order); - } - - if (order.OrderStatus == OrderStatus.Pending) - { - if (order.PaymentStatus == PaymentStatus.Authorized || - order.PaymentStatus == PaymentStatus.Paid) - { - SetOrderStatus(order, OrderStatus.Processing, false); - } - } - - if (order.OrderStatus == OrderStatus.Pending) - { - if (order.ShippingStatus == ShippingStatus.PartiallyShipped || - order.ShippingStatus == ShippingStatus.Shipped || - order.ShippingStatus == ShippingStatus.Delivered) - { - SetOrderStatus(order, OrderStatus.Processing, false); - } - } - - //is order complete? - if (order.OrderStatus != OrderStatus.Cancelled && - order.OrderStatus != OrderStatus.Complete) - { - if (order.PaymentStatus == PaymentStatus.Paid) - { - var completed = false; - if (order.ShippingStatus == ShippingStatus.ShippingNotRequired) - { - //shipping is not required - completed = true; - } - else - { - //shipping is required - if (_orderSettings.CompleteOrderWhenDelivered) - { - completed = order.ShippingStatus == ShippingStatus.Delivered; - } - else - { - completed = order.ShippingStatus == ShippingStatus.Shipped || - order.ShippingStatus == ShippingStatus.Delivered; - } - } - - if (completed) - { - SetOrderStatus(order, OrderStatus.Complete, true); - } - } - } - } - - /// - /// Places an order - /// - /// Process payment request - /// Place order result - public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentRequest) - { - if (processPaymentRequest == null) - throw new ArgumentNullException("processPaymentRequest"); - - var result = new PlaceOrderResult(); - try - { - if (processPaymentRequest.OrderGuid == Guid.Empty) - processPaymentRequest.OrderGuid = Guid.NewGuid(); - - //prepare order details - var details = PreparePlaceOrderDetails(processPaymentRequest); - - #region Payment workflow - - - //process payment - ProcessPaymentResult processPaymentResult = null; - //skip payment workflow if order total equals zero - var skipPaymentWorkflow = details.OrderTotal == decimal.Zero; - if (!skipPaymentWorkflow) - { - var paymentMethod = _paymentService.LoadPaymentMethodBySystemName(processPaymentRequest.PaymentMethodSystemName); - if (paymentMethod == null) - throw new NopException("Payment method couldn't be loaded"); - - //ensure that payment method is active - if (!paymentMethod.IsPaymentMethodActive(_paymentSettings)) - throw new NopException("Payment method is not active"); - - if (details.IsRecurringShoppingCart) - { - //recurring cart - switch (_paymentService.GetRecurringPaymentType(processPaymentRequest.PaymentMethodSystemName)) - { - case RecurringPaymentType.NotSupported: - throw new NopException("Recurring payments are not supported by selected payment method"); - case RecurringPaymentType.Manual: - case RecurringPaymentType.Automatic: - processPaymentResult = _paymentService.ProcessRecurringPayment(processPaymentRequest); - break; - default: - throw new NopException("Not supported recurring payment type"); - } - } - else - //standard cart - processPaymentResult = _paymentService.ProcessPayment(processPaymentRequest); - } - else - //payment is not required - processPaymentResult = new ProcessPaymentResult { NewPaymentStatus = PaymentStatus.Paid }; - - if (processPaymentResult == null) - throw new NopException("processPaymentResult is not available"); - - #endregion - - if (processPaymentResult.Success) - { - #region Save order details - - var order = SaveOrderDetails(processPaymentRequest, processPaymentResult, details); - result.PlacedOrder = order; - - //move shopping cart items to order items - foreach (var sc in details.Cart) - { - //prices - decimal taxRate; - List scDiscounts; - decimal discountAmount; - int? maximumDiscountQty; - var scUnitPrice = _priceCalculationService.GetUnitPrice(sc); - var scSubTotal = _priceCalculationService.GetSubTotal(sc, true, out discountAmount, out scDiscounts, out maximumDiscountQty); - var scUnitPriceInclTax = _taxService.GetProductPrice(sc.Product, scUnitPrice, true, details.Customer, out taxRate); - var scUnitPriceExclTax = _taxService.GetProductPrice(sc.Product, scUnitPrice, false, details.Customer, out taxRate); - var scSubTotalInclTax = _taxService.GetProductPrice(sc.Product, scSubTotal, true, details.Customer, out taxRate); - var scSubTotalExclTax = _taxService.GetProductPrice(sc.Product, scSubTotal, false, details.Customer, out taxRate); - var discountAmountInclTax = _taxService.GetProductPrice(sc.Product, discountAmount, true, details.Customer, out taxRate); - var discountAmountExclTax = _taxService.GetProductPrice(sc.Product, discountAmount, false, details.Customer, out taxRate); - foreach (var disc in scDiscounts) - if (!details.AppliedDiscounts.ContainsDiscount(disc)) - details.AppliedDiscounts.Add(disc); - - //attributes - var attributeDescription = _productAttributeFormatter.FormatAttributes(sc.Product, sc.AttributesXml, details.Customer, subTotal: _taxSettings.PricesIncludeTax ? scSubTotalInclTax : scSubTotalExclTax); - - var itemWeight = _shippingService.GetShoppingCartItemWeight(sc); - - //save order item - var orderItem = new OrderItem - { - OrderItemGuid = Guid.NewGuid(), - Order = order, - ProductId = sc.ProductId, - UnitPriceInclTax = scUnitPriceInclTax, - UnitPriceExclTax = scUnitPriceExclTax, - PriceInclTax = scSubTotalInclTax, - PriceExclTax = scSubTotalExclTax, - OriginalProductCost = _priceCalculationService.GetProductCost(sc.Product, sc.AttributesXml), - AttributeDescription = attributeDescription, - AttributesXml = sc.AttributesXml, - Quantity = sc.Quantity, - DiscountAmountInclTax = discountAmountInclTax, - DiscountAmountExclTax = discountAmountExclTax, - DownloadCount = 0, - IsDownloadActivated = false, - LicenseDownloadId = 0, - ItemWeight = itemWeight, - RentalStartDateUtc = sc.RentalStartDateUtc, - RentalEndDateUtc = sc.RentalEndDateUtc, - VatRate = taxRate //MF 25.11.16 - }; - order.OrderItems.Add(orderItem); - _orderService.UpdateOrder(order); - - //gift cards - if (sc.Product.IsGiftCard) - { - string giftCardRecipientName; - string giftCardRecipientEmail; - string giftCardSenderName; - string giftCardSenderEmail; - string giftCardMessage; - _productAttributeParser.GetGiftCardAttribute(sc.AttributesXml, out giftCardRecipientName, - out giftCardRecipientEmail, out giftCardSenderName, out giftCardSenderEmail, out giftCardMessage); - - for (var i = 0; i < sc.Quantity; i++) - { - _giftCardService.InsertGiftCard(new GiftCard - { - GiftCardType = sc.Product.GiftCardType, - PurchasedWithOrderItem = orderItem, - Amount = sc.Product.OverriddenGiftCardAmount.HasValue ? sc.Product.OverriddenGiftCardAmount.Value : scUnitPriceExclTax, - IsGiftCardActivated = false, - GiftCardCouponCode = _giftCardService.GenerateGiftCardCode(), - RecipientName = giftCardRecipientName, - RecipientEmail = giftCardRecipientEmail, - SenderName = giftCardSenderName, - SenderEmail = giftCardSenderEmail, - Message = giftCardMessage, - IsRecipientNotified = false, - CreatedOnUtc = DateTime.UtcNow - }); - } - } - - //inventory - _productService.AdjustInventory(sc.Product, -sc.Quantity, sc.AttributesXml, - string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.PlaceOrder"), order.Id)); - } - - //clear shopping cart - details.Cart.ToList().ForEach(sci => _shoppingCartService.DeleteShoppingCartItem(sci, false)); - - //discount usage history - foreach (var discount in details.AppliedDiscounts) - { - var d = _discountService.GetDiscountById(discount.Id); - if (d != null) - { - _discountService.InsertDiscountUsageHistory(new DiscountUsageHistory - { - Discount = d, - Order = order, - CreatedOnUtc = DateTime.UtcNow - }); - } - } - - //gift card usage history - if (details.AppliedGiftCards != null) - foreach (var agc in details.AppliedGiftCards) - { - agc.GiftCard.GiftCardUsageHistory.Add(new GiftCardUsageHistory - { - GiftCard = agc.GiftCard, - UsedWithOrder = order, - UsedValue = agc.AmountCanBeUsed, - CreatedOnUtc = DateTime.UtcNow - }); - _giftCardService.UpdateGiftCard(agc.GiftCard); - } - - //recurring orders - if (details.IsRecurringShoppingCart) - { - //create recurring payment (the first payment) - var rp = new RecurringPayment - { - CycleLength = processPaymentRequest.RecurringCycleLength, - CyclePeriod = processPaymentRequest.RecurringCyclePeriod, - TotalCycles = processPaymentRequest.RecurringTotalCycles, - StartDateUtc = DateTime.UtcNow, - IsActive = true, - CreatedOnUtc = DateTime.UtcNow, - InitialOrder = order, - }; - _orderService.InsertRecurringPayment(rp); - - switch (_paymentService.GetRecurringPaymentType(processPaymentRequest.PaymentMethodSystemName)) - { - case RecurringPaymentType.NotSupported: - //not supported - break; - case RecurringPaymentType.Manual: - rp.RecurringPaymentHistory.Add(new RecurringPaymentHistory - { - RecurringPayment = rp, - CreatedOnUtc = DateTime.UtcNow, - OrderId = order.Id, - }); - _orderService.UpdateRecurringPayment(rp); - break; - case RecurringPaymentType.Automatic: - //will be created later (process is automated) - break; - default: - break; - } - } - - #endregion - - //notifications - SendNotificationsAndSaveNotes(order); - - //reset checkout data - _customerService.ResetCheckoutData(details.Customer, processPaymentRequest.StoreId, clearCouponCodes: true, clearCheckoutAttributes: true); - _customerActivityService.InsertActivity("PublicStore.PlaceOrder", _localizationService.GetResource("ActivityLog.PublicStore.PlaceOrder"), order.Id); - - //check order status - CheckOrderStatus(order); - - //raise event - _eventPublisher.Publish(new OrderPlacedEvent(order)); - - if (order.PaymentStatus == PaymentStatus.Paid) - ProcessOrderPaid(order); - } - else - foreach (var paymentError in processPaymentResult.Errors) - result.AddError(string.Format(_localizationService.GetResource("Checkout.PaymentError"), paymentError)); - } - catch (Exception exc) - { - _logger.Error(exc.Message, exc); - result.AddError(exc.Message); - } - - #region Process errors - - if (!result.Success) - { - //log errors - var logError = result.Errors.Aggregate("Error while placing order. ", - (current, next) => string.Format("{0}Error {1}: {2}. ", current, result.Errors.IndexOf(next) + 1, next)); - var customer = _customerService.GetCustomerById(processPaymentRequest.CustomerId); - _logger.Error(logError, customer: customer); - } - - #endregion - - return result; - } - - /// - /// Update order totals - /// - /// Parameters for the updating order - public virtual void UpdateOrderTotals(UpdateOrderParameters updateOrderParameters) - { - if (!_orderSettings.AutoUpdateOrderTotalsOnEditingOrder) - return; - - var updatedOrder = updateOrderParameters.UpdatedOrder; - var updatedOrderItem = updateOrderParameters.UpdatedOrderItem; - - //restore shopping cart from order items - var restoredCart = updatedOrder.OrderItems.Select(orderItem => new ShoppingCartItem - { - Id = orderItem.Id, - AttributesXml = orderItem.AttributesXml, - Customer = updatedOrder.Customer, - Product = orderItem.Product, - Quantity = orderItem.Id == updatedOrderItem.Id ? updateOrderParameters.Quantity : orderItem.Quantity, - RentalEndDateUtc = orderItem.RentalEndDateUtc, - RentalStartDateUtc = orderItem.RentalStartDateUtc, - ShoppingCartType = ShoppingCartType.ShoppingCart, - StoreId = updatedOrder.StoreId, - VatRate = orderItem.Id == updatedOrderItem.Id ? updateOrderParameters.VatRate : orderItem.VatRate, - SubTotalInclTax = orderItem.Id == updatedOrderItem.Id ? updateOrderParameters.SubTotalInclTax : orderItem.PriceInclTax, - SubTotalExclTax = orderItem.Id == updatedOrderItem.Id ? updateOrderParameters.SubTotalExclTax : orderItem.PriceExclTax - }).ToList(); - - //get shopping cart item which has been updated - var updatedShoppingCartItem = restoredCart.FirstOrDefault(shoppingCartItem => shoppingCartItem.Id == updatedOrderItem.Id); - var itemDeleted = updatedShoppingCartItem == null; - - //validate shopping cart for warnings - updateOrderParameters.Warnings.AddRange(_shoppingCartService.GetShoppingCartWarnings(restoredCart, string.Empty, false)); - if (!itemDeleted) - updateOrderParameters.Warnings.AddRange(_shoppingCartService.GetShoppingCartItemWarnings(updatedOrder.Customer, updatedShoppingCartItem.ShoppingCartType, - updatedShoppingCartItem.Product, updatedOrder.StoreId, updatedShoppingCartItem.AttributesXml, updatedShoppingCartItem.CustomerEnteredPrice, - updatedShoppingCartItem.RentalStartDateUtc, updatedShoppingCartItem.RentalEndDateUtc, updatedShoppingCartItem.Quantity, false)); - - _orderTotalCalculationService.UpdateOrderTotals(updateOrderParameters, restoredCart); - - if (updateOrderParameters.PickupPoint != null) - { - updatedOrder.PickUpInStore = true; - updatedOrder.PickupAddress = new Address - { - Address1 = updateOrderParameters.PickupPoint.Address, - City = updateOrderParameters.PickupPoint.City, - Country = _countryService.GetCountryByTwoLetterIsoCode(updateOrderParameters.PickupPoint.CountryCode), - ZipPostalCode = updateOrderParameters.PickupPoint.ZipPostalCode, - CreatedOnUtc = DateTime.UtcNow, - }; - updatedOrder.ShippingMethod = string.Format(_localizationService.GetResource("Checkout.PickupPoints.Name"), updateOrderParameters.PickupPoint.Name); - updatedOrder.ShippingRateComputationMethodSystemName = updateOrderParameters.PickupPoint.ProviderSystemName; - } - - if (!itemDeleted) - { - updatedOrderItem.ItemWeight = _shippingService.GetShoppingCartItemWeight(updatedShoppingCartItem); - updatedOrderItem.OriginalProductCost = _priceCalculationService.GetProductCost(updatedShoppingCartItem.Product, updatedShoppingCartItem.AttributesXml); - updatedOrderItem.AttributeDescription = _productAttributeFormatter.FormatAttributes(updatedShoppingCartItem.Product, - updatedShoppingCartItem.AttributesXml, updatedOrder.Customer,subTotal: updatedOrder.CustomerTaxDisplayType == TaxDisplayType.IncludingTax ? updatedOrderItem.PriceInclTax : updatedOrderItem.PriceExclTax); - - //gift cards - if (updatedShoppingCartItem.Product.IsGiftCard) - { - string giftCardRecipientName; - string giftCardRecipientEmail; - string giftCardSenderName; - string giftCardSenderEmail; - string giftCardMessage; - _productAttributeParser.GetGiftCardAttribute(updatedShoppingCartItem.AttributesXml, out giftCardRecipientName, - out giftCardRecipientEmail, out giftCardSenderName, out giftCardSenderEmail, out giftCardMessage); - - for (var i = 0; i < updatedShoppingCartItem.Quantity; i++) - { - _giftCardService.InsertGiftCard(new GiftCard - { - GiftCardType = updatedShoppingCartItem.Product.GiftCardType, - PurchasedWithOrderItem = updatedOrderItem, - Amount = updatedShoppingCartItem.Product.OverriddenGiftCardAmount.HasValue ? - updatedShoppingCartItem.Product.OverriddenGiftCardAmount.Value : updatedOrderItem.UnitPriceExclTax, - IsGiftCardActivated = false, - GiftCardCouponCode = _giftCardService.GenerateGiftCardCode(), - RecipientName = giftCardRecipientName, - RecipientEmail = giftCardRecipientEmail, - SenderName = giftCardSenderName, - SenderEmail = giftCardSenderEmail, - Message = giftCardMessage, - IsRecipientNotified = false, - CreatedOnUtc = DateTime.UtcNow - }); - } - } - } - - _orderService.UpdateOrder(updatedOrder); - - //discount usage history - var discountUsageHistoryForOrder = _discountService.GetAllDiscountUsageHistory(null, updatedOrder.Customer.Id, updatedOrder.Id); - foreach (var discount in updateOrderParameters.AppliedDiscounts) - { - if (!discountUsageHistoryForOrder.Any(history => history.DiscountId == discount.Id)) - { - var d = _discountService.GetDiscountById(discount.Id); - if (d != null) - { - _discountService.InsertDiscountUsageHistory(new DiscountUsageHistory - { - Discount = d, - Order = updatedOrder, - CreatedOnUtc = DateTime.UtcNow - }); - } - } - } - - CheckOrderStatus(updatedOrder); - } - - /// - /// Deletes an order - /// - /// The order - public virtual void DeleteOrder(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - //check whether the order wasn't cancelled before - //if it already was cancelled, then there's no need to make the following adjustments - //(such as reward points, inventory, recurring payments) - //they already was done when cancelling the order - if (order.OrderStatus != OrderStatus.Cancelled) - { - //return (add) back redeemded reward points - ReturnBackRedeemedRewardPoints(order); - //reduce (cancel) back reward points (previously awarded for this order) - ReduceRewardPoints(order); - - //cancel recurring payments - var recurringPayments = _orderService.SearchRecurringPayments(initialOrderId: order.Id); - foreach (var rp in recurringPayments) - { - var errors = CancelRecurringPayment(rp); - //use "errors" variable? - } - - //Adjust inventory for already shipped shipments - //only products with "use multiple warehouses" - foreach (var shipment in order.Shipments) - { - foreach (var shipmentItem in shipment.ShipmentItems) - { - var orderItem = _orderService.GetOrderItemById(shipmentItem.OrderItemId); - if (orderItem == null) - continue; - - _productService.ReverseBookedInventory(orderItem.Product, shipmentItem, - string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.DeleteOrder"), order.Id)); - } - } - - //Adjust inventory - foreach (var orderItem in order.OrderItems) - { - _productService.AdjustInventory(orderItem.Product, orderItem.Quantity, orderItem.AttributesXml, - string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.DeleteOrder"), order.Id)); - } - - } - - //deactivate gift cards - if (_orderSettings.DeactivateGiftCardsAfterDeletingOrder) - SetActivatedValueForPurchasedGiftCards(order, false); - - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = "Order has been deleted", - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - //now delete an order - _orderService.DeleteOrder(order); - } - - /// - /// Process next recurring payment - /// - /// Recurring payment - /// Process payment result (info about last payment for automatic recurring payments) - /// Collection of errors - public virtual IEnumerable ProcessNextRecurringPayment(RecurringPayment recurringPayment, ProcessPaymentResult paymentResult = null) - { - if (recurringPayment == null) - throw new ArgumentNullException("recurringPayment"); - - try - { - if (!recurringPayment.IsActive) - throw new NopException("Recurring payment is not active"); - - var initialOrder = recurringPayment.InitialOrder; - if (initialOrder == null) - throw new NopException("Initial order could not be loaded"); - - var customer = initialOrder.Customer; - if (customer == null) - throw new NopException("Customer could not be loaded"); - - var nextPaymentDate = recurringPayment.NextPaymentDate; - if (!nextPaymentDate.HasValue) - throw new NopException("Next payment date could not be calculated"); - - //payment info - var processPaymentRequest = new ProcessPaymentRequest - { - StoreId = initialOrder.StoreId, - CustomerId = customer.Id, - OrderGuid = Guid.NewGuid(), - InitialOrderId = initialOrder.Id, - RecurringCycleLength = recurringPayment.CycleLength, - RecurringCyclePeriod = recurringPayment.CyclePeriod, - RecurringTotalCycles = recurringPayment.TotalCycles, - CustomValues = initialOrder.DeserializeCustomValues() - }; - - //prepare order details - var details = PrepareRecurringOrderDetails(processPaymentRequest); - - ProcessPaymentResult processPaymentResult; - //skip payment workflow if order total equals zero - var skipPaymentWorkflow = details.OrderTotal == decimal.Zero; - if (!skipPaymentWorkflow) - { - var paymentMethod = _paymentService.LoadPaymentMethodBySystemName(processPaymentRequest.PaymentMethodSystemName); - if (paymentMethod == null) - throw new NopException("Payment method couldn't be loaded"); - - if (!paymentMethod.IsPaymentMethodActive(_paymentSettings)) - throw new NopException("Payment method is not active"); - - //Old credit card info - if (details.InitialOrder.AllowStoringCreditCardNumber) - { - processPaymentRequest.CreditCardType = _encryptionService.DecryptText(details.InitialOrder.CardType); - processPaymentRequest.CreditCardName = _encryptionService.DecryptText(details.InitialOrder.CardName); - processPaymentRequest.CreditCardNumber = _encryptionService.DecryptText(details.InitialOrder.CardNumber); - processPaymentRequest.CreditCardCvv2 = _encryptionService.DecryptText(details.InitialOrder.CardCvv2); - try - { - processPaymentRequest.CreditCardExpireMonth = Convert.ToInt32(_encryptionService.DecryptText(details.InitialOrder.CardExpirationMonth)); - processPaymentRequest.CreditCardExpireYear = Convert.ToInt32(_encryptionService.DecryptText(details.InitialOrder.CardExpirationYear)); - } - catch { } - } - - //payment type - switch (_paymentService.GetRecurringPaymentType(processPaymentRequest.PaymentMethodSystemName)) - { - case RecurringPaymentType.NotSupported: - throw new NopException("Recurring payments are not supported by selected payment method"); - case RecurringPaymentType.Manual: - processPaymentResult = _paymentService.ProcessRecurringPayment(processPaymentRequest); - break; - case RecurringPaymentType.Automatic: - //payment is processed on payment gateway site, info about last transaction in paymentResult parameter - processPaymentResult = paymentResult ?? new ProcessPaymentResult(); - break; - default: - throw new NopException("Not supported recurring payment type"); - } - } - else - processPaymentResult = paymentResult ?? new ProcessPaymentResult { NewPaymentStatus = PaymentStatus.Paid }; - - if (processPaymentResult == null) - throw new NopException("processPaymentResult is not available"); - - if (processPaymentResult.Success) - { - //save order details - var order = SaveOrderDetails(processPaymentRequest, processPaymentResult, details); - - foreach (var orderItem in details.InitialOrder.OrderItems) - { - //save item - var newOrderItem = new OrderItem - { - OrderItemGuid = Guid.NewGuid(), - Order = order, - ProductId = orderItem.ProductId, - UnitPriceInclTax = orderItem.UnitPriceInclTax, - UnitPriceExclTax = orderItem.UnitPriceExclTax, - PriceInclTax = orderItem.PriceInclTax, - PriceExclTax = orderItem.PriceExclTax, - OriginalProductCost = orderItem.OriginalProductCost, - AttributeDescription = orderItem.AttributeDescription, - AttributesXml = orderItem.AttributesXml, - Quantity = orderItem.Quantity, - DiscountAmountInclTax = orderItem.DiscountAmountInclTax, - DiscountAmountExclTax = orderItem.DiscountAmountExclTax, - DownloadCount = 0, - IsDownloadActivated = false, - LicenseDownloadId = 0, - ItemWeight = orderItem.ItemWeight, - RentalStartDateUtc = orderItem.RentalStartDateUtc, - RentalEndDateUtc = orderItem.RentalEndDateUtc, - VatRate = orderItem.VatRate - }; - order.OrderItems.Add(newOrderItem); - _orderService.UpdateOrder(order); - - //gift cards - if (orderItem.Product.IsGiftCard) - { - string giftCardRecipientName; - string giftCardRecipientEmail; - string giftCardSenderName; - string giftCardSenderEmail; - string giftCardMessage; - - _productAttributeParser.GetGiftCardAttribute(orderItem.AttributesXml, out giftCardRecipientName, - out giftCardRecipientEmail, out giftCardSenderName, out giftCardSenderEmail, out giftCardMessage); - - for (var i = 0; i < orderItem.Quantity; i++) - { - _giftCardService.InsertGiftCard(new GiftCard - { - GiftCardType = orderItem.Product.GiftCardType, - PurchasedWithOrderItem = newOrderItem, - Amount = orderItem.UnitPriceExclTax, - IsGiftCardActivated = false, - GiftCardCouponCode = _giftCardService.GenerateGiftCardCode(), - RecipientName = giftCardRecipientName, - RecipientEmail = giftCardRecipientEmail, - SenderName = giftCardSenderName, - SenderEmail = giftCardSenderEmail, - Message = giftCardMessage, - IsRecipientNotified = false, - CreatedOnUtc = DateTime.UtcNow - }); - } - } - - //inventory - _productService.AdjustInventory(orderItem.Product, -orderItem.Quantity, orderItem.AttributesXml, - string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.PlaceOrder"), order.Id)); - } - - //notifications - SendNotificationsAndSaveNotes(order); - - //check order status - CheckOrderStatus(order); - - //raise event - _eventPublisher.Publish(new OrderPlacedEvent(order)); - - if (order.PaymentStatus == PaymentStatus.Paid) - ProcessOrderPaid(order); - - //last payment succeeded - recurringPayment.LastPaymentFailed = false; - - //next recurring payment - recurringPayment.RecurringPaymentHistory.Add(new RecurringPaymentHistory - { - RecurringPayment = recurringPayment, - CreatedOnUtc = DateTime.UtcNow, - OrderId = order.Id, - }); - _orderService.UpdateRecurringPayment(recurringPayment); - - return new List(); - } - else - { - //log errors - var logError = processPaymentResult.Errors.Aggregate("Error while processing recurring order. ", - (current, next) => string.Format("{0}Error {1}: {2}. ", current, processPaymentResult.Errors.IndexOf(next) + 1, next)); - _logger.Error(logError, customer: customer); - - if (processPaymentResult.RecurringPaymentFailed) - { - //set flag that last payment failed - recurringPayment.LastPaymentFailed = true; - _orderService.UpdateRecurringPayment(recurringPayment); - - if (_paymentSettings.CancelRecurringPaymentsAfterFailedPayment) - { - //cancel recurring payment - CancelRecurringPayment(recurringPayment).ToList().ForEach(error => _logger.Error(error)); - - //notify a customer about cancelled payment - _workflowMessageService.SendRecurringPaymentCancelledCustomerNotification(recurringPayment, initialOrder.CustomerLanguageId); - } - else - //notify a customer about failed payment - _workflowMessageService.SendRecurringPaymentFailedCustomerNotification(recurringPayment, initialOrder.CustomerLanguageId); - } - - return processPaymentResult.Errors; - } - } - catch (Exception exc) - { - _logger.Error(string.Format("Error while processing recurring order. {0}", exc.Message), exc); - throw; - } - } - - /// - /// Cancels a recurring payment - /// - /// Recurring payment - public virtual IList CancelRecurringPayment(RecurringPayment recurringPayment) - { - if (recurringPayment == null) - throw new ArgumentNullException("recurringPayment"); - - var initialOrder = recurringPayment.InitialOrder; - if (initialOrder == null) - return new List { "Initial order could not be loaded" }; - - - var request = new CancelRecurringPaymentRequest(); - CancelRecurringPaymentResult result = null; - try - { - request.Order = initialOrder; - 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 = "Recurring payment has been cancelled", - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(initialOrder); - - //notify a store owner - _workflowMessageService - .SendRecurringPaymentCancelledStoreOwnerNotification(recurringPayment, - _localizationSettings.DefaultAdminLanguageId); - } - } - catch (Exception exc) - { - if (result == null) - result = new CancelRecurringPaymentResult(); - result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); - } - - - //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("Unable to cancel recurring payment. {0}", 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; - } - - /// - /// Gets a value indicating whether a customer can cancel recurring payment - /// - /// Customer - /// Recurring Payment - /// value indicating whether a customer can cancel recurring payment - public virtual bool CanCancelRecurringPayment(Customer customerToValidate, RecurringPayment recurringPayment) - { - if (recurringPayment == null) - return false; - - if (customerToValidate == null) - return false; - - var initialOrder = recurringPayment.InitialOrder; - if (initialOrder == null) - return false; - - var customer = recurringPayment.InitialOrder.Customer; - if (customer == null) - return false; - - if (initialOrder.OrderStatus == OrderStatus.Cancelled) - return false; - - if (!customerToValidate.IsAdmin()) - { - if (customer.Id != customerToValidate.Id) - return false; - } - - if (!recurringPayment.NextPaymentDate.HasValue) - return false; - - return true; - } - - - /// - /// Gets a value indicating whether a customer can retry last failed recurring payment - /// - /// Customer - /// Recurring Payment - /// True if a customer can retry payment; otherwise false - public virtual bool CanRetryLastRecurringPayment(Customer customer, RecurringPayment recurringPayment) - { - if (recurringPayment == null || customer == null) - return false; - - if (recurringPayment.InitialOrder == null || recurringPayment.InitialOrder.OrderStatus == OrderStatus.Cancelled) - return false; - - if (!recurringPayment.LastPaymentFailed || _paymentService.GetRecurringPaymentType(recurringPayment.InitialOrder.PaymentMethodSystemName) != RecurringPaymentType.Manual) - return false; - - if (recurringPayment.InitialOrder.Customer == null || (!customer.IsAdmin() && recurringPayment.InitialOrder.Customer.Id != customer.Id)) - return false; - - return true; - } - - - /// - /// Send a shipment - /// - /// Shipment - /// True to notify customer - public virtual void Ship(Shipment shipment, bool notifyCustomer) - { - if (shipment == null) - throw new ArgumentNullException("shipment"); - - var order = _orderService.GetOrderById(shipment.OrderId); - if (order == null) - throw new Exception("Order cannot be loaded"); - - if (shipment.ShippedDateUtc.HasValue) - throw new Exception("This shipment is already shipped"); - - shipment.ShippedDateUtc = DateTime.UtcNow; - _shipmentService.UpdateShipment(shipment); - - //process products with "Multiple warehouse" support enabled - foreach (var item in shipment.ShipmentItems) - { - var orderItem = _orderService.GetOrderItemById(item.OrderItemId); - _productService.BookReservedInventory(orderItem.Product, item.WarehouseId, -item.Quantity, - string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.Ship"), shipment.OrderId)); - } - - //check whether we have more items to ship - if (order.HasItemsToAddToShipment() || order.HasItemsToShip()) - 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("Shipment# {0} has been sent", shipment.Id), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - if (notifyCustomer) - { - //notify customer - int queuedEmailId = _workflowMessageService.SendShipmentSentCustomerNotification(shipment, order.CustomerLanguageId); - if (queuedEmailId > 0) - { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("\"Shipped\" email (to customer) has been queued. Queued email identifier: {0}.", queuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - } - } - - //event - _eventPublisher.PublishShipmentSent(shipment); - - //check order status - CheckOrderStatus(order); - } - - /// - /// Marks a shipment as delivered - /// - /// Shipment - /// True to notify customer - public virtual void Deliver(Shipment shipment, bool notifyCustomer) - { - if (shipment == null) - throw new ArgumentNullException("shipment"); - - var order = shipment.Order; - if (order == null) - throw new Exception("Order cannot be loaded"); - - if (!shipment.ShippedDateUtc.HasValue) - throw new Exception("This shipment is not shipped yet"); - - if (shipment.DeliveryDateUtc.HasValue) - throw new Exception("This shipment is already delivered"); - - shipment.DeliveryDateUtc = DateTime.UtcNow; - _shipmentService.UpdateShipment(shipment); - - if (!order.HasItemsToAddToShipment() && !order.HasItemsToShip() && !order.HasItemsToDeliver()) - order.ShippingStatusId = (int)ShippingStatus.Delivered; - _orderService.UpdateOrder(order); - - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("Shipment# {0} has been delivered", shipment.Id), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - if (notifyCustomer) - { - //send email notification - int queuedEmailId = _workflowMessageService.SendShipmentDeliveredCustomerNotification(shipment, order.CustomerLanguageId); - if (queuedEmailId > 0) - { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("\"Delivered\" email (to customer) has been queued. Queued email identifier: {0}.", queuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - } - } - - //event - _eventPublisher.PublishShipmentDelivered(shipment); - - //check order status - CheckOrderStatus(order); - } - - - - /// - /// Gets a value indicating whether cancel is allowed - /// - /// Order - /// A value indicating whether cancel is allowed - public virtual bool CanCancelOrder(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (order.OrderStatus == OrderStatus.Cancelled) - return false; - - return true; - } - - /// - /// Cancels order - /// - /// Order - /// True to notify customer - public virtual void CancelOrder(Order order, bool notifyCustomer) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (!CanCancelOrder(order)) - throw new NopException("Cannot do cancel for order."); - - //Cancel order - SetOrderStatus(order, OrderStatus.Cancelled, notifyCustomer); - - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = "Order has been cancelled", - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - //return (add) back redeemded reward points - ReturnBackRedeemedRewardPoints(order); - - //cancel recurring payments - var recurringPayments = _orderService.SearchRecurringPayments(initialOrderId: order.Id); - foreach (var rp in recurringPayments) - { - var errors = CancelRecurringPayment(rp); - //use "errors" variable? - } - - //Adjust inventory for already shipped shipments - //only products with "use multiple warehouses" - foreach (var shipment in order.Shipments) - { - foreach (var shipmentItem in shipment.ShipmentItems) - { - var orderItem = _orderService.GetOrderItemById(shipmentItem.OrderItemId); - if (orderItem == null) - continue; - - _productService.ReverseBookedInventory(orderItem.Product, shipmentItem, - string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.CancelOrder"), order.Id)); - } - } - //Adjust inventory - foreach (var orderItem in order.OrderItems) - { - _productService.AdjustInventory(orderItem.Product, orderItem.Quantity, orderItem.AttributesXml, - string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.CancelOrder"), order.Id)); - } - - _eventPublisher.Publish(new OrderCancelledEvent(order)); - - } - - /// - /// Gets a value indicating whether order can be marked as authorized - /// - /// Order - /// A value indicating whether order can be marked as authorized - public virtual bool CanMarkOrderAsAuthorized(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (order.OrderStatus == OrderStatus.Cancelled) - return false; - - if (order.PaymentStatus == PaymentStatus.Pending) - return true; - - return false; - } - - /// - /// Marks order as authorized - /// - /// Order - public virtual void MarkAsAuthorized(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - order.PaymentStatusId = (int)PaymentStatus.Authorized; - _orderService.UpdateOrder(order); - - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = "Order has been marked as authorized", - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - //check order status - CheckOrderStatus(order); - } - - - - /// - /// Gets a value indicating whether capture from admin panel is allowed - /// - /// Order - /// A value indicating whether capture from admin panel is allowed - public virtual bool CanCapture(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (order.OrderStatus == OrderStatus.Cancelled || - order.OrderStatus == OrderStatus.Pending) - return false; - - if (order.PaymentStatus == PaymentStatus.Authorized && - _paymentService.SupportCapture(order.PaymentMethodSystemName)) - return true; - - return false; - } - - /// - /// Capture an order (from admin panel) - /// - /// Order - /// A list of errors; empty list if no errors - public virtual IList Capture(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (!CanCapture(order)) - throw new NopException("Cannot do capture for order."); - - var request = new CapturePaymentRequest(); - CapturePaymentResult result = null; - try - { - //old info from placing order - request.Order = order; - result = _paymentService.Capture(request); - - if (result.Success) - { - var paidDate = order.PaidDateUtc; - 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 = "Order has been captured", - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - CheckOrderStatus(order); - - if (order.PaymentStatus == PaymentStatus.Paid) - { - ProcessOrderPaid(order); - } - } - } - catch (Exception exc) - { - if (result == null) - result = new CapturePaymentResult(); - result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); - } - - - //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); - - //log it - string logError = string.Format("Error capturing order #{0}. Error: {1}", order.Id, error); - _logger.InsertLog(LogLevel.Error, logError, logError); - } - return result.Errors; - } - - /// - /// Gets a value indicating whether order can be marked as paid - /// - /// Order - /// A value indicating whether order can be marked as paid - public virtual bool CanMarkOrderAsPaid(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (order.OrderStatus == OrderStatus.Cancelled) - return false; - - if (order.PaymentStatus == PaymentStatus.Paid || - order.PaymentStatus == PaymentStatus.Refunded || - order.PaymentStatus == PaymentStatus.Voided) - return false; - - return true; - } - - /// - /// Marks order as paid - /// - /// Order - public virtual void MarkOrderAsPaid(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (!CanMarkOrderAsPaid(order)) - throw new NopException("You can't mark this order as paid"); - - order.PaymentStatusId = (int)PaymentStatus.Paid; - order.PaidDateUtc = DateTime.UtcNow; - _orderService.UpdateOrder(order); - - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = "Order has been marked as paid", - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - CheckOrderStatus(order); - - if (order.PaymentStatus == PaymentStatus.Paid) - { - ProcessOrderPaid(order); - } - } - - - - /// - /// Gets a value indicating whether refund from admin panel is allowed - /// - /// Order - /// A value indicating whether refund from admin panel is allowed - public virtual bool CanRefund(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (order.OrderTotal == decimal.Zero) - return false; - - //refund cannot be made if previously a partial refund has been already done. only other partial refund can be made in this case - if (order.RefundedAmount > decimal.Zero) - return false; - - //uncomment the lines below in order to disallow this operation for cancelled orders - //if (order.OrderStatus == OrderStatus.Cancelled) - // return false; - - if (order.PaymentStatus == PaymentStatus.Paid && - _paymentService.SupportRefund(order.PaymentMethodSystemName)) - return true; - - return false; - } - - /// - /// Refunds an order (from admin panel) - /// - /// Order - /// A list of errors; empty list if no errors - public virtual IList Refund(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (!CanRefund(order)) - throw new NopException("Cannot do refund for order."); - - var request = new RefundPaymentRequest(); - RefundPaymentResult result = null; - try - { - request.Order = order; - request.AmountToRefund = order.OrderTotal; - request.IsPartialRefund = false; - result = _paymentService.Refund(request); - if (result.Success) - { - //total amount refunded - decimal totalAmountRefunded = order.RefundedAmount + request.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("Order has been refunded. Amount = {0}", request.AmountToRefund), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - //check order status - CheckOrderStatus(order); - - //notifications - var orderRefundedStoreOwnerNotificationQueuedEmailId = _workflowMessageService.SendOrderRefundedStoreOwnerNotification(order, request.AmountToRefund, _localizationSettings.DefaultAdminLanguageId); - if (orderRefundedStoreOwnerNotificationQueuedEmailId > 0) - { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("\"Order refunded\" email (to store owner) has been queued. Queued email identifier: {0}.", orderRefundedStoreOwnerNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - } - var orderRefundedCustomerNotificationQueuedEmailId = _workflowMessageService.SendOrderRefundedCustomerNotification(order, request.AmountToRefund, order.CustomerLanguageId); - if (orderRefundedCustomerNotificationQueuedEmailId > 0) - { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("\"Order refunded\" email (to customer) has been queued. Queued email identifier: {0}.", orderRefundedCustomerNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - } - - //raise event - _eventPublisher.Publish(new OrderRefundedEvent(order, request.AmountToRefund)); - } - - } - catch (Exception exc) - { - if (result == null) - result = new RefundPaymentResult(); - result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); - } - - //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 refund order. {0}", error), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - //log it - string logError = string.Format("Error refunding order #{0}. Error: {1}", order.Id, error); - _logger.InsertLog(LogLevel.Error, logError, logError); - } - return result.Errors; - } - - /// - /// Gets a value indicating whether order can be marked as refunded - /// - /// Order - /// A value indicating whether order can be marked as refunded - public virtual bool CanRefundOffline(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (order.OrderTotal == decimal.Zero) - return false; - - //refund cannot be made if previously a partial refund has been already done. only other partial refund can be made in this case - if (order.RefundedAmount > decimal.Zero) - return false; - - //uncomment the lines below in order to disallow this operation for cancelled orders - //if (order.OrderStatus == OrderStatus.Cancelled) - // return false; - - if (order.PaymentStatus == PaymentStatus.Paid) - return true; - - return false; - } - - /// - /// Refunds an order (offline) - /// - /// Order - public virtual void RefundOffline(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (!CanRefundOffline(order)) - throw new NopException("You can't refund this order"); - - //amout to refund - decimal amountToRefund = order.OrderTotal; - - //total amount refunded - decimal totalAmountRefunded = order.RefundedAmount + amountToRefund; - - //update order info - order.RefundedAmount = totalAmountRefunded; - order.PaymentStatus = PaymentStatus.Refunded; - _orderService.UpdateOrder(order); - - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("Order has been marked as refunded. Amount = {0}", amountToRefund), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - //check order status - CheckOrderStatus(order); - - //notifications - var orderRefundedStoreOwnerNotificationQueuedEmailId = _workflowMessageService.SendOrderRefundedStoreOwnerNotification(order, amountToRefund, _localizationSettings.DefaultAdminLanguageId); - if (orderRefundedStoreOwnerNotificationQueuedEmailId > 0) - { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("\"Order refunded\" email (to store owner) has been queued. Queued email identifier: {0}.", orderRefundedStoreOwnerNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - } - var orderRefundedCustomerNotificationQueuedEmailId = _workflowMessageService.SendOrderRefundedCustomerNotification(order, amountToRefund, order.CustomerLanguageId); - if (orderRefundedCustomerNotificationQueuedEmailId > 0) - { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("\"Order refunded\" email (to customer) has been queued. Queued email identifier: {0}.", orderRefundedCustomerNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - } - - //raise event - _eventPublisher.Publish(new OrderRefundedEvent(order, amountToRefund)); - } - - /// - /// Gets a value indicating whether partial refund from admin panel is allowed - /// - /// Order - /// Amount to refund - /// A value indicating whether refund from admin panel is allowed - public virtual bool CanPartiallyRefund(Order order, decimal amountToRefund) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (order.OrderTotal == decimal.Zero) - return false; - - //uncomment the lines below in order to allow this operation for cancelled orders - //if (order.OrderStatus == OrderStatus.Cancelled) - // return false; - - decimal canBeRefunded = order.OrderTotal - order.RefundedAmount; - if (canBeRefunded <= decimal.Zero) - return false; - - if (amountToRefund > canBeRefunded) - return false; - - if ((order.PaymentStatus == PaymentStatus.Paid || - order.PaymentStatus == PaymentStatus.PartiallyRefunded) && - _paymentService.SupportPartiallyRefund(order.PaymentMethodSystemName)) - return true; - - return false; - } - - /// - /// Partially refunds an order (from admin panel) - /// - /// Order - /// Amount to refund - /// A list of errors; empty list if no errors - public virtual IList PartiallyRefund(Order order, decimal amountToRefund) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (!CanPartiallyRefund(order, amountToRefund)) - throw new NopException("Cannot do partial refund for order."); - - var request = new RefundPaymentRequest(); - RefundPaymentResult result = null; - try - { - request.Order = order; - request.AmountToRefund = amountToRefund; - request.IsPartialRefund = true; - - result = _paymentService.Refund(request); - - if (result.Success) - { - //total amount refunded - decimal totalAmountRefunded = order.RefundedAmount + amountToRefund; - - //update order info - order.RefundedAmount = totalAmountRefunded; - //mark payment status as 'Refunded' if the order total amount is fully refunded - order.PaymentStatus = order.OrderTotal == totalAmountRefunded && result.NewPaymentStatus == PaymentStatus.PartiallyRefunded ? PaymentStatus.Refunded : result.NewPaymentStatus; - _orderService.UpdateOrder(order); - - - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("Order has been partially refunded. Amount = {0}", amountToRefund), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - //check order status - CheckOrderStatus(order); - - //notifications - var orderRefundedStoreOwnerNotificationQueuedEmailId = _workflowMessageService.SendOrderRefundedStoreOwnerNotification(order, amountToRefund, _localizationSettings.DefaultAdminLanguageId); - if (orderRefundedStoreOwnerNotificationQueuedEmailId > 0) - { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("\"Order refunded\" email (to store owner) has been queued. Queued email identifier: {0}.", orderRefundedStoreOwnerNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - } - var orderRefundedCustomerNotificationQueuedEmailId = _workflowMessageService.SendOrderRefundedCustomerNotification(order, amountToRefund, order.CustomerLanguageId); - if (orderRefundedCustomerNotificationQueuedEmailId > 0) - { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("\"Order refunded\" email (to customer) has been queued. Queued email identifier: {0}.", orderRefundedCustomerNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - } - - //raise event - _eventPublisher.Publish(new OrderRefundedEvent(order, amountToRefund)); - } - } - catch (Exception exc) - { - if (result == null) - result = new RefundPaymentResult(); - result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); - } - - //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 partially refund order. {0}", error), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - //log it - string logError = string.Format("Error refunding order #{0}. Error: {1}", order.Id, error); - _logger.InsertLog(LogLevel.Error, logError, logError); - } - return result.Errors; - } - - /// - /// Gets a value indicating whether order can be marked as partially refunded - /// - /// Order - /// Amount to refund - /// A value indicating whether order can be marked as partially refunded - public virtual bool CanPartiallyRefundOffline(Order order, decimal amountToRefund) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (order.OrderTotal == decimal.Zero) - return false; - - //uncomment the lines below in order to allow this operation for cancelled orders - //if (order.OrderStatus == OrderStatus.Cancelled) - // return false; - - decimal canBeRefunded = order.OrderTotal - order.RefundedAmount; - if (canBeRefunded <= decimal.Zero) - return false; - - if (amountToRefund > canBeRefunded) - return false; - - if (order.PaymentStatus == PaymentStatus.Paid || - order.PaymentStatus == PaymentStatus.PartiallyRefunded) - return true; - - return false; - } - - /// - /// Partially refunds an order (offline) - /// - /// Order - /// Amount to refund - public virtual void PartiallyRefundOffline(Order order, decimal amountToRefund) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (!CanPartiallyRefundOffline(order, amountToRefund)) - throw new NopException("You can't partially refund (offline) this order"); - - //total amount refunded - decimal totalAmountRefunded = order.RefundedAmount + amountToRefund; - - //update order info - order.RefundedAmount = totalAmountRefunded; - //mark payment status as 'Refunded' if the order total amount is fully refunded - order.PaymentStatus = order.OrderTotal == totalAmountRefunded ? PaymentStatus.Refunded : PaymentStatus.PartiallyRefunded; - _orderService.UpdateOrder(order); - - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("Order has been marked as partially refunded. Amount = {0}", amountToRefund), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - //check order status - CheckOrderStatus(order); - - //notifications - var orderRefundedStoreOwnerNotificationQueuedEmailId = _workflowMessageService.SendOrderRefundedStoreOwnerNotification(order, amountToRefund, _localizationSettings.DefaultAdminLanguageId); - if (orderRefundedStoreOwnerNotificationQueuedEmailId > 0) - { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("\"Order refunded\" email (to store owner) has been queued. Queued email identifier: {0}.", orderRefundedStoreOwnerNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - } - var orderRefundedCustomerNotificationQueuedEmailId = _workflowMessageService.SendOrderRefundedCustomerNotification(order, amountToRefund, order.CustomerLanguageId); - if (orderRefundedCustomerNotificationQueuedEmailId > 0) - { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format("\"Order refunded\" email (to customer) has been queued. Queued email identifier: {0}.", orderRefundedCustomerNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - } - //raise event - _eventPublisher.Publish(new OrderRefundedEvent(order, amountToRefund)); - } - - - - /// - /// Gets a value indicating whether void from admin panel is allowed - /// - /// Order - /// A value indicating whether void from admin panel is allowed - public virtual bool CanVoid(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (order.OrderTotal == decimal.Zero) - return false; - - //uncomment the lines below in order to allow this operation for cancelled orders - //if (order.OrderStatus == OrderStatus.Cancelled) - // return false; - - if (order.PaymentStatus == PaymentStatus.Authorized && - _paymentService.SupportVoid(order.PaymentMethodSystemName)) - return true; - - return false; - } - - /// - /// Voids order (from admin panel) - /// - /// Order - /// Voided order - public virtual IList Void(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (!CanVoid(order)) - throw new NopException("Cannot do void for order."); - - var request = new VoidPaymentRequest(); - VoidPaymentResult result = null; - try - { - request.Order = order; - result = _paymentService.Void(request); - - if (result.Success) - { - //update order info - order.PaymentStatus = result.NewPaymentStatus; - _orderService.UpdateOrder(order); - - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = "Order has been voided", - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - //check order status - CheckOrderStatus(order); - } - } - catch (Exception exc) - { - if (result == null) - result = new VoidPaymentResult(); - result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); - } - - //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 voiding order. {0}", error), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - //log it - string logError = string.Format("Error voiding order #{0}. Error: {1}", order.Id, error); - _logger.InsertLog(LogLevel.Error, logError, logError); - } - return result.Errors; - } - - /// - /// Gets a value indicating whether order can be marked as voided - /// - /// Order - /// A value indicating whether order can be marked as voided - public virtual bool CanVoidOffline(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (order.OrderTotal == decimal.Zero) - return false; - - //uncomment the lines below in order to allow this operation for cancelled orders - //if (order.OrderStatus == OrderStatus.Cancelled) - // return false; - - if (order.PaymentStatus == PaymentStatus.Authorized) - return true; - - return false; - } - - /// - /// Voids order (offline) - /// - /// Order - public virtual void VoidOffline(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - if (!CanVoidOffline(order)) - throw new NopException("You can't void this order"); - - order.PaymentStatusId = (int)PaymentStatus.Voided; - _orderService.UpdateOrder(order); - - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = "Order has been marked as voided", - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - - //check orer status - CheckOrderStatus(order); - } - - - - /// - /// Place order items in current user shopping cart. - /// - /// The order - public virtual void ReOrder(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - //move shopping cart items (if possible) - foreach (var orderItem in order.OrderItems) - { - _shoppingCartService.AddToCart(order.Customer, orderItem.Product, - ShoppingCartType.ShoppingCart, order.StoreId, - orderItem.AttributesXml, orderItem.UnitPriceExclTax, - orderItem.RentalStartDateUtc, orderItem.RentalEndDateUtc, - orderItem.Quantity, false); - } - - //set checkout attributes - //comment the code below if you want to disable this functionality - _genericAttributeService.SaveAttribute(order.Customer, SystemCustomerAttributeNames.CheckoutAttributes, order.CheckoutAttributesXml, order.StoreId); - } - - /// - /// Check whether return request is allowed - /// - /// Order - /// Result - public virtual bool IsReturnRequestAllowed(Order order) - { - if (!_orderSettings.ReturnRequestsEnabled) - return false; - - if (order == null || order.Deleted) - return false; - - //status should be compelte - if (order.OrderStatus != OrderStatus.Complete) - return false; - - //validate allowed number of days - if (_orderSettings.NumberOfDaysReturnRequestAvailable > 0) - { - var daysPassed = (DateTime.UtcNow - order.CreatedOnUtc).TotalDays; - if (daysPassed >= _orderSettings.NumberOfDaysReturnRequestAvailable) - return false; - } - - //ensure that we have at least one returnable product - return order.OrderItems.Any(oi => !oi.Product.NotReturnable); - } - - - - /// - /// Valdiate minimum order sub-total amount - /// - /// Shopping cart - /// true - OK; false - minimum order sub-total amount is not reached - public virtual bool ValidateMinOrderSubtotalAmount(IList cart) - { - if (cart == null) - throw new ArgumentNullException("cart"); - - //min order amount sub-total validation - if (cart.Any() && _orderSettings.MinOrderSubtotalAmount > decimal.Zero) - { - //subtotal - decimal orderSubTotalDiscountAmountBase; - List orderSubTotalAppliedDiscounts; - decimal subTotalWithoutDiscountBase; - decimal subTotalWithDiscountBase; - _orderTotalCalculationService.GetShoppingCartSubTotal(cart, _orderSettings.MinOrderSubtotalAmountIncludingTax, - out orderSubTotalDiscountAmountBase, out orderSubTotalAppliedDiscounts, - out subTotalWithoutDiscountBase, out subTotalWithDiscountBase); - - if (subTotalWithoutDiscountBase < _orderSettings.MinOrderSubtotalAmount) - return false; - } - - return true; - } - - /// - /// Valdiate minimum order total amount - /// - /// Shopping cart - /// true - OK; false - minimum order total amount is not reached - public virtual bool ValidateMinOrderTotalAmount(IList cart) - { - if (cart == null) - throw new ArgumentNullException("cart"); - - if (cart.Any() && _orderSettings.MinOrderTotalAmount > decimal.Zero) - { - decimal? shoppingCartTotalBase = _orderTotalCalculationService.GetShoppingCartTotal(cart); - if (shoppingCartTotalBase.HasValue && shoppingCartTotalBase.Value < _orderSettings.MinOrderTotalAmount) - return false; - } - - return true; - } - - /// - /// Get invoice ID - /// - /// - public virtual string GetInvoiceId() - { - var actYear = DateTime.UtcNow.Year; - int ident = _orderSettings.InvoiceIdent; - if (_orderSettings.InvoiceYear < actYear) - { - // Reset counter if a new year - ident = 1; - _orderSettings.InvoiceYear = actYear; - _settingService.SetSetting("ordersettings.invoiceyear", _orderSettings.InvoiceYear); - } - else - { - ident += 1; - } - _orderSettings.InvoiceIdent = ident; - // Update settings - _settingService.SetSetting("ordersettings.invoiceident", _orderSettings.InvoiceIdent); - - return string.Format("I-{0}.{1}", DateTime.UtcNow.Year, ident.ToString("D5")); - } - /// - /// Gets a value indicating whether payment workflow is required - /// - /// Shopping cart - /// A value indicating reward points should be used; null to detect current choice of the customer - /// true - OK; false - minimum order total amount is not reached - public virtual bool IsPaymentWorkflowRequired(IList cart, bool? useRewardPoints = null) - { - if (cart == null) - throw new ArgumentNullException("cart"); - - bool result = true; - - //check whether order total equals zero - decimal? shoppingCartTotalBase = _orderTotalCalculationService.GetShoppingCartTotal(cart, useRewardPoints: useRewardPoints); - if (shoppingCartTotalBase.HasValue && shoppingCartTotalBase.Value == decimal.Zero) - result = false; - return result; - } - - #endregion - } -} +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Nop.Core; +using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Common; +using Nop.Core.Domain.Customers; +using Nop.Core.Domain.Directory; +using Nop.Core.Domain.Discounts; +using Nop.Core.Domain.Localization; +using Nop.Core.Domain.Logging; +using Nop.Core.Domain.Orders; +using Nop.Core.Domain.Payments; +using Nop.Core.Domain.Shipping; +using Nop.Core.Domain.Tax; +using Nop.Core.Domain.Vendors; +using Nop.Services.Affiliates; +using Nop.Services.Catalog; +using Nop.Services.Common; +using Nop.Services.Customers; +using Nop.Services.Directory; +using Nop.Services.Discounts; +using Nop.Services.Events; +using Nop.Services.Localization; +using Nop.Services.Logging; +using Nop.Services.Messages; +using Nop.Services.Payments; +using Nop.Services.Security; +using Nop.Services.Shipping; +using Nop.Services.Tax; +using Nop.Services.Vendors; +using Nop.Services.Configuration; +using System.Text; + +namespace Nop.Services.Orders +{ + /// + /// Order processing service + /// + public partial class OrderProcessingService : IOrderProcessingService + { + #region Fields + + private readonly IOrderService _orderService; + private readonly IWebHelper _webHelper; + private readonly ILocalizationService _localizationService; + private readonly ILanguageService _languageService; + private readonly IProductService _productService; + private readonly IPaymentService _paymentService; + private readonly ILogger _logger; + private readonly IOrderTotalCalculationService _orderTotalCalculationService; + private readonly IPriceCalculationService _priceCalculationService; + private readonly IPriceFormatter _priceFormatter; + private readonly IProductAttributeParser _productAttributeParser; + private readonly IProductAttributeFormatter _productAttributeFormatter; + private readonly IGiftCardService _giftCardService; + private readonly IShoppingCartService _shoppingCartService; + private readonly ICheckoutAttributeFormatter _checkoutAttributeFormatter; + private readonly IShippingService _shippingService; + private readonly IShipmentService _shipmentService; + private readonly ITaxService _taxService; + private readonly ICustomerService _customerService; + private readonly IDiscountService _discountService; + private readonly IEncryptionService _encryptionService; + private readonly IWorkContext _workContext; + private readonly IWorkflowMessageService _workflowMessageService; + private readonly IVendorService _vendorService; + private readonly ICustomerActivityService _customerActivityService; + private readonly ICurrencyService _currencyService; + private readonly IAffiliateService _affiliateService; + private readonly IEventPublisher _eventPublisher; + private readonly IPdfService _pdfService; + private readonly IRewardPointService _rewardPointService; + private readonly IGenericAttributeService _genericAttributeService; + private readonly ICountryService _countryService; + private readonly IStateProvinceService _stateProvinceService; + + private readonly ShippingSettings _shippingSettings; + private readonly PaymentSettings _paymentSettings; + private readonly RewardPointsSettings _rewardPointsSettings; + private readonly OrderSettings _orderSettings; + private readonly TaxSettings _taxSettings; + private readonly LocalizationSettings _localizationSettings; + private readonly CurrencySettings _currencySettings; + private readonly ICustomNumberFormatter _customNumberFormatter; + + private readonly ISettingService _settingService; + + #endregion + + #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 + /// Workflow message service + /// Vendor service + /// Customer activity service + /// Currency service + /// Affiliate service + /// Event published + /// PDF service + /// Reward point service + /// Generic attribute service + /// Country service + /// Payment settings + /// Shipping settings + /// Reward points settings + /// Order settings + /// Tax settings + /// Localization settings + /// Currency settings + /// Setting service + /// Custom number formatter + public OrderProcessingService(IOrderService orderService, + IWebHelper webHelper, + ILocalizationService localizationService, + ILanguageService languageService, + IProductService productService, + IPaymentService paymentService, + ILogger logger, + IOrderTotalCalculationService orderTotalCalculationService, + IPriceCalculationService priceCalculationService, + IPriceFormatter priceFormatter, + IProductAttributeParser productAttributeParser, + IProductAttributeFormatter productAttributeFormatter, + IGiftCardService giftCardService, + IShoppingCartService shoppingCartService, + ICheckoutAttributeFormatter checkoutAttributeFormatter, + IShippingService shippingService, + IShipmentService shipmentService, + ITaxService taxService, + ICustomerService customerService, + IDiscountService discountService, + IEncryptionService encryptionService, + IWorkContext workContext, + IWorkflowMessageService workflowMessageService, + IVendorService vendorService, + ICustomerActivityService customerActivityService, + ICurrencyService currencyService, + IAffiliateService affiliateService, + IEventPublisher eventPublisher, + IPdfService pdfService, + IRewardPointService rewardPointService, + IGenericAttributeService genericAttributeService, + ICountryService countryService, + IStateProvinceService stateProvinceService, + ShippingSettings shippingSettings, + PaymentSettings paymentSettings, + RewardPointsSettings rewardPointsSettings, + OrderSettings orderSettings, + TaxSettings taxSettings, + LocalizationSettings localizationSettings, + CurrencySettings currencySettings, + ISettingService settingService, + ICustomNumberFormatter customNumberFormatter) + { + this._orderService = orderService; + this._webHelper = webHelper; + this._localizationService = localizationService; + this._languageService = languageService; + this._productService = productService; + this._paymentService = paymentService; + this._logger = logger; + this._orderTotalCalculationService = orderTotalCalculationService; + this._priceCalculationService = priceCalculationService; + this._priceFormatter = priceFormatter; + this._productAttributeParser = productAttributeParser; + this._productAttributeFormatter = productAttributeFormatter; + this._giftCardService = giftCardService; + this._shoppingCartService = shoppingCartService; + this._checkoutAttributeFormatter = checkoutAttributeFormatter; + this._workContext = workContext; + this._workflowMessageService = workflowMessageService; + this._vendorService = vendorService; + this._shippingService = shippingService; + this._shipmentService = shipmentService; + this._taxService = taxService; + this._customerService = customerService; + this._discountService = discountService; + this._encryptionService = encryptionService; + this._customerActivityService = customerActivityService; + this._currencyService = currencyService; + this._affiliateService = affiliateService; + this._eventPublisher = eventPublisher; + this._pdfService = pdfService; + this._rewardPointService = rewardPointService; + this._genericAttributeService = genericAttributeService; + this._countryService = countryService; + this._stateProvinceService = stateProvinceService; + + this._paymentSettings = paymentSettings; + this._shippingSettings = shippingSettings; + this._rewardPointsSettings = rewardPointsSettings; + this._orderSettings = orderSettings; + this._taxSettings = taxSettings; + this._localizationSettings = localizationSettings; + this._currencySettings = currencySettings; + this._settingService = settingService; + this._customNumberFormatter = customNumberFormatter; + } + + #endregion + + #region Nested classes + + protected class PlaceOrderContainter + { + public PlaceOrderContainter() + { + this.Cart = new List(); + this.AppliedDiscounts = new List(); + this.AppliedGiftCards = new List(); + } + + public Customer Customer { get; set; } + public Language CustomerLanguage { get; set; } + public int AffiliateId { get; set; } + public TaxDisplayType CustomerTaxDisplayType {get; set; } + public string CustomerCurrencyCode { get; set; } + public decimal CustomerCurrencyRate { get; set; } + + public Address BillingAddress { get; set; } + public Address ShippingAddress {get; set; } + public ShippingStatus ShippingStatus { get; set; } + public string ShippingMethodName { get; set; } + public string ShippingRateComputationMethodSystemName { get; set; } + public bool PickUpInStore { get; set; } + public Address PickupAddress { get; set; } + + public bool IsRecurringShoppingCart { get; set; } + //initial order (used with recurring payments) + public Order InitialOrder { get; set; } + + public string CheckoutAttributeDescription { get; set; } + public string CheckoutAttributesXml { get; set; } + + public IList Cart { get; set; } + public List AppliedDiscounts { get; set; } + public List AppliedGiftCards { get; set; } + + public decimal OrderSubTotalInclTax { get; set; } + public decimal OrderSubTotalExclTax { get; set; } + public decimal OrderSubTotalDiscountInclTax { get; set; } + public decimal OrderSubTotalDiscountExclTax { get; set; } + public decimal OrderShippingTotalInclTax { get; set; } + public decimal OrderShippingTotalExclTax { get; set; } + public decimal OrderShippingTotalNonTaxable { get; set; } + public decimal PaymentAdditionalFeeInclTax {get; set; } + public decimal PaymentAdditionalFeeExclTax { get; set; } + public decimal PaymentAdditionalFeeNonTaxable { get; set; } + public decimal OrderTaxTotal {get; set; } + public string VatNumber {get; set; } + public string TaxRates {get; set; } + public decimal OrderDiscountAmount { get; set; } + public RewardPoints RedeemedRewardPoints { get; set; } + public decimal OrderTotal { get; set; } + public decimal OrderAmount { get; set; } //MF 09.12.16 + public decimal OrderAmountIncl { get; set; } //MF 09.12.16 + public decimal OrderDiscountAmountIncl { get; set; } + public decimal EarnedRewardPointsBaseAmountIncl { get; set; } + public decimal EarnedRewardPointsBaseAmountExcl { get; set; } + + } + + #endregion + + #region Utilities + + /// + /// Prepare details to place an order. It also sets some properties to "processPaymentRequest" + /// + /// Process payment request + /// Details + protected virtual PlaceOrderContainter PreparePlaceOrderDetails(ProcessPaymentRequest processPaymentRequest) + { + var details = new PlaceOrderContainter(); + + //customer + details.Customer = _customerService.GetCustomerById(processPaymentRequest.CustomerId); + if (details.Customer == null) + throw new ArgumentException("Customer is not set"); + + //affiliate + var affiliate = _affiliateService.GetAffiliateById(details.Customer.AffiliateId); + if (affiliate != null && affiliate.Active && !affiliate.Deleted) + details.AffiliateId = affiliate.Id; + + //check whether customer is guest + if (details.Customer.IsGuest() && !_orderSettings.AnonymousCheckoutAllowed) + throw new NopException("Anonymous checkout is not allowed"); + + //customer currency + var currencyTmp = _currencyService.GetCurrencyById( + details.Customer.GetAttribute(SystemCustomerAttributeNames.CurrencyId, processPaymentRequest.StoreId)); + var customerCurrency = (currencyTmp != null && currencyTmp.Published) ? currencyTmp : _workContext.WorkingCurrency; + var primaryStoreCurrency = _currencyService.GetCurrencyById(_currencySettings.PrimaryStoreCurrencyId); + details.CustomerCurrencyCode = customerCurrency.CurrencyCode; + details.CustomerCurrencyRate = customerCurrency.Rate / primaryStoreCurrency.Rate; + + //customer language + details.CustomerLanguage = _languageService.GetLanguageById( + details.Customer.GetAttribute(SystemCustomerAttributeNames.LanguageId, processPaymentRequest.StoreId)); + if (details.CustomerLanguage == null || !details.CustomerLanguage.Published) + details.CustomerLanguage = _workContext.WorkingLanguage; + + //billing address + if (details.Customer.BillingAddress == null) + throw new NopException("Billing address is not provided"); + + if (!CommonHelper.IsValidEmail(details.Customer.BillingAddress.Email)) + throw new NopException("Email is not valid"); + + details.BillingAddress = (Address)details.Customer.BillingAddress.Clone(); + if (details.BillingAddress.Country != null && !details.BillingAddress.Country.AllowsBilling) + throw new NopException(string.Format("Country '{0}' is not allowed for billing", details.BillingAddress.Country.Name)); + + //checkout attributes + details.CheckoutAttributesXml = details.Customer.GetAttribute(SystemCustomerAttributeNames.CheckoutAttributes, processPaymentRequest.StoreId); + details.CheckoutAttributeDescription = _checkoutAttributeFormatter.FormatAttributes(details.CheckoutAttributesXml, details.Customer); + + //load shopping cart + details.Cart = details.Customer.ShoppingCartItems.Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart) + .LimitPerStore(processPaymentRequest.StoreId).ToList(); + + if (!details.Cart.Any()) + throw new NopException("Cart is empty"); + + //validate the entire shopping cart + var warnings = _shoppingCartService.GetShoppingCartWarnings(details.Cart, details.CheckoutAttributesXml, true); + if (warnings.Any()) + throw new NopException(warnings.Aggregate(string.Empty, (current, next) => string.Format("{0}{1};", current, next))); + + //validate individual cart items + foreach (var sci in details.Cart) + { + var sciWarnings = _shoppingCartService.GetShoppingCartItemWarnings(details.Customer, + sci.ShoppingCartType, sci.Product, processPaymentRequest.StoreId, sci.AttributesXml, + sci.CustomerEnteredPrice, sci.RentalStartDateUtc, sci.RentalEndDateUtc, sci.Quantity, false); + if (sciWarnings.Any()) + throw new NopException(sciWarnings.Aggregate(string.Empty, (current, next) => string.Format("{0}{1};", current, next))); + } + + //min totals validation + if (!ValidateMinOrderSubtotalAmount(details.Cart)) + { + var minOrderSubtotalAmount = _currencyService.ConvertFromPrimaryStoreCurrency(_orderSettings.MinOrderSubtotalAmount, _workContext.WorkingCurrency); + throw new NopException(string.Format(_localizationService.GetResource("Checkout.MinOrderSubtotalAmount"), + _priceFormatter.FormatPrice(minOrderSubtotalAmount, true, false))); + } + + if (!ValidateMinOrderTotalAmount(details.Cart)) + { + var minOrderTotalAmount = _currencyService.ConvertFromPrimaryStoreCurrency(_orderSettings.MinOrderTotalAmount, _workContext.WorkingCurrency); + throw new NopException(string.Format(_localizationService.GetResource("Checkout.MinOrderTotalAmount"), + _priceFormatter.FormatPrice(minOrderTotalAmount, true, false))); + } + + //tax display type + if (_taxSettings.AllowCustomersToSelectTaxDisplayType) + details.CustomerTaxDisplayType = (TaxDisplayType)details.Customer.GetAttribute(SystemCustomerAttributeNames.TaxDisplayTypeId, processPaymentRequest.StoreId); + else + details.CustomerTaxDisplayType = _taxSettings.TaxDisplayType; + + + //order total (and applied discounts, gift cards, reward points) + List appliedGiftCardsIncl; + List appliedGiftCardsExcl; + List orderAppliedDiscounts; + List subTotalAppliedDiscounts; + List shippingAppliedDiscounts; + decimal orderDiscountAmount; + RewardPoints redeemedRewardPointsIncl; + RewardPoints redeemedRewardPointsExcl; + TaxSummary taxSummaryIncl; + TaxSummary taxSummaryExcl; + decimal earnedRewardPointsBaseAmountIncl; + decimal earnedRewardPointsBaseAmountExcl; + + //calculate two times to get correct incl./excl. tax + var orderTotalIncl = _orderTotalCalculationService.GetShoppingCartTotal(details.Cart, out orderDiscountAmount, + out orderAppliedDiscounts, out subTotalAppliedDiscounts, out shippingAppliedDiscounts, + out appliedGiftCardsIncl, out redeemedRewardPointsIncl, + out taxSummaryIncl, out earnedRewardPointsBaseAmountIncl, true); + + var orderTotalExcl = _orderTotalCalculationService.GetShoppingCartTotal(details.Cart, out orderDiscountAmount, + out orderAppliedDiscounts, out subTotalAppliedDiscounts, out shippingAppliedDiscounts, + out appliedGiftCardsExcl, out redeemedRewardPointsExcl, + out taxSummaryExcl, out earnedRewardPointsBaseAmountExcl, false); + + //sub total (incl tax) (excl tax) + details.OrderSubTotalInclTax = taxSummaryIncl.TotalSubTotalAmount; + details.OrderSubTotalDiscountInclTax = taxSummaryIncl.TotalSubTotalDiscAmount; + + details.OrderSubTotalExclTax = taxSummaryExcl.TotalSubTotalAmount; + details.OrderSubTotalDiscountExclTax = taxSummaryExcl.TotalSubTotalDiscAmount; + + + //discount history + foreach (var disc in subTotalAppliedDiscounts) + if (!details.AppliedDiscounts.ContainsDiscount(disc)) + details.AppliedDiscounts.Add(disc); + + //shipping info + if (details.Cart.RequiresShipping()) + { + var pickupPoint = details.Customer.GetAttribute(SystemCustomerAttributeNames.SelectedPickupPoint, processPaymentRequest.StoreId); + if (_shippingSettings.AllowPickUpInStore && pickupPoint != null) + { + var country = _countryService.GetCountryByTwoLetterIsoCode(pickupPoint.CountryCode); + var state = _stateProvinceService.GetStateProvinceByAbbreviation(pickupPoint.StateAbbreviation); + + details.PickUpInStore = true; + details.PickupAddress = new Address + { + Address1 = pickupPoint.Address, + City = pickupPoint.City, + Country = country, + StateProvince = state, + ZipPostalCode = pickupPoint.ZipPostalCode, + CreatedOnUtc = DateTime.UtcNow, + }; + } + else + { + if (details.Customer.ShippingAddress == null) + throw new NopException("Shipping address is not provided"); + + if (!CommonHelper.IsValidEmail(details.Customer.ShippingAddress.Email)) + throw new NopException("Email is not valid"); + + //clone shipping address + details.ShippingAddress = (Address)details.Customer.ShippingAddress.Clone(); + if (details.ShippingAddress.Country != null && !details.ShippingAddress.Country.AllowsShipping) + throw new NopException(string.Format("Country '{0}' is not allowed for shipping", details.ShippingAddress.Country.Name)); + } + + var shippingOption = details.Customer.GetAttribute(SystemCustomerAttributeNames.SelectedShippingOption, processPaymentRequest.StoreId); + if (shippingOption != null) + { + details.ShippingMethodName = shippingOption.Name; + details.ShippingRateComputationMethodSystemName = shippingOption.ShippingRateComputationMethodSystemName; + } + + details.ShippingStatus = ShippingStatus.NotYetShipped; + } + else + details.ShippingStatus = ShippingStatus.ShippingNotRequired; + + //shipping total + details.OrderShippingTotalInclTax = taxSummaryIncl.TotalShippingAmountTaxable ?? decimal.Zero; + details.OrderShippingTotalExclTax = taxSummaryExcl.TotalShippingAmountTaxable ?? decimal.Zero; + details.OrderShippingTotalNonTaxable = taxSummaryIncl.TotalShippingAmountNonTaxable ?? decimal.Zero; + + foreach (var disc in shippingAppliedDiscounts) + if (!details.AppliedDiscounts.ContainsDiscount(disc)) + details.AppliedDiscounts.Add(disc); + + //payment total + details.PaymentAdditionalFeeInclTax = taxSummaryIncl.TotalPaymentFeeAmountTaxable; + details.PaymentAdditionalFeeExclTax = taxSummaryExcl.TotalPaymentFeeAmountTaxable; + details.PaymentAdditionalFeeNonTaxable = taxSummaryExcl.TotalPaymentFeeAmountNonTaxable ?? decimal.Zero; + + //tax amount + bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; + details.OrderTaxTotal = includingTax? taxSummaryIncl.TotalAmountTax : taxSummaryExcl.TotalAmountTax; + + //VAT number + var customerVatStatus = (VatNumberStatus)details.Customer.GetAttribute(SystemCustomerAttributeNames.VatNumberStatusId); + if (_taxSettings.EuVatEnabled && customerVatStatus == VatNumberStatus.Valid) + details.VatNumber = details.Customer.GetAttribute(SystemCustomerAttributeNames.VatNumber); + + //tax rates + //var taxrates = includingTax ? taxSummaryIncl.GenerateOldTaxrateDict() : taxSummaryExcl.GenerateOldTaxrateDict(); + var taxrates = includingTax ? taxSummaryIncl : taxSummaryExcl; + details.TaxRates = taxrates.GenerateTaxRateString(); + + //order total (and applied discounts, gift cards, reward points) + details.OrderDiscountAmount = taxSummaryExcl.TotalInvDiscAmount; + details.OrderDiscountAmountIncl = taxSummaryIncl.TotalInvDiscAmount; + details.RedeemedRewardPoints = includingTax ? redeemedRewardPointsIncl : redeemedRewardPointsExcl; + details.AppliedGiftCards = includingTax ? appliedGiftCardsIncl : appliedGiftCardsExcl; + details.OrderTotal = includingTax ? taxSummaryIncl.TotalPaymentAmount : taxSummaryExcl.TotalPaymentAmount; + details.OrderAmount = includingTax ? taxSummaryIncl.TotalAmount : taxSummaryExcl.TotalAmount; + details.OrderAmountIncl = includingTax ? taxSummaryIncl.TotalAmountIncludingTax : taxSummaryExcl.TotalAmountIncludingTax; + details.EarnedRewardPointsBaseAmountIncl = earnedRewardPointsBaseAmountIncl; + details.EarnedRewardPointsBaseAmountExcl = earnedRewardPointsBaseAmountExcl; + + + //discount history + foreach (var disc in orderAppliedDiscounts) + if (!details.AppliedDiscounts.ContainsDiscount(disc)) + details.AppliedDiscounts.Add(disc); + + processPaymentRequest.OrderTotal = details.OrderTotal; + + //recurring or standard shopping cart? + details.IsRecurringShoppingCart = details.Cart.IsRecurring(); + if (details.IsRecurringShoppingCart) + { + int recurringCycleLength; + RecurringProductCyclePeriod recurringCyclePeriod; + int recurringTotalCycles; + var recurringCyclesError = details.Cart.GetRecurringCycleInfo(_localizationService, + out recurringCycleLength, out recurringCyclePeriod, out recurringTotalCycles); + if (!string.IsNullOrEmpty(recurringCyclesError)) + throw new NopException(recurringCyclesError); + + processPaymentRequest.RecurringCycleLength = recurringCycleLength; + processPaymentRequest.RecurringCyclePeriod = recurringCyclePeriod; + processPaymentRequest.RecurringTotalCycles = recurringTotalCycles; + } + + return details; + } + + /// + /// Prepare details to place order based on the recurring payment. + /// + /// Process payment request + /// Details + protected virtual PlaceOrderContainter PrepareRecurringOrderDetails(ProcessPaymentRequest processPaymentRequest) + { + var details = new PlaceOrderContainter(); + details.IsRecurringShoppingCart = true; + + //Load initial order + details.InitialOrder = _orderService.GetOrderById(processPaymentRequest.InitialOrderId); + if (details.InitialOrder == null) + throw new ArgumentException("Initial order is not set for recurring payment"); + + processPaymentRequest.PaymentMethodSystemName = details.InitialOrder.PaymentMethodSystemName; + + //customer + details.Customer = _customerService.GetCustomerById(processPaymentRequest.CustomerId); + if (details.Customer == null) + throw new ArgumentException("Customer is not set"); + + //affiliate + var affiliate = _affiliateService.GetAffiliateById(details.Customer.AffiliateId); + if (affiliate != null && affiliate.Active && !affiliate.Deleted) + details.AffiliateId = affiliate.Id; + + //check whether customer is guest + if (details.Customer.IsGuest() && !_orderSettings.AnonymousCheckoutAllowed) + throw new NopException("Anonymous checkout is not allowed"); + + //customer currency + details.CustomerCurrencyCode = details.InitialOrder.CustomerCurrencyCode; + details.CustomerCurrencyRate = details.InitialOrder.CurrencyRate; + + //customer language + details.CustomerLanguage = _languageService.GetLanguageById(details.InitialOrder.CustomerLanguageId); + if (details.CustomerLanguage == null || !details.CustomerLanguage.Published) + details.CustomerLanguage = _workContext.WorkingLanguage; + + //billing address + if (details.InitialOrder.BillingAddress == null) + throw new NopException("Billing address is not available"); + + details.BillingAddress = (Address)details.InitialOrder.BillingAddress.Clone(); + if (details.BillingAddress.Country != null && !details.BillingAddress.Country.AllowsBilling) + throw new NopException(string.Format("Country '{0}' is not allowed for billing", details.BillingAddress.Country.Name)); + + //checkout attributes + details.CheckoutAttributesXml = details.InitialOrder.CheckoutAttributesXml; + details.CheckoutAttributeDescription = details.InitialOrder.CheckoutAttributeDescription; + + //tax display type + details.CustomerTaxDisplayType = details.InitialOrder.CustomerTaxDisplayType; + + //sub total + details.OrderSubTotalInclTax = details.InitialOrder.OrderSubtotalInclTax; + details.OrderSubTotalExclTax = details.InitialOrder.OrderSubtotalExclTax; + + //shipping info + if (details.InitialOrder.ShippingStatus != ShippingStatus.ShippingNotRequired) + { + details.PickUpInStore = details.InitialOrder.PickUpInStore; + if (!details.PickUpInStore) + { + if (details.InitialOrder.ShippingAddress == null) + throw new NopException("Shipping address is not available"); + + //clone shipping address + details.ShippingAddress = (Address)details.InitialOrder.ShippingAddress.Clone(); + if (details.ShippingAddress.Country != null && !details.ShippingAddress.Country.AllowsShipping) + throw new NopException(string.Format("Country '{0}' is not allowed for shipping", details.ShippingAddress.Country.Name)); + } + else + if (details.InitialOrder.PickupAddress != null) + details.PickupAddress = (Address)details.InitialOrder.PickupAddress.Clone(); + details.ShippingMethodName = details.InitialOrder.ShippingMethod; + details.ShippingRateComputationMethodSystemName = details.InitialOrder.ShippingRateComputationMethodSystemName; + details.ShippingStatus = ShippingStatus.NotYetShipped; + } + else + details.ShippingStatus = ShippingStatus.ShippingNotRequired; + + //shipping total + details.OrderShippingTotalInclTax = details.InitialOrder.OrderShippingInclTax; + details.OrderShippingTotalExclTax = details.InitialOrder.OrderShippingExclTax; + details.OrderShippingTotalNonTaxable = details.InitialOrder.OrderShippingNonTaxable; + + //payment total + details.PaymentAdditionalFeeInclTax = details.InitialOrder.PaymentMethodAdditionalFeeInclTax; + details.PaymentAdditionalFeeExclTax = details.InitialOrder.PaymentMethodAdditionalFeeExclTax; + details.PaymentAdditionalFeeNonTaxable = details.InitialOrder.PaymentMethodAdditionalFeeNonTaxable; + + //tax total + details.OrderTaxTotal = details.InitialOrder.OrderTax; + + //VAT number + details.VatNumber = details.InitialOrder.VatNumber; + + //order total + details.OrderDiscountAmount = details.InitialOrder.OrderDiscount; + details.OrderTotal = details.InitialOrder.OrderTotal; + details.OrderAmount = details.InitialOrder.OrderAmount; + details.OrderAmountIncl = details.InitialOrder.OrderAmountIncl; + details.OrderDiscountAmountIncl = details.InitialOrder.OrderDiscountIncl; + details.EarnedRewardPointsBaseAmountIncl = details.InitialOrder.EarnedRewardPointsBaseAmountIncl; + details.EarnedRewardPointsBaseAmountExcl = details.InitialOrder.EarnedRewardPointsBaseAmountExcl; + processPaymentRequest.OrderTotal = details.OrderTotal; + + return details; + } + + /// + /// Save order and add order notes + /// + /// Process payment request + /// Process payment result + /// Details + /// Order + protected virtual Order SaveOrderDetails(ProcessPaymentRequest processPaymentRequest, + ProcessPaymentResult processPaymentResult, PlaceOrderContainter details) + { + var order = new Order + { + StoreId = processPaymentRequest.StoreId, + OrderGuid = processPaymentRequest.OrderGuid, + CustomerId = details.Customer.Id, + CustomerLanguageId = details.CustomerLanguage.Id, + CustomerTaxDisplayType = details.CustomerTaxDisplayType, + CustomerIp = _webHelper.GetCurrentIpAddress(), + OrderSubtotalInclTax = details.OrderSubTotalInclTax, + OrderSubtotalExclTax = details.OrderSubTotalExclTax, + OrderSubTotalDiscountInclTax = details.OrderSubTotalDiscountInclTax, + OrderSubTotalDiscountExclTax = details.OrderSubTotalDiscountExclTax, + OrderShippingInclTax = details.OrderShippingTotalInclTax, + OrderShippingExclTax = details.OrderShippingTotalExclTax, + OrderShippingNonTaxable = details.OrderShippingTotalNonTaxable, + PaymentMethodAdditionalFeeInclTax = details.PaymentAdditionalFeeInclTax, + PaymentMethodAdditionalFeeExclTax = details.PaymentAdditionalFeeExclTax, + PaymentMethodAdditionalFeeNonTaxable = details.PaymentAdditionalFeeNonTaxable, + TaxRates = details.TaxRates, + OrderTax = details.OrderTaxTotal, + OrderTotal = details.OrderTotal, + OrderAmount = details.OrderAmount, + OrderAmountIncl = details.OrderAmountIncl, + OrderDiscountIncl = details.OrderDiscountAmountIncl, + EarnedRewardPointsBaseAmountIncl = details.EarnedRewardPointsBaseAmountIncl, + EarnedRewardPointsBaseAmountExcl = details.EarnedRewardPointsBaseAmountExcl, + RefundedAmount = decimal.Zero, + OrderDiscount = details.OrderDiscountAmount, + CheckoutAttributeDescription = details.CheckoutAttributeDescription, + CheckoutAttributesXml = details.CheckoutAttributesXml, + CustomerCurrencyCode = details.CustomerCurrencyCode, + CurrencyRate = details.CustomerCurrencyRate, + AffiliateId = details.AffiliateId, + OrderStatus = OrderStatus.Pending, + AllowStoringCreditCardNumber = processPaymentResult.AllowStoringCreditCardNumber, + CardType = processPaymentResult.AllowStoringCreditCardNumber ? _encryptionService.EncryptText(processPaymentRequest.CreditCardType) : string.Empty, + CardName = processPaymentResult.AllowStoringCreditCardNumber ? _encryptionService.EncryptText(processPaymentRequest.CreditCardName) : string.Empty, + CardNumber = processPaymentResult.AllowStoringCreditCardNumber ? _encryptionService.EncryptText(processPaymentRequest.CreditCardNumber) : string.Empty, + MaskedCreditCardNumber = _encryptionService.EncryptText(_paymentService.GetMaskedCreditCardNumber(processPaymentRequest.CreditCardNumber)), + CardCvv2 = processPaymentResult.AllowStoringCreditCardNumber ? _encryptionService.EncryptText(processPaymentRequest.CreditCardCvv2) : string.Empty, + CardExpirationMonth = processPaymentResult.AllowStoringCreditCardNumber ? _encryptionService.EncryptText(processPaymentRequest.CreditCardExpireMonth.ToString()) : string.Empty, + CardExpirationYear = processPaymentResult.AllowStoringCreditCardNumber ? _encryptionService.EncryptText(processPaymentRequest.CreditCardExpireYear.ToString()) : string.Empty, + PaymentMethodSystemName = processPaymentRequest.PaymentMethodSystemName, + AuthorizationTransactionId = processPaymentResult.AuthorizationTransactionId, + AuthorizationTransactionCode = processPaymentResult.AuthorizationTransactionCode, + AuthorizationTransactionResult = processPaymentResult.AuthorizationTransactionResult, + CaptureTransactionId = processPaymentResult.CaptureTransactionId, + CaptureTransactionResult = processPaymentResult.CaptureTransactionResult, + SubscriptionTransactionId = processPaymentResult.SubscriptionTransactionId, + PaymentStatus = processPaymentResult.NewPaymentStatus, + PaidDateUtc = null, + BillingAddress = details.BillingAddress, + ShippingAddress = details.ShippingAddress, + ShippingStatus = details.ShippingStatus, + ShippingMethod = details.ShippingMethodName, + PickUpInStore = details.PickUpInStore, + PickupAddress = details.PickupAddress, + ShippingRateComputationMethodSystemName = details.ShippingRateComputationMethodSystemName, + CustomValuesXml = processPaymentRequest.SerializeCustomValues(), + VatNumber = details.VatNumber, + CreatedOnUtc = DateTime.UtcNow, + InvoiceId = null, + InvoiceDateUtc = null, + CustomOrderNumber = string.Empty + }; + + _orderService.InsertOrder(order); + + //generate and set custom order number + order.CustomOrderNumber = _customNumberFormatter.GenerateOrderCustomNumber(order); + _orderService.UpdateOrder(order); + + //reward points history + if (details.RedeemedRewardPoints.AmountTotal > decimal.Zero) + { + //set to negative + details.RedeemedRewardPoints.RevertSign(); + //add entry + _rewardPointService.AddRewardPointsHistoryEntry(details.Customer, details.RedeemedRewardPoints, order.StoreId, + string.Format(_localizationService.GetResource("RewardPoints.Message.RedeemedForOrder", order.CustomerLanguageId), order.CustomOrderNumber), + order, hasUsedAmount: true); + + _customerService.UpdateCustomer(details.Customer); + } + + return order; + } + + /// + /// Send "order placed" notifications and save order notes + /// + /// Order + protected virtual void SendNotificationsAndSaveNotes(Order order) + { + //notes, messages + if (_workContext.OriginalCustomerIfImpersonated != null) + //this order is placed by a store administrator impersonating a customer + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("Order placed by a store owner ('{0}'. ID = {1}) impersonating the customer.", + _workContext.OriginalCustomerIfImpersonated.Email, _workContext.OriginalCustomerIfImpersonated.Id), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + else + order.OrderNotes.Add(new OrderNote + { + Note = "Order placed", + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + //send email notifications + var orderPlacedStoreOwnerNotificationQueuedEmailId = _workflowMessageService.SendOrderPlacedStoreOwnerNotification(order, _localizationSettings.DefaultAdminLanguageId); + if (orderPlacedStoreOwnerNotificationQueuedEmailId > 0) + { + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("\"Order placed\" email (to store owner) has been queued. Queued email identifier: {0}.", orderPlacedStoreOwnerNotificationQueuedEmailId), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + } + + var orderPlacedAttachmentFilePath = _orderSettings.AttachPdfInvoiceToOrderPlacedEmail ? + _pdfService.PrintOrderToPdf(order) : null; + var orderPlacedAttachmentFileName = _orderSettings.AttachPdfInvoiceToOrderPlacedEmail ? + "order.pdf" : null; + var orderPlacedCustomerNotificationQueuedEmailId = _workflowMessageService + .SendOrderPlacedCustomerNotification(order, order.CustomerLanguageId, orderPlacedAttachmentFilePath, orderPlacedAttachmentFileName); + if (orderPlacedCustomerNotificationQueuedEmailId > 0) + { + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("\"Order placed\" email (to customer) has been queued. Queued email identifier: {0}.", orderPlacedCustomerNotificationQueuedEmailId), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + } + + var vendors = GetVendorsInOrder(order); + foreach (var vendor in vendors) + { + var orderPlacedVendorNotificationQueuedEmailId = _workflowMessageService.SendOrderPlacedVendorNotification(order, vendor, _localizationSettings.DefaultAdminLanguageId); + if (orderPlacedVendorNotificationQueuedEmailId > 0) + { + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("\"Order placed\" email (to vendor) has been queued. Queued email identifier: {0}.", orderPlacedVendorNotificationQueuedEmailId), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + } + } + } + /// + /// Award (earn) reward points (for placing a new order) + /// + /// Order + protected virtual void AwardRewardPoints(Order order) + { + //Ensure that reward points were not added (earned) before. We should not add reward points if they were already earned for this order + if (order.RewardPointsHistoryEntryId.HasValue) + return; + + var points = new RewardPoints(_rewardPointService); + + //earned points + decimal usedPurchasedRewardPointsAmount = order.RedeemedRewardPointsEntry.UsedAmountPurchased; + + bool includingTax = order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax; + decimal amount = _rewardPointService.GetRewardPointsBaseAmount(includingTax ? order.EarnedRewardPointsBaseAmountIncl : order.EarnedRewardPointsBaseAmountExcl, usedPurchasedRewardPointsAmount); + points.Points = _rewardPointService.CalculateRewardPoints(order.Customer, amount); + if (points.Points == 0) + return; + + //check whether delay is set + DateTime? activatingDate = null; + if (_rewardPointsSettings.ActivationDelay > 0) + { + var delayPeriod = (RewardPointsActivatingDelayPeriod)_rewardPointsSettings.ActivationDelayPeriodId; + var delayInHours = delayPeriod.ToHours(_rewardPointsSettings.ActivationDelay); + activatingDate = DateTime.UtcNow.AddHours(delayInHours); + } + + //add reward points + order.RewardPointsHistoryEntryId = _rewardPointService.AddRewardPointsHistoryEntry(order.Customer, points, order.StoreId, + string.Format(_localizationService.GetResource("RewardPoints.Message.EarnedForOrder"), order.CustomOrderNumber, amount), activatingDate: activatingDate); + + _orderService.UpdateOrder(order); + } + + /// + /// Reduce (cancel) reward points (previously awarded for placing an order) + /// + /// Order + protected virtual void ReduceRewardPoints(Order order) + { + //ensure that reward points were already earned for this order before + if (!order.RewardPointsHistoryEntryId.HasValue) + return; + + //is this needed? points have already been assigned, why recalculate them? Use what was assigned + //var totalForRewardPoints = _orderTotalCalculationService.CalculateApplicableOrderTotalForRewardPoints(order.OrderShippingInclTax, order.OrderTotal); + //int points = _orderTotalCalculationService.CalculateRewardPoints(order.Customer, totalForRewardPoints); + //if (points == 0) + // return; + + //get appropriate history entry + var rewardPointsHistoryEntry = _rewardPointService.GetRewardPointsHistoryEntryById(order.RewardPointsHistoryEntryId.Value); + if (rewardPointsHistoryEntry != null && rewardPointsHistoryEntry.CreatedOnUtc > DateTime.UtcNow && !rewardPointsHistoryEntry.PointsBalance.HasValue && rewardPointsHistoryEntry.PointsPurchased == 0) + { + //just delete the upcoming entry (points were not granted yet); assure PointsBalance.HasValue is null and points where not purchased + _rewardPointService.DeleteRewardPointsHistoryEntry(rewardPointsHistoryEntry); + } + else + { + //or reduce reward points if the entry already exists + var rewardPoints = new RewardPoints(_rewardPointService) + { + Points = -rewardPointsHistoryEntry.Points, + PointsPurchased = -rewardPointsHistoryEntry.PointsPurchased + }; + _rewardPointService.AddRewardPointsHistoryEntry(order.Customer, rewardPoints, order.StoreId, + string.Format(_localizationService.GetResource("RewardPoints.Message.ReducedForOrder"), order.CustomOrderNumber)); + } + + _orderService.UpdateOrder(order); + } + + /// + /// Return back redeemded reward points to a customer (spent when placing an order) + /// + /// Order + protected virtual void ReturnBackRedeemedRewardPoints(Order order) + { + //were some points redeemed when placing an order? + if (order.RedeemedRewardPointsEntry == null) + return; + + //return back + var redeemdRewardPoints = new RewardPoints(_rewardPointService) + { + Points = -order.RedeemedRewardPointsEntry.Points, + PointsPurchased = -order.RedeemedRewardPointsEntry.PointsPurchased + }; + _rewardPointService.AddRewardPointsHistoryEntry(order.Customer, redeemdRewardPoints, order.StoreId, + string.Format(_localizationService.GetResource("RewardPoints.Message.ReturnedForOrder"), order.CustomOrderNumber)); + _orderService.UpdateOrder(order); + } + + /// + /// Award purchased reward points + /// + /// Order + /// shoppingcart item + protected virtual void AwardPurchasedRewardPoints(Order order, ShoppingCartItem sc, OrderItem orderItem) + { + //Ensure that reward points were not added before. + if (order.RewardPointsHistoryEntryId.HasValue) + return; + + var points = new RewardPoints(_rewardPointService) + { + PointsPurchased = _rewardPointService.ConvertAmountToRewardPoints(orderItem.PriceInclTax, sc.Product.OverriddenRPExchangeRate) + }; + + //When puchasing reward points, earned points are immediately added. As points get pruchased, points can be earned independently of the setting EarnRewardPointsOnlyWhenUsingPurchasedRewardPoints. + //earned points + bool includingTax = order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax; + decimal baseAmount = includingTax ? order.EarnedRewardPointsBaseAmountIncl : order.EarnedRewardPointsBaseAmountExcl; + points.Points = _rewardPointService.CalculateRewardPoints(order.Customer, baseAmount); + + //total points + if (points.PointsPurchased == 0 && points.Points == 0) + return; + + //add reward points + string message = string.Format( + points.Points == 0 ? _localizationService.GetResource("RewardPoints.Message.PurchasedWithOrder") + : _localizationService.GetResource("RewardPoints.Message.PurchasedEarnedWithOrder") + , order.CustomOrderNumber, baseAmount); + + order.RewardPointsHistoryEntryId = _rewardPointService.AddRewardPointsHistoryEntry(order.Customer, points, order.StoreId, + message.ToString(), orderItem: orderItem); + + _orderService.UpdateOrder(order); + } + /// + /// Set IsActivated value for purchase gift cards for particular order + /// + /// Order + /// A value indicating whether to activate gift cards; true - activate, false - deactivate + protected virtual void SetActivatedValueForPurchasedGiftCards(Order order, bool activate) + { + var giftCards = _giftCardService.GetAllGiftCards(purchasedWithOrderId: order.Id, + isGiftCardActivated: !activate); + foreach (var gc in giftCards) + { + if (activate) + { + //activate + bool isRecipientNotified = gc.IsRecipientNotified; + if (gc.GiftCardType == GiftCardType.Virtual) + { + //send email for virtual gift card + if (!String.IsNullOrEmpty(gc.RecipientEmail) && + !String.IsNullOrEmpty(gc.SenderEmail)) + { + var customerLang = _languageService.GetLanguageById(order.CustomerLanguageId); + if (customerLang == null) + customerLang = _languageService.GetAllLanguages().FirstOrDefault(); + if (customerLang == null) + throw new Exception("No languages could be loaded"); + int 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); + } + } + } + + /// + /// Sets an order status + /// + /// Order + /// New order status + /// True to notify customer + protected virtual void SetOrderStatus(Order order, OrderStatus os, bool notifyCustomer) + { + if (order == null) + throw new ArgumentNullException("order"); + + OrderStatus prevOrderStatus = order.OrderStatus; + if (prevOrderStatus == os) + return; + + //set and save new order status + order.OrderStatusId = (int)os; + _orderService.UpdateOrder(order); + + //order notes, notifications + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("Order status has been changed to {0}", os.ToString()), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + + if (prevOrderStatus != OrderStatus.Complete && + os == OrderStatus.Complete + && notifyCustomer) + { + //notification + var orderCompletedAttachmentFilePath = _orderSettings.AttachPdfInvoiceToOrderCompletedEmail ? + _pdfService.PrintOrderToPdf(order) : null; + var orderCompletedAttachmentFileName = _orderSettings.AttachPdfInvoiceToOrderCompletedEmail ? + "order.pdf" : null; + int orderCompletedCustomerNotificationQueuedEmailId = _workflowMessageService + .SendOrderCompletedCustomerNotification(order, order.CustomerLanguageId, orderCompletedAttachmentFilePath, + orderCompletedAttachmentFileName); + if (orderCompletedCustomerNotificationQueuedEmailId > 0) + { + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("\"Order completed\" email (to customer) has been queued. Queued email identifier: {0}.", orderCompletedCustomerNotificationQueuedEmailId), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + } + } + + 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("\"Order cancelled\" email (to customer) has been queued. Queued email identifier: {0}.", orderCancelledCustomerNotificationQueuedEmailId), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + } + } + + //reward points + if (order.OrderStatus == OrderStatus.Complete) + { + AwardRewardPoints(order); + } + if (order.OrderStatus == OrderStatus.Cancelled) + { + ReduceRewardPoints(order); + } + + //gift cards activation + if (_orderSettings.ActivateGiftCardsAfterCompletingOrder && order.OrderStatus == OrderStatus.Complete) + { + SetActivatedValueForPurchasedGiftCards(order, true); + } + + //gift cards deactivation + if (_orderSettings.DeactivateGiftCardsAfterCancellingOrder && order.OrderStatus == OrderStatus.Cancelled) + { + SetActivatedValueForPurchasedGiftCards(order, false); + } + } + + /// + /// Process order paid status + /// + /// Order + protected virtual void ProcessOrderPaid(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + //raise event + _eventPublisher.Publish(new OrderPaidEvent(order)); + + //order paid email notification + if (order.OrderTotal != decimal.Zero) + { + //we should not send it for free ($0 total) orders? + //remove this "if" statement if you want to send it in this case + + var orderPaidAttachmentFilePath = _orderSettings.AttachPdfInvoiceToOrderPaidEmail ? + _pdfService.PrintOrderToPdf(order) : null; + var orderPaidAttachmentFileName = _orderSettings.AttachPdfInvoiceToOrderPaidEmail ? + "order.pdf" : null; + _workflowMessageService.SendOrderPaidCustomerNotification(order, order.CustomerLanguageId, + orderPaidAttachmentFilePath, orderPaidAttachmentFileName); + + _workflowMessageService.SendOrderPaidStoreOwnerNotification(order, _localizationSettings.DefaultAdminLanguageId); + var vendors = GetVendorsInOrder(order); + foreach (var vendor in vendors) + { + _workflowMessageService.SendOrderPaidVendorNotification(order, vendor, _localizationSettings.DefaultAdminLanguageId); + } + //TODO add "order paid email sent" order note + } + + //customer roles with "purchased with product" specified + ProcessCustomerRolesWithPurchasedProductSpecified(order, true); + } + + /// + /// Process customer roles with "Purchased with Product" property configured + /// + /// Order + /// A value indicating whether to add configured customer role; true - add, false - remove + protected virtual void ProcessCustomerRolesWithPurchasedProductSpecified(Order order, bool add) + { + if (order == null) + throw new ArgumentNullException("order"); + + //purchased product identifiers + var purchasedProductIds = new List(); + foreach (var orderItem in order.OrderItems) + { + //standard items + purchasedProductIds.Add(orderItem.ProductId); + + //bundled (associated) products + var attributeValues = _productAttributeParser.ParseProductAttributeValues(orderItem.AttributesXml); + foreach (var attributeValue in attributeValues) + { + if (attributeValue.AttributeValueType == AttributeValueType.AssociatedToProduct) + { + purchasedProductIds.Add(attributeValue.AssociatedProductId); + } + } + } + + //list of customer roles + var customerRoles = _customerService + .GetAllCustomerRoles(true) + .Where(cr => purchasedProductIds.Contains(cr.PurchasedWithProductId)) + .ToList(); + + if (customerRoles.Any()) + { + var customer = order.Customer; + foreach (var customerRole in customerRoles) + { + if (customer.CustomerRoles.Count(cr => cr.Id == customerRole.Id) == 0) + { + //not in the list yet + if (add) + { + //add + customer.CustomerRoles.Add(customerRole); + } + } + else + { + //already in the list + if (!add) + { + //remove + customer.CustomerRoles.Remove(customerRole); + } + } + } + _customerService.UpdateCustomer(customer); + } + } + + /// + /// Get a list of vendors in order (order items) + /// + /// Order + /// Vendors + protected virtual IList GetVendorsInOrder(Order order) + { + var vendors = new List(); + foreach (var orderItem in order.OrderItems) + { + var vendorId = orderItem.Product.VendorId; + //find existing + var vendor = vendors.FirstOrDefault(v => v.Id == vendorId); + if (vendor == null) + { + //not found. load by Id + vendor = _vendorService.GetVendorById(vendorId); + if (vendor != null && !vendor.Deleted && vendor.Active) + { + vendors.Add(vendor); + } + } + } + + return vendors; + } + + #endregion + + #region Methods + + /// + /// Checks order status + /// + /// Order + /// Validated order + public virtual void CheckOrderStatus(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (order.PaymentStatus == PaymentStatus.Paid && !order.PaidDateUtc.HasValue) + { + //ensure that paid date is set + order.PaidDateUtc = DateTime.UtcNow; + _orderService.UpdateOrder(order); + } + + //set invoice id + if (order.PaymentStatus == PaymentStatus.Paid && order.InvoiceId == null) + { + order.InvoiceDateUtc = DateTime.UtcNow; + order.InvoiceId = GetInvoiceId(); + _orderService.UpdateOrder(order); + } + + if (order.OrderStatus == OrderStatus.Pending) + { + if (order.PaymentStatus == PaymentStatus.Authorized || + order.PaymentStatus == PaymentStatus.Paid) + { + SetOrderStatus(order, OrderStatus.Processing, false); + } + } + + if (order.OrderStatus == OrderStatus.Pending) + { + if (order.ShippingStatus == ShippingStatus.PartiallyShipped || + order.ShippingStatus == ShippingStatus.Shipped || + order.ShippingStatus == ShippingStatus.Delivered) + { + SetOrderStatus(order, OrderStatus.Processing, false); + } + } + + //is order complete? + if (order.OrderStatus != OrderStatus.Cancelled && + order.OrderStatus != OrderStatus.Complete) + { + if (order.PaymentStatus == PaymentStatus.Paid) + { + var completed = false; + if (order.ShippingStatus == ShippingStatus.ShippingNotRequired) + { + //shipping is not required + completed = true; + } + else + { + //shipping is required + if (_orderSettings.CompleteOrderWhenDelivered) + { + completed = order.ShippingStatus == ShippingStatus.Delivered; + } + else + { + completed = order.ShippingStatus == ShippingStatus.Shipped || + order.ShippingStatus == ShippingStatus.Delivered; + } + } + + if (completed) + { + SetOrderStatus(order, OrderStatus.Complete, true); + } + } + } + } + + /// + /// Places an order + /// + /// Process payment request + /// Place order result + public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentRequest) + { + if (processPaymentRequest == null) + throw new ArgumentNullException("processPaymentRequest"); + + var result = new PlaceOrderResult(); + try + { + if (processPaymentRequest.OrderGuid == Guid.Empty) + processPaymentRequest.OrderGuid = Guid.NewGuid(); + + //prepare order details + var details = PreparePlaceOrderDetails(processPaymentRequest); + + #region Payment workflow + + + //process payment + ProcessPaymentResult processPaymentResult = null; + //skip payment workflow if order total equals zero + var skipPaymentWorkflow = details.OrderTotal == decimal.Zero; + if (!skipPaymentWorkflow) + { + var paymentMethod = _paymentService.LoadPaymentMethodBySystemName(processPaymentRequest.PaymentMethodSystemName); + if (paymentMethod == null) + throw new NopException("Payment method couldn't be loaded"); + + //ensure that payment method is active + if (!paymentMethod.IsPaymentMethodActive(_paymentSettings)) + throw new NopException("Payment method is not active"); + + if (details.IsRecurringShoppingCart) + { + //recurring cart + switch (_paymentService.GetRecurringPaymentType(processPaymentRequest.PaymentMethodSystemName)) + { + case RecurringPaymentType.NotSupported: + throw new NopException("Recurring payments are not supported by selected payment method"); + case RecurringPaymentType.Manual: + case RecurringPaymentType.Automatic: + processPaymentResult = _paymentService.ProcessRecurringPayment(processPaymentRequest); + break; + default: + throw new NopException("Not supported recurring payment type"); + } + } + else + //standard cart + processPaymentResult = _paymentService.ProcessPayment(processPaymentRequest); + } + else + //payment is not required + processPaymentResult = new ProcessPaymentResult { NewPaymentStatus = PaymentStatus.Paid }; + + if (processPaymentResult == null) + throw new NopException("processPaymentResult is not available"); + + #endregion + + if (processPaymentResult.Success) + { + #region Save order details + + var order = SaveOrderDetails(processPaymentRequest, processPaymentResult, details); + result.PlacedOrder = order; + + //move shopping cart items to order items + foreach (var sc in details.Cart) + { + //prices + decimal taxRate; + List scDiscounts; + decimal discountAmount; + int? maximumDiscountQty; + var scUnitPrice = _priceCalculationService.GetUnitPrice(sc); + var scSubTotal = _priceCalculationService.GetSubTotal(sc, true, out discountAmount, out scDiscounts, out maximumDiscountQty); + var scUnitPriceInclTax = _taxService.GetProductPrice(sc.Product, scUnitPrice, true, details.Customer, out taxRate); + var scUnitPriceExclTax = _taxService.GetProductPrice(sc.Product, scUnitPrice, false, details.Customer, out taxRate); + var scSubTotalInclTax = _taxService.GetProductPrice(sc.Product, scSubTotal, true, details.Customer, out taxRate); + var scSubTotalExclTax = _taxService.GetProductPrice(sc.Product, scSubTotal, false, details.Customer, out taxRate); + var discountAmountInclTax = _taxService.GetProductPrice(sc.Product, discountAmount, true, details.Customer, out taxRate); + var discountAmountExclTax = _taxService.GetProductPrice(sc.Product, discountAmount, false, details.Customer, out taxRate); + foreach (var disc in scDiscounts) + if (!details.AppliedDiscounts.ContainsDiscount(disc)) + details.AppliedDiscounts.Add(disc); + + //attributes + var attributeDescription = _productAttributeFormatter.FormatAttributes(sc.Product, sc.AttributesXml, details.Customer, subTotal: _taxSettings.PricesIncludeTax ? scSubTotalInclTax : scSubTotalExclTax); + + var itemWeight = _shippingService.GetShoppingCartItemWeight(sc); + + //save order item + var orderItem = new OrderItem + { + OrderItemGuid = Guid.NewGuid(), + Order = order, + ProductId = sc.ProductId, + UnitPriceInclTax = scUnitPriceInclTax, + UnitPriceExclTax = scUnitPriceExclTax, + PriceInclTax = scSubTotalInclTax, + PriceExclTax = scSubTotalExclTax, + OriginalProductCost = _priceCalculationService.GetProductCost(sc.Product, sc.AttributesXml), + AttributeDescription = attributeDescription, + AttributesXml = sc.AttributesXml, + Quantity = sc.Quantity, + DiscountAmountInclTax = discountAmountInclTax, + DiscountAmountExclTax = discountAmountExclTax, + DownloadCount = 0, + IsDownloadActivated = false, + LicenseDownloadId = 0, + ItemWeight = itemWeight, + RentalStartDateUtc = sc.RentalStartDateUtc, + RentalEndDateUtc = sc.RentalEndDateUtc, + TaxRate = taxRate //MF 25.11.16 + }; + order.OrderItems.Add(orderItem); + _orderService.UpdateOrder(order); + + //gift cards + if (sc.Product.IsGiftCard) + { + string giftCardRecipientName; + string giftCardRecipientEmail; + string giftCardSenderName; + string giftCardSenderEmail; + string giftCardMessage; + _productAttributeParser.GetGiftCardAttribute(sc.AttributesXml, out giftCardRecipientName, + out giftCardRecipientEmail, out giftCardSenderName, out giftCardSenderEmail, out giftCardMessage); + + for (var i = 0; i < sc.Quantity; i++) + { + _giftCardService.InsertGiftCard(new GiftCard + { + GiftCardType = sc.Product.GiftCardType, + PurchasedWithOrderItem = orderItem, + Amount = sc.Product.OverriddenGiftCardAmount.HasValue ? sc.Product.OverriddenGiftCardAmount.Value : scUnitPriceExclTax, + IsGiftCardActivated = false, + GiftCardCouponCode = _giftCardService.GenerateGiftCardCode(), + RecipientName = giftCardRecipientName, + RecipientEmail = giftCardRecipientEmail, + SenderName = giftCardSenderName, + SenderEmail = giftCardSenderEmail, + Message = giftCardMessage, + IsRecipientNotified = false, + CreatedOnUtc = DateTime.UtcNow + }); + } + } + + //purchased reward points from product + if (sc.Product.IsRewardPoints && scUnitPriceExclTax > decimal.Zero) + { + AwardPurchasedRewardPoints(order, sc, orderItem); + } + + //inventory + _productService.AdjustInventory(sc.Product, -sc.Quantity, sc.AttributesXml, + string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.PlaceOrder"), order.Id)); + } + + //clear shopping cart + details.Cart.ToList().ForEach(sci => _shoppingCartService.DeleteShoppingCartItem(sci, false)); + + //discount usage history + foreach (var discount in details.AppliedDiscounts) + { + var d = _discountService.GetDiscountById(discount.Id); + if (d != null) + { + _discountService.InsertDiscountUsageHistory(new DiscountUsageHistory + { + Discount = d, + Order = order, + CreatedOnUtc = DateTime.UtcNow + }); + } + } + + //gift card usage history + if (details.AppliedGiftCards != null) + foreach (var agc in details.AppliedGiftCards) + { + agc.GiftCard.GiftCardUsageHistory.Add(new GiftCardUsageHistory + { + GiftCard = agc.GiftCard, + UsedWithOrder = order, + UsedValue = agc.AmountCanBeUsed, + CreatedOnUtc = DateTime.UtcNow + }); + _giftCardService.UpdateGiftCard(agc.GiftCard); + } + + //recurring orders + if (details.IsRecurringShoppingCart) + { + //create recurring payment (the first payment) + var rp = new RecurringPayment + { + CycleLength = processPaymentRequest.RecurringCycleLength, + CyclePeriod = processPaymentRequest.RecurringCyclePeriod, + TotalCycles = processPaymentRequest.RecurringTotalCycles, + StartDateUtc = DateTime.UtcNow, + IsActive = true, + CreatedOnUtc = DateTime.UtcNow, + InitialOrder = order, + }; + _orderService.InsertRecurringPayment(rp); + + switch (_paymentService.GetRecurringPaymentType(processPaymentRequest.PaymentMethodSystemName)) + { + case RecurringPaymentType.NotSupported: + //not supported + break; + case RecurringPaymentType.Manual: + rp.RecurringPaymentHistory.Add(new RecurringPaymentHistory + { + RecurringPayment = rp, + CreatedOnUtc = DateTime.UtcNow, + OrderId = order.Id, + }); + _orderService.UpdateRecurringPayment(rp); + break; + case RecurringPaymentType.Automatic: + //will be created later (process is automated) + break; + default: + break; + } + } + + #endregion + + //notifications + SendNotificationsAndSaveNotes(order); + + //reset checkout data + _customerService.ResetCheckoutData(details.Customer, processPaymentRequest.StoreId, clearCouponCodes: true, clearCheckoutAttributes: true); + _customerActivityService.InsertActivity("PublicStore.PlaceOrder", _localizationService.GetResource("ActivityLog.PublicStore.PlaceOrder"), order.Id); + + //check order status + CheckOrderStatus(order); + + //raise event + _eventPublisher.Publish(new OrderPlacedEvent(order)); + + if (order.PaymentStatus == PaymentStatus.Paid) + ProcessOrderPaid(order); + } + else + foreach (var paymentError in processPaymentResult.Errors) + result.AddError(string.Format(_localizationService.GetResource("Checkout.PaymentError"), paymentError)); + } + catch (Exception exc) + { + _logger.Error(exc.Message, exc); + result.AddError(exc.Message); + } + + #region Process errors + + if (!result.Success) + { + //log errors + var logError = result.Errors.Aggregate("Error while placing order. ", + (current, next) => string.Format("{0}Error {1}: {2}. ", current, result.Errors.IndexOf(next) + 1, next)); + var customer = _customerService.GetCustomerById(processPaymentRequest.CustomerId); + _logger.Error(logError, customer: customer); + } + + #endregion + + return result; + } + + /// + /// Update order totals + /// + /// Parameters for the updating order + public virtual void UpdateOrderTotals(UpdateOrderParameters updateOrderParameters) + { + if (!_orderSettings.AutoUpdateOrderTotalsOnEditingOrder) + return; + + var updatedOrder = updateOrderParameters.UpdatedOrder; + var updatedOrderItem = updateOrderParameters.UpdatedOrderItem; + + //restore shopping cart from order items + var restoredCart = updatedOrder.OrderItems.Select(orderItem => new ShoppingCartItem + { + Id = orderItem.Id, + AttributesXml = orderItem.AttributesXml, + Customer = updatedOrder.Customer, + Product = orderItem.Product, + Quantity = orderItem.Id == updatedOrderItem.Id ? updateOrderParameters.Quantity : orderItem.Quantity, + RentalEndDateUtc = orderItem.RentalEndDateUtc, + RentalStartDateUtc = orderItem.RentalStartDateUtc, + ShoppingCartType = ShoppingCartType.ShoppingCart, + StoreId = updatedOrder.StoreId, + TaxRate = orderItem.Id == updatedOrderItem.Id ? updateOrderParameters.TaxRate : orderItem.TaxRate, + SubTotalInclTax = orderItem.Id == updatedOrderItem.Id ? updateOrderParameters.SubTotalInclTax : orderItem.PriceInclTax, + SubTotalExclTax = orderItem.Id == updatedOrderItem.Id ? updateOrderParameters.SubTotalExclTax : orderItem.PriceExclTax + }).ToList(); + + //get shopping cart item which has been updated + var updatedShoppingCartItem = restoredCart.FirstOrDefault(shoppingCartItem => shoppingCartItem.Id == updatedOrderItem.Id); + var itemDeleted = updatedShoppingCartItem == null; + + //validate shopping cart for warnings + updateOrderParameters.Warnings.AddRange(_shoppingCartService.GetShoppingCartWarnings(restoredCart, string.Empty, false)); + if (!itemDeleted) + updateOrderParameters.Warnings.AddRange(_shoppingCartService.GetShoppingCartItemWarnings(updatedOrder.Customer, updatedShoppingCartItem.ShoppingCartType, + updatedShoppingCartItem.Product, updatedOrder.StoreId, updatedShoppingCartItem.AttributesXml, updatedShoppingCartItem.CustomerEnteredPrice, + updatedShoppingCartItem.RentalStartDateUtc, updatedShoppingCartItem.RentalEndDateUtc, updatedShoppingCartItem.Quantity, false)); + + _orderTotalCalculationService.UpdateOrderTotals(updateOrderParameters, restoredCart); + + if (updateOrderParameters.PickupPoint != null) + { + updatedOrder.PickUpInStore = true; + updatedOrder.PickupAddress = new Address + { + Address1 = updateOrderParameters.PickupPoint.Address, + City = updateOrderParameters.PickupPoint.City, + Country = _countryService.GetCountryByTwoLetterIsoCode(updateOrderParameters.PickupPoint.CountryCode), + ZipPostalCode = updateOrderParameters.PickupPoint.ZipPostalCode, + CreatedOnUtc = DateTime.UtcNow, + }; + updatedOrder.ShippingMethod = string.Format(_localizationService.GetResource("Checkout.PickupPoints.Name"), updateOrderParameters.PickupPoint.Name); + updatedOrder.ShippingRateComputationMethodSystemName = updateOrderParameters.PickupPoint.ProviderSystemName; + } + + if (!itemDeleted) + { + updatedOrderItem.ItemWeight = _shippingService.GetShoppingCartItemWeight(updatedShoppingCartItem); + updatedOrderItem.OriginalProductCost = _priceCalculationService.GetProductCost(updatedShoppingCartItem.Product, updatedShoppingCartItem.AttributesXml); + updatedOrderItem.AttributeDescription = _productAttributeFormatter.FormatAttributes(updatedShoppingCartItem.Product, + updatedShoppingCartItem.AttributesXml, updatedOrder.Customer,subTotal: updatedOrder.CustomerTaxDisplayType == TaxDisplayType.IncludingTax ? updatedOrderItem.PriceInclTax : updatedOrderItem.PriceExclTax); + + //gift cards + if (updatedShoppingCartItem.Product.IsGiftCard) + { + string giftCardRecipientName; + string giftCardRecipientEmail; + string giftCardSenderName; + string giftCardSenderEmail; + string giftCardMessage; + _productAttributeParser.GetGiftCardAttribute(updatedShoppingCartItem.AttributesXml, out giftCardRecipientName, + out giftCardRecipientEmail, out giftCardSenderName, out giftCardSenderEmail, out giftCardMessage); + + for (var i = 0; i < updatedShoppingCartItem.Quantity; i++) + { + _giftCardService.InsertGiftCard(new GiftCard + { + GiftCardType = updatedShoppingCartItem.Product.GiftCardType, + PurchasedWithOrderItem = updatedOrderItem, + Amount = updatedShoppingCartItem.Product.OverriddenGiftCardAmount.HasValue ? + updatedShoppingCartItem.Product.OverriddenGiftCardAmount.Value : updatedOrderItem.UnitPriceExclTax, + IsGiftCardActivated = false, + GiftCardCouponCode = _giftCardService.GenerateGiftCardCode(), + RecipientName = giftCardRecipientName, + RecipientEmail = giftCardRecipientEmail, + SenderName = giftCardSenderName, + SenderEmail = giftCardSenderEmail, + Message = giftCardMessage, + IsRecipientNotified = false, + CreatedOnUtc = DateTime.UtcNow + }); + } + } + } + + _orderService.UpdateOrder(updatedOrder); + + //discount usage history + var discountUsageHistoryForOrder = _discountService.GetAllDiscountUsageHistory(null, updatedOrder.Customer.Id, updatedOrder.Id); + foreach (var discount in updateOrderParameters.AppliedDiscounts) + { + if (!discountUsageHistoryForOrder.Any(history => history.DiscountId == discount.Id)) + { + var d = _discountService.GetDiscountById(discount.Id); + if (d != null) + { + _discountService.InsertDiscountUsageHistory(new DiscountUsageHistory + { + Discount = d, + Order = updatedOrder, + CreatedOnUtc = DateTime.UtcNow + }); + } + } + } + + CheckOrderStatus(updatedOrder); + } + + /// + /// Deletes an order + /// + /// The order + public virtual void DeleteOrder(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + //check whether the order wasn't cancelled before + //if it already was cancelled, then there's no need to make the following adjustments + //(such as reward points, inventory, recurring payments) + //they already was done when cancelling the order + if (order.OrderStatus != OrderStatus.Cancelled) + { + //return (add) back redeemded reward points + ReturnBackRedeemedRewardPoints(order); + //reduce (cancel) back reward points (previously awarded for this order) + ReduceRewardPoints(order); + + //cancel recurring payments + var recurringPayments = _orderService.SearchRecurringPayments(initialOrderId: order.Id); + foreach (var rp in recurringPayments) + { + var errors = CancelRecurringPayment(rp); + //use "errors" variable? + } + + //Adjust inventory for already shipped shipments + //only products with "use multiple warehouses" + foreach (var shipment in order.Shipments) + { + foreach (var shipmentItem in shipment.ShipmentItems) + { + var orderItem = _orderService.GetOrderItemById(shipmentItem.OrderItemId); + if (orderItem == null) + continue; + + _productService.ReverseBookedInventory(orderItem.Product, shipmentItem, + string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.DeleteOrder"), order.Id)); + } + } + + //Adjust inventory + foreach (var orderItem in order.OrderItems) + { + _productService.AdjustInventory(orderItem.Product, orderItem.Quantity, orderItem.AttributesXml, + string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.DeleteOrder"), order.Id)); + } + + } + + //deactivate gift cards + if (_orderSettings.DeactivateGiftCardsAfterDeletingOrder) + SetActivatedValueForPurchasedGiftCards(order, false); + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = "Order has been deleted", + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + //now delete an order + _orderService.DeleteOrder(order); + } + + /// + /// Process next recurring payment + /// + /// Recurring payment + /// Process payment result (info about last payment for automatic recurring payments) + /// Collection of errors + public virtual IEnumerable ProcessNextRecurringPayment(RecurringPayment recurringPayment, ProcessPaymentResult paymentResult = null) + { + if (recurringPayment == null) + throw new ArgumentNullException("recurringPayment"); + + try + { + if (!recurringPayment.IsActive) + throw new NopException("Recurring payment is not active"); + + var initialOrder = recurringPayment.InitialOrder; + if (initialOrder == null) + throw new NopException("Initial order could not be loaded"); + + var customer = initialOrder.Customer; + if (customer == null) + throw new NopException("Customer could not be loaded"); + + var nextPaymentDate = recurringPayment.NextPaymentDate; + if (!nextPaymentDate.HasValue) + throw new NopException("Next payment date could not be calculated"); + + //payment info + var processPaymentRequest = new ProcessPaymentRequest + { + StoreId = initialOrder.StoreId, + CustomerId = customer.Id, + OrderGuid = Guid.NewGuid(), + InitialOrderId = initialOrder.Id, + RecurringCycleLength = recurringPayment.CycleLength, + RecurringCyclePeriod = recurringPayment.CyclePeriod, + RecurringTotalCycles = recurringPayment.TotalCycles, + CustomValues = initialOrder.DeserializeCustomValues() + }; + + //prepare order details + var details = PrepareRecurringOrderDetails(processPaymentRequest); + + ProcessPaymentResult processPaymentResult; + //skip payment workflow if order total equals zero + var skipPaymentWorkflow = details.OrderTotal == decimal.Zero; + if (!skipPaymentWorkflow) + { + var paymentMethod = _paymentService.LoadPaymentMethodBySystemName(processPaymentRequest.PaymentMethodSystemName); + if (paymentMethod == null) + throw new NopException("Payment method couldn't be loaded"); + + if (!paymentMethod.IsPaymentMethodActive(_paymentSettings)) + throw new NopException("Payment method is not active"); + + //Old credit card info + if (details.InitialOrder.AllowStoringCreditCardNumber) + { + processPaymentRequest.CreditCardType = _encryptionService.DecryptText(details.InitialOrder.CardType); + processPaymentRequest.CreditCardName = _encryptionService.DecryptText(details.InitialOrder.CardName); + processPaymentRequest.CreditCardNumber = _encryptionService.DecryptText(details.InitialOrder.CardNumber); + processPaymentRequest.CreditCardCvv2 = _encryptionService.DecryptText(details.InitialOrder.CardCvv2); + try + { + processPaymentRequest.CreditCardExpireMonth = Convert.ToInt32(_encryptionService.DecryptText(details.InitialOrder.CardExpirationMonth)); + processPaymentRequest.CreditCardExpireYear = Convert.ToInt32(_encryptionService.DecryptText(details.InitialOrder.CardExpirationYear)); + } + catch { } + } + + //payment type + switch (_paymentService.GetRecurringPaymentType(processPaymentRequest.PaymentMethodSystemName)) + { + case RecurringPaymentType.NotSupported: + throw new NopException("Recurring payments are not supported by selected payment method"); + case RecurringPaymentType.Manual: + processPaymentResult = _paymentService.ProcessRecurringPayment(processPaymentRequest); + break; + case RecurringPaymentType.Automatic: + //payment is processed on payment gateway site, info about last transaction in paymentResult parameter + processPaymentResult = paymentResult ?? new ProcessPaymentResult(); + break; + default: + throw new NopException("Not supported recurring payment type"); + } + } + else + processPaymentResult = paymentResult ?? new ProcessPaymentResult { NewPaymentStatus = PaymentStatus.Paid }; + + if (processPaymentResult == null) + throw new NopException("processPaymentResult is not available"); + + if (processPaymentResult.Success) + { + //save order details + var order = SaveOrderDetails(processPaymentRequest, processPaymentResult, details); + + foreach (var orderItem in details.InitialOrder.OrderItems) + { + //save item + var newOrderItem = new OrderItem + { + OrderItemGuid = Guid.NewGuid(), + Order = order, + ProductId = orderItem.ProductId, + UnitPriceInclTax = orderItem.UnitPriceInclTax, + UnitPriceExclTax = orderItem.UnitPriceExclTax, + PriceInclTax = orderItem.PriceInclTax, + PriceExclTax = orderItem.PriceExclTax, + OriginalProductCost = orderItem.OriginalProductCost, + AttributeDescription = orderItem.AttributeDescription, + AttributesXml = orderItem.AttributesXml, + Quantity = orderItem.Quantity, + DiscountAmountInclTax = orderItem.DiscountAmountInclTax, + DiscountAmountExclTax = orderItem.DiscountAmountExclTax, + DownloadCount = 0, + IsDownloadActivated = false, + LicenseDownloadId = 0, + ItemWeight = orderItem.ItemWeight, + RentalStartDateUtc = orderItem.RentalStartDateUtc, + RentalEndDateUtc = orderItem.RentalEndDateUtc, + TaxRate = orderItem.TaxRate + }; + order.OrderItems.Add(newOrderItem); + _orderService.UpdateOrder(order); + + //gift cards + if (orderItem.Product.IsGiftCard) + { + string giftCardRecipientName; + string giftCardRecipientEmail; + string giftCardSenderName; + string giftCardSenderEmail; + string giftCardMessage; + + _productAttributeParser.GetGiftCardAttribute(orderItem.AttributesXml, out giftCardRecipientName, + out giftCardRecipientEmail, out giftCardSenderName, out giftCardSenderEmail, out giftCardMessage); + + for (var i = 0; i < orderItem.Quantity; i++) + { + _giftCardService.InsertGiftCard(new GiftCard + { + GiftCardType = orderItem.Product.GiftCardType, + PurchasedWithOrderItem = newOrderItem, + Amount = orderItem.UnitPriceExclTax, + IsGiftCardActivated = false, + GiftCardCouponCode = _giftCardService.GenerateGiftCardCode(), + RecipientName = giftCardRecipientName, + RecipientEmail = giftCardRecipientEmail, + SenderName = giftCardSenderName, + SenderEmail = giftCardSenderEmail, + Message = giftCardMessage, + IsRecipientNotified = false, + CreatedOnUtc = DateTime.UtcNow + }); + } + } + + //inventory + _productService.AdjustInventory(orderItem.Product, -orderItem.Quantity, orderItem.AttributesXml, + string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.PlaceOrder"), order.Id)); + } + + //notifications + SendNotificationsAndSaveNotes(order); + + //check order status + CheckOrderStatus(order); + + //raise event + _eventPublisher.Publish(new OrderPlacedEvent(order)); + + if (order.PaymentStatus == PaymentStatus.Paid) + ProcessOrderPaid(order); + + //last payment succeeded + recurringPayment.LastPaymentFailed = false; + + //next recurring payment + recurringPayment.RecurringPaymentHistory.Add(new RecurringPaymentHistory + { + RecurringPayment = recurringPayment, + CreatedOnUtc = DateTime.UtcNow, + OrderId = order.Id, + }); + _orderService.UpdateRecurringPayment(recurringPayment); + + return new List(); + } + else + { + //log errors + var logError = processPaymentResult.Errors.Aggregate("Error while processing recurring order. ", + (current, next) => string.Format("{0}Error {1}: {2}. ", current, processPaymentResult.Errors.IndexOf(next) + 1, next)); + _logger.Error(logError, customer: customer); + + if (processPaymentResult.RecurringPaymentFailed) + { + //set flag that last payment failed + recurringPayment.LastPaymentFailed = true; + _orderService.UpdateRecurringPayment(recurringPayment); + + if (_paymentSettings.CancelRecurringPaymentsAfterFailedPayment) + { + //cancel recurring payment + CancelRecurringPayment(recurringPayment).ToList().ForEach(error => _logger.Error(error)); + + //notify a customer about cancelled payment + _workflowMessageService.SendRecurringPaymentCancelledCustomerNotification(recurringPayment, initialOrder.CustomerLanguageId); + } + else + //notify a customer about failed payment + _workflowMessageService.SendRecurringPaymentFailedCustomerNotification(recurringPayment, initialOrder.CustomerLanguageId); + } + + return processPaymentResult.Errors; + } + } + catch (Exception exc) + { + _logger.Error(string.Format("Error while processing recurring order. {0}", exc.Message), exc); + throw; + } + } + + /// + /// Cancels a recurring payment + /// + /// Recurring payment + public virtual IList CancelRecurringPayment(RecurringPayment recurringPayment) + { + if (recurringPayment == null) + throw new ArgumentNullException("recurringPayment"); + + var initialOrder = recurringPayment.InitialOrder; + if (initialOrder == null) + return new List { "Initial order could not be loaded" }; + + + var request = new CancelRecurringPaymentRequest(); + CancelRecurringPaymentResult result = null; + try + { + request.Order = initialOrder; + 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 = "Recurring payment has been cancelled", + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(initialOrder); + + //notify a store owner + _workflowMessageService + .SendRecurringPaymentCancelledStoreOwnerNotification(recurringPayment, + _localizationSettings.DefaultAdminLanguageId); + } + } + catch (Exception exc) + { + if (result == null) + result = new CancelRecurringPaymentResult(); + result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); + } + + + //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("Unable to cancel recurring payment. {0}", 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; + } + + /// + /// Gets a value indicating whether a customer can cancel recurring payment + /// + /// Customer + /// Recurring Payment + /// value indicating whether a customer can cancel recurring payment + public virtual bool CanCancelRecurringPayment(Customer customerToValidate, RecurringPayment recurringPayment) + { + if (recurringPayment == null) + return false; + + if (customerToValidate == null) + return false; + + var initialOrder = recurringPayment.InitialOrder; + if (initialOrder == null) + return false; + + var customer = recurringPayment.InitialOrder.Customer; + if (customer == null) + return false; + + if (initialOrder.OrderStatus == OrderStatus.Cancelled) + return false; + + if (!customerToValidate.IsAdmin()) + { + if (customer.Id != customerToValidate.Id) + return false; + } + + if (!recurringPayment.NextPaymentDate.HasValue) + return false; + + return true; + } + + + /// + /// Gets a value indicating whether a customer can retry last failed recurring payment + /// + /// Customer + /// Recurring Payment + /// True if a customer can retry payment; otherwise false + public virtual bool CanRetryLastRecurringPayment(Customer customer, RecurringPayment recurringPayment) + { + if (recurringPayment == null || customer == null) + return false; + + if (recurringPayment.InitialOrder == null || recurringPayment.InitialOrder.OrderStatus == OrderStatus.Cancelled) + return false; + + if (!recurringPayment.LastPaymentFailed || _paymentService.GetRecurringPaymentType(recurringPayment.InitialOrder.PaymentMethodSystemName) != RecurringPaymentType.Manual) + return false; + + if (recurringPayment.InitialOrder.Customer == null || (!customer.IsAdmin() && recurringPayment.InitialOrder.Customer.Id != customer.Id)) + return false; + + return true; + } + + + /// + /// Send a shipment + /// + /// Shipment + /// True to notify customer + public virtual void Ship(Shipment shipment, bool notifyCustomer) + { + if (shipment == null) + throw new ArgumentNullException("shipment"); + + var order = _orderService.GetOrderById(shipment.OrderId); + if (order == null) + throw new Exception("Order cannot be loaded"); + + if (shipment.ShippedDateUtc.HasValue) + throw new Exception("This shipment is already shipped"); + + shipment.ShippedDateUtc = DateTime.UtcNow; + _shipmentService.UpdateShipment(shipment); + + //process products with "Multiple warehouse" support enabled + foreach (var item in shipment.ShipmentItems) + { + var orderItem = _orderService.GetOrderItemById(item.OrderItemId); + _productService.BookReservedInventory(orderItem.Product, item.WarehouseId, -item.Quantity, + string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.Ship"), shipment.OrderId)); + } + + //check whether we have more items to ship + if (order.HasItemsToAddToShipment() || order.HasItemsToShip()) + 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("Shipment# {0} has been sent", shipment.Id), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + if (notifyCustomer) + { + //notify customer + int queuedEmailId = _workflowMessageService.SendShipmentSentCustomerNotification(shipment, order.CustomerLanguageId); + if (queuedEmailId > 0) + { + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("\"Shipped\" email (to customer) has been queued. Queued email identifier: {0}.", queuedEmailId), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + } + } + + //event + _eventPublisher.PublishShipmentSent(shipment); + + //check order status + CheckOrderStatus(order); + } + + /// + /// Marks a shipment as delivered + /// + /// Shipment + /// True to notify customer + public virtual void Deliver(Shipment shipment, bool notifyCustomer) + { + if (shipment == null) + throw new ArgumentNullException("shipment"); + + var order = shipment.Order; + if (order == null) + throw new Exception("Order cannot be loaded"); + + if (!shipment.ShippedDateUtc.HasValue) + throw new Exception("This shipment is not shipped yet"); + + if (shipment.DeliveryDateUtc.HasValue) + throw new Exception("This shipment is already delivered"); + + shipment.DeliveryDateUtc = DateTime.UtcNow; + _shipmentService.UpdateShipment(shipment); + + if (!order.HasItemsToAddToShipment() && !order.HasItemsToShip() && !order.HasItemsToDeliver()) + order.ShippingStatusId = (int)ShippingStatus.Delivered; + _orderService.UpdateOrder(order); + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("Shipment# {0} has been delivered", shipment.Id), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + if (notifyCustomer) + { + //send email notification + int queuedEmailId = _workflowMessageService.SendShipmentDeliveredCustomerNotification(shipment, order.CustomerLanguageId); + if (queuedEmailId > 0) + { + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("\"Delivered\" email (to customer) has been queued. Queued email identifier: {0}.", queuedEmailId), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + } + } + + //event + _eventPublisher.PublishShipmentDelivered(shipment); + + //check order status + CheckOrderStatus(order); + } + + + + /// + /// Gets a value indicating whether cancel is allowed + /// + /// Order + /// A value indicating whether cancel is allowed + public virtual bool CanCancelOrder(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (order.OrderStatus == OrderStatus.Cancelled) + return false; + + return true; + } + + /// + /// Cancels order + /// + /// Order + /// True to notify customer + public virtual void CancelOrder(Order order, bool notifyCustomer) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (!CanCancelOrder(order)) + throw new NopException("Cannot do cancel for order."); + + //Cancel order + SetOrderStatus(order, OrderStatus.Cancelled, notifyCustomer); + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = "Order has been cancelled", + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + //return (add) back redeemded reward points + ReturnBackRedeemedRewardPoints(order); + + //cancel recurring payments + var recurringPayments = _orderService.SearchRecurringPayments(initialOrderId: order.Id); + foreach (var rp in recurringPayments) + { + var errors = CancelRecurringPayment(rp); + //use "errors" variable? + } + + //Adjust inventory for already shipped shipments + //only products with "use multiple warehouses" + foreach (var shipment in order.Shipments) + { + foreach (var shipmentItem in shipment.ShipmentItems) + { + var orderItem = _orderService.GetOrderItemById(shipmentItem.OrderItemId); + if (orderItem == null) + continue; + + _productService.ReverseBookedInventory(orderItem.Product, shipmentItem, + string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.CancelOrder"), order.Id)); + } + } + //Adjust inventory + foreach (var orderItem in order.OrderItems) + { + _productService.AdjustInventory(orderItem.Product, orderItem.Quantity, orderItem.AttributesXml, + string.Format(_localizationService.GetResource("Admin.StockQuantityHistory.Messages.CancelOrder"), order.Id)); + } + + _eventPublisher.Publish(new OrderCancelledEvent(order)); + + } + + /// + /// Gets a value indicating whether order can be marked as authorized + /// + /// Order + /// A value indicating whether order can be marked as authorized + public virtual bool CanMarkOrderAsAuthorized(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (order.OrderStatus == OrderStatus.Cancelled) + return false; + + if (order.PaymentStatus == PaymentStatus.Pending) + return true; + + return false; + } + + /// + /// Marks order as authorized + /// + /// Order + public virtual void MarkAsAuthorized(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + order.PaymentStatusId = (int)PaymentStatus.Authorized; + _orderService.UpdateOrder(order); + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = "Order has been marked as authorized", + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + //check order status + CheckOrderStatus(order); + } + + + + /// + /// Gets a value indicating whether capture from admin panel is allowed + /// + /// Order + /// A value indicating whether capture from admin panel is allowed + public virtual bool CanCapture(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (order.OrderStatus == OrderStatus.Cancelled || + order.OrderStatus == OrderStatus.Pending) + return false; + + if (order.PaymentStatus == PaymentStatus.Authorized && + _paymentService.SupportCapture(order.PaymentMethodSystemName)) + return true; + + return false; + } + + /// + /// Capture an order (from admin panel) + /// + /// Order + /// A list of errors; empty list if no errors + public virtual IList Capture(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (!CanCapture(order)) + throw new NopException("Cannot do capture for order."); + + var request = new CapturePaymentRequest(); + CapturePaymentResult result = null; + try + { + //old info from placing order + request.Order = order; + result = _paymentService.Capture(request); + + if (result.Success) + { + var paidDate = order.PaidDateUtc; + 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 = "Order has been captured", + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + CheckOrderStatus(order); + + if (order.PaymentStatus == PaymentStatus.Paid) + { + ProcessOrderPaid(order); + } + } + } + catch (Exception exc) + { + if (result == null) + result = new CapturePaymentResult(); + result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); + } + + + //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); + + //log it + string logError = string.Format("Error capturing order #{0}. Error: {1}", order.Id, error); + _logger.InsertLog(LogLevel.Error, logError, logError); + } + return result.Errors; + } + + /// + /// Gets a value indicating whether order can be marked as paid + /// + /// Order + /// A value indicating whether order can be marked as paid + public virtual bool CanMarkOrderAsPaid(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (order.OrderStatus == OrderStatus.Cancelled) + return false; + + if (order.PaymentStatus == PaymentStatus.Paid || + order.PaymentStatus == PaymentStatus.Refunded || + order.PaymentStatus == PaymentStatus.Voided) + return false; + + return true; + } + + /// + /// Marks order as paid + /// + /// Order + public virtual void MarkOrderAsPaid(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (!CanMarkOrderAsPaid(order)) + throw new NopException("You can't mark this order as paid"); + + order.PaymentStatusId = (int)PaymentStatus.Paid; + order.PaidDateUtc = DateTime.UtcNow; + _orderService.UpdateOrder(order); + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = "Order has been marked as paid", + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + CheckOrderStatus(order); + + if (order.PaymentStatus == PaymentStatus.Paid) + { + ProcessOrderPaid(order); + } + } + + + + /// + /// Gets a value indicating whether refund from admin panel is allowed + /// + /// Order + /// A value indicating whether refund from admin panel is allowed + public virtual bool CanRefund(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (order.OrderTotal == decimal.Zero) + return false; + + //refund cannot be made if previously a partial refund has been already done. only other partial refund can be made in this case + if (order.RefundedAmount > decimal.Zero) + return false; + + //uncomment the lines below in order to disallow this operation for cancelled orders + //if (order.OrderStatus == OrderStatus.Cancelled) + // return false; + + if (order.PaymentStatus == PaymentStatus.Paid && + _paymentService.SupportRefund(order.PaymentMethodSystemName)) + return true; + + return false; + } + + /// + /// Refunds an order (from admin panel) + /// + /// Order + /// A list of errors; empty list if no errors + public virtual IList Refund(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (!CanRefund(order)) + throw new NopException("Cannot do refund for order."); + + var request = new RefundPaymentRequest(); + RefundPaymentResult result = null; + try + { + request.Order = order; + request.AmountToRefund = order.OrderTotal; + request.IsPartialRefund = false; + result = _paymentService.Refund(request); + if (result.Success) + { + //total amount refunded + decimal totalAmountRefunded = order.RefundedAmount + request.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("Order has been refunded. Amount = {0}", request.AmountToRefund), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + //check order status + CheckOrderStatus(order); + + //notifications + var orderRefundedStoreOwnerNotificationQueuedEmailId = _workflowMessageService.SendOrderRefundedStoreOwnerNotification(order, request.AmountToRefund, _localizationSettings.DefaultAdminLanguageId); + if (orderRefundedStoreOwnerNotificationQueuedEmailId > 0) + { + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("\"Order refunded\" email (to store owner) has been queued. Queued email identifier: {0}.", orderRefundedStoreOwnerNotificationQueuedEmailId), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + } + var orderRefundedCustomerNotificationQueuedEmailId = _workflowMessageService.SendOrderRefundedCustomerNotification(order, request.AmountToRefund, order.CustomerLanguageId); + if (orderRefundedCustomerNotificationQueuedEmailId > 0) + { + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("\"Order refunded\" email (to customer) has been queued. Queued email identifier: {0}.", orderRefundedCustomerNotificationQueuedEmailId), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + } + + //raise event + _eventPublisher.Publish(new OrderRefundedEvent(order, request.AmountToRefund)); + } + + } + catch (Exception exc) + { + if (result == null) + result = new RefundPaymentResult(); + result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); + } + + //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 refund order. {0}", error), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + //log it + string logError = string.Format("Error refunding order #{0}. Error: {1}", order.Id, error); + _logger.InsertLog(LogLevel.Error, logError, logError); + } + return result.Errors; + } + + /// + /// Gets a value indicating whether order can be marked as refunded + /// + /// Order + /// A value indicating whether order can be marked as refunded + public virtual bool CanRefundOffline(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (order.OrderTotal == decimal.Zero) + return false; + + //refund cannot be made if previously a partial refund has been already done. only other partial refund can be made in this case + if (order.RefundedAmount > decimal.Zero) + return false; + + //uncomment the lines below in order to disallow this operation for cancelled orders + //if (order.OrderStatus == OrderStatus.Cancelled) + // return false; + + if (order.PaymentStatus == PaymentStatus.Paid) + return true; + + return false; + } + + /// + /// Refunds an order (offline) + /// + /// Order + public virtual void RefundOffline(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (!CanRefundOffline(order)) + throw new NopException("You can't refund this order"); + + //amout to refund + decimal amountToRefund = order.OrderTotal; + + //total amount refunded + decimal totalAmountRefunded = order.RefundedAmount + amountToRefund; + + //update order info + order.RefundedAmount = totalAmountRefunded; + order.PaymentStatus = PaymentStatus.Refunded; + _orderService.UpdateOrder(order); + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("Order has been marked as refunded. Amount = {0}", amountToRefund), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + //check order status + CheckOrderStatus(order); + + //notifications + var orderRefundedStoreOwnerNotificationQueuedEmailId = _workflowMessageService.SendOrderRefundedStoreOwnerNotification(order, amountToRefund, _localizationSettings.DefaultAdminLanguageId); + if (orderRefundedStoreOwnerNotificationQueuedEmailId > 0) + { + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("\"Order refunded\" email (to store owner) has been queued. Queued email identifier: {0}.", orderRefundedStoreOwnerNotificationQueuedEmailId), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + } + var orderRefundedCustomerNotificationQueuedEmailId = _workflowMessageService.SendOrderRefundedCustomerNotification(order, amountToRefund, order.CustomerLanguageId); + if (orderRefundedCustomerNotificationQueuedEmailId > 0) + { + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("\"Order refunded\" email (to customer) has been queued. Queued email identifier: {0}.", orderRefundedCustomerNotificationQueuedEmailId), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + } + + //raise event + _eventPublisher.Publish(new OrderRefundedEvent(order, amountToRefund)); + } + + /// + /// Gets a value indicating whether partial refund from admin panel is allowed + /// + /// Order + /// Amount to refund + /// A value indicating whether refund from admin panel is allowed + public virtual bool CanPartiallyRefund(Order order, decimal amountToRefund) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (order.OrderTotal == decimal.Zero) + return false; + + //uncomment the lines below in order to allow this operation for cancelled orders + //if (order.OrderStatus == OrderStatus.Cancelled) + // return false; + + decimal canBeRefunded = order.OrderTotal - order.RefundedAmount; + if (canBeRefunded <= decimal.Zero) + return false; + + if (amountToRefund > canBeRefunded) + return false; + + if ((order.PaymentStatus == PaymentStatus.Paid || + order.PaymentStatus == PaymentStatus.PartiallyRefunded) && + _paymentService.SupportPartiallyRefund(order.PaymentMethodSystemName)) + return true; + + return false; + } + + /// + /// Partially refunds an order (from admin panel) + /// + /// Order + /// Amount to refund + /// A list of errors; empty list if no errors + public virtual IList PartiallyRefund(Order order, decimal amountToRefund) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (!CanPartiallyRefund(order, amountToRefund)) + throw new NopException("Cannot do partial refund for order."); + + var request = new RefundPaymentRequest(); + RefundPaymentResult result = null; + try + { + request.Order = order; + request.AmountToRefund = amountToRefund; + request.IsPartialRefund = true; + + result = _paymentService.Refund(request); + + if (result.Success) + { + //total amount refunded + decimal totalAmountRefunded = order.RefundedAmount + amountToRefund; + + //update order info + order.RefundedAmount = totalAmountRefunded; + //mark payment status as 'Refunded' if the order total amount is fully refunded + order.PaymentStatus = order.OrderTotal == totalAmountRefunded && result.NewPaymentStatus == PaymentStatus.PartiallyRefunded ? PaymentStatus.Refunded : result.NewPaymentStatus; + _orderService.UpdateOrder(order); + + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("Order has been partially refunded. Amount = {0}", amountToRefund), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + //check order status + CheckOrderStatus(order); + + //notifications + var orderRefundedStoreOwnerNotificationQueuedEmailId = _workflowMessageService.SendOrderRefundedStoreOwnerNotification(order, amountToRefund, _localizationSettings.DefaultAdminLanguageId); + if (orderRefundedStoreOwnerNotificationQueuedEmailId > 0) + { + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("\"Order refunded\" email (to store owner) has been queued. Queued email identifier: {0}.", orderRefundedStoreOwnerNotificationQueuedEmailId), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + } + var orderRefundedCustomerNotificationQueuedEmailId = _workflowMessageService.SendOrderRefundedCustomerNotification(order, amountToRefund, order.CustomerLanguageId); + if (orderRefundedCustomerNotificationQueuedEmailId > 0) + { + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("\"Order refunded\" email (to customer) has been queued. Queued email identifier: {0}.", orderRefundedCustomerNotificationQueuedEmailId), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + } + + //raise event + _eventPublisher.Publish(new OrderRefundedEvent(order, amountToRefund)); + } + } + catch (Exception exc) + { + if (result == null) + result = new RefundPaymentResult(); + result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); + } + + //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 partially refund order. {0}", error), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + //log it + string logError = string.Format("Error refunding order #{0}. Error: {1}", order.Id, error); + _logger.InsertLog(LogLevel.Error, logError, logError); + } + return result.Errors; + } + + /// + /// Gets a value indicating whether order can be marked as partially refunded + /// + /// Order + /// Amount to refund + /// A value indicating whether order can be marked as partially refunded + public virtual bool CanPartiallyRefundOffline(Order order, decimal amountToRefund) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (order.OrderTotal == decimal.Zero) + return false; + + //uncomment the lines below in order to allow this operation for cancelled orders + //if (order.OrderStatus == OrderStatus.Cancelled) + // return false; + + decimal canBeRefunded = order.OrderTotal - order.RefundedAmount; + if (canBeRefunded <= decimal.Zero) + return false; + + if (amountToRefund > canBeRefunded) + return false; + + if (order.PaymentStatus == PaymentStatus.Paid || + order.PaymentStatus == PaymentStatus.PartiallyRefunded) + return true; + + return false; + } + + /// + /// Partially refunds an order (offline) + /// + /// Order + /// Amount to refund + public virtual void PartiallyRefundOffline(Order order, decimal amountToRefund) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (!CanPartiallyRefundOffline(order, amountToRefund)) + throw new NopException("You can't partially refund (offline) this order"); + + //total amount refunded + decimal totalAmountRefunded = order.RefundedAmount + amountToRefund; + + //update order info + order.RefundedAmount = totalAmountRefunded; + //mark payment status as 'Refunded' if the order total amount is fully refunded + order.PaymentStatus = order.OrderTotal == totalAmountRefunded ? PaymentStatus.Refunded : PaymentStatus.PartiallyRefunded; + _orderService.UpdateOrder(order); + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("Order has been marked as partially refunded. Amount = {0}", amountToRefund), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + //check order status + CheckOrderStatus(order); + + //notifications + var orderRefundedStoreOwnerNotificationQueuedEmailId = _workflowMessageService.SendOrderRefundedStoreOwnerNotification(order, amountToRefund, _localizationSettings.DefaultAdminLanguageId); + if (orderRefundedStoreOwnerNotificationQueuedEmailId > 0) + { + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("\"Order refunded\" email (to store owner) has been queued. Queued email identifier: {0}.", orderRefundedStoreOwnerNotificationQueuedEmailId), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + } + var orderRefundedCustomerNotificationQueuedEmailId = _workflowMessageService.SendOrderRefundedCustomerNotification(order, amountToRefund, order.CustomerLanguageId); + if (orderRefundedCustomerNotificationQueuedEmailId > 0) + { + order.OrderNotes.Add(new OrderNote + { + Note = string.Format("\"Order refunded\" email (to customer) has been queued. Queued email identifier: {0}.", orderRefundedCustomerNotificationQueuedEmailId), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + } + //raise event + _eventPublisher.Publish(new OrderRefundedEvent(order, amountToRefund)); + } + + + + /// + /// Gets a value indicating whether void from admin panel is allowed + /// + /// Order + /// A value indicating whether void from admin panel is allowed + public virtual bool CanVoid(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (order.OrderTotal == decimal.Zero) + return false; + + //uncomment the lines below in order to allow this operation for cancelled orders + //if (order.OrderStatus == OrderStatus.Cancelled) + // return false; + + if (order.PaymentStatus == PaymentStatus.Authorized && + _paymentService.SupportVoid(order.PaymentMethodSystemName)) + return true; + + return false; + } + + /// + /// Voids order (from admin panel) + /// + /// Order + /// Voided order + public virtual IList Void(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (!CanVoid(order)) + throw new NopException("Cannot do void for order."); + + var request = new VoidPaymentRequest(); + VoidPaymentResult result = null; + try + { + request.Order = order; + result = _paymentService.Void(request); + + if (result.Success) + { + //update order info + order.PaymentStatus = result.NewPaymentStatus; + _orderService.UpdateOrder(order); + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = "Order has been voided", + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + //check order status + CheckOrderStatus(order); + } + } + catch (Exception exc) + { + if (result == null) + result = new VoidPaymentResult(); + result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); + } + + //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 voiding order. {0}", error), + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + //log it + string logError = string.Format("Error voiding order #{0}. Error: {1}", order.Id, error); + _logger.InsertLog(LogLevel.Error, logError, logError); + } + return result.Errors; + } + + /// + /// Gets a value indicating whether order can be marked as voided + /// + /// Order + /// A value indicating whether order can be marked as voided + public virtual bool CanVoidOffline(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (order.OrderTotal == decimal.Zero) + return false; + + //uncomment the lines below in order to allow this operation for cancelled orders + //if (order.OrderStatus == OrderStatus.Cancelled) + // return false; + + if (order.PaymentStatus == PaymentStatus.Authorized) + return true; + + return false; + } + + /// + /// Voids order (offline) + /// + /// Order + public virtual void VoidOffline(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + if (!CanVoidOffline(order)) + throw new NopException("You can't void this order"); + + order.PaymentStatusId = (int)PaymentStatus.Voided; + _orderService.UpdateOrder(order); + + //add a note + order.OrderNotes.Add(new OrderNote + { + Note = "Order has been marked as voided", + DisplayToCustomer = false, + CreatedOnUtc = DateTime.UtcNow + }); + _orderService.UpdateOrder(order); + + //check orer status + CheckOrderStatus(order); + } + + + + /// + /// Place order items in current user shopping cart. + /// + /// The order + public virtual void ReOrder(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + //move shopping cart items (if possible) + foreach (var orderItem in order.OrderItems) + { + _shoppingCartService.AddToCart(order.Customer, orderItem.Product, + ShoppingCartType.ShoppingCart, order.StoreId, + orderItem.AttributesXml, orderItem.UnitPriceExclTax, + orderItem.RentalStartDateUtc, orderItem.RentalEndDateUtc, + orderItem.Quantity, false); + } + + //set checkout attributes + //comment the code below if you want to disable this functionality + _genericAttributeService.SaveAttribute(order.Customer, SystemCustomerAttributeNames.CheckoutAttributes, order.CheckoutAttributesXml, order.StoreId); + } + + /// + /// Check whether return request is allowed + /// + /// Order + /// Result + public virtual bool IsReturnRequestAllowed(Order order) + { + if (!_orderSettings.ReturnRequestsEnabled) + return false; + + if (order == null || order.Deleted) + return false; + + //status should be compelte + if (order.OrderStatus != OrderStatus.Complete) + return false; + + //validate allowed number of days + if (_orderSettings.NumberOfDaysReturnRequestAvailable > 0) + { + var daysPassed = (DateTime.UtcNow - order.CreatedOnUtc).TotalDays; + if (daysPassed >= _orderSettings.NumberOfDaysReturnRequestAvailable) + return false; + } + + //ensure that we have at least one returnable product + return order.OrderItems.Any(oi => !oi.Product.NotReturnable); + } + + + + /// + /// Valdiate minimum order sub-total amount + /// + /// Shopping cart + /// true - OK; false - minimum order sub-total amount is not reached + public virtual bool ValidateMinOrderSubtotalAmount(IList cart) + { + if (cart == null) + throw new ArgumentNullException("cart"); + + //min order amount sub-total validation + if (cart.Any() && _orderSettings.MinOrderSubtotalAmount > decimal.Zero) + { + //subtotal + decimal orderSubTotalDiscountAmountBase; + List orderSubTotalAppliedDiscounts; + decimal subTotalWithoutDiscountBase; + decimal subTotalWithDiscountBase; + _orderTotalCalculationService.GetShoppingCartSubTotal(cart, _orderSettings.MinOrderSubtotalAmountIncludingTax, + out orderSubTotalDiscountAmountBase, out orderSubTotalAppliedDiscounts, + out subTotalWithoutDiscountBase, out subTotalWithDiscountBase); + + if (subTotalWithoutDiscountBase < _orderSettings.MinOrderSubtotalAmount) + return false; + } + + return true; + } + + /// + /// Valdiate minimum order total amount + /// + /// Shopping cart + /// true - OK; false - minimum order total amount is not reached + public virtual bool ValidateMinOrderTotalAmount(IList cart) + { + if (cart == null) + throw new ArgumentNullException("cart"); + + if (cart.Any() && _orderSettings.MinOrderTotalAmount > decimal.Zero) + { + decimal? shoppingCartTotalBase = _orderTotalCalculationService.GetShoppingCartTotal(cart); + if (shoppingCartTotalBase.HasValue && shoppingCartTotalBase.Value < _orderSettings.MinOrderTotalAmount) + return false; + } + + return true; + } + + /// + /// Get invoice ID + /// + /// + public virtual string GetInvoiceId() + { + var actYear = DateTime.UtcNow.Year; + int ident = _orderSettings.InvoiceIdent; + if (_orderSettings.InvoiceYear < actYear) + { + // Reset counter if a new year + ident = 1; + _orderSettings.InvoiceYear = actYear; + _settingService.SetSetting("ordersettings.invoiceyear", _orderSettings.InvoiceYear); + } + else + { + ident += 1; + } + _orderSettings.InvoiceIdent = ident; + // Update settings + _settingService.SetSetting("ordersettings.invoiceident", _orderSettings.InvoiceIdent); + + return string.Format("I-{0}.{1}", DateTime.UtcNow.Year, ident.ToString("D5")); + } + /// + /// Gets a value indicating whether payment workflow is required + /// + /// Shopping cart + /// A value indicating reward points should be used; null to detect current choice of the customer + /// true - OK; false - minimum order total amount is not reached + public virtual bool IsPaymentWorkflowRequired(IList cart, bool? useRewardPoints = null) + { + if (cart == null) + throw new ArgumentNullException("cart"); + + bool result = true; + + //check whether order total equals zero + decimal? shoppingCartTotalBase = _orderTotalCalculationService.GetShoppingCartTotal(cart, useRewardPoints: useRewardPoints); + if (shoppingCartTotalBase.HasValue && shoppingCartTotalBase.Value == decimal.Zero) + result = false; + return result; + } + + #endregion + } +} diff --git a/src/Libraries/Nop.Services/Orders/OrderTotalCalculationService.cs b/src/Libraries/Nop.Services/Orders/OrderTotalCalculationService.cs index 9c170915837..0e53e93d3fe 100644 --- a/src/Libraries/Nop.Services/Orders/OrderTotalCalculationService.cs +++ b/src/Libraries/Nop.Services/Orders/OrderTotalCalculationService.cs @@ -139,7 +139,7 @@ protected virtual decimal GetOrderSubtotalDiscount(Customer customer, if (discountAmount < decimal.Zero) discountAmount = decimal.Zero; - return discountAmount; + return RoundingHelper.RoundAmount(discountAmount); //round amount as it is displayed in this way and rounding errors my happen } /// @@ -209,9 +209,55 @@ protected virtual decimal GetOrderTotalDiscount(Customer customer, decimal order if (_shoppingCartSettings.RoundPricesDuringCalculation) discountAmount = RoundingHelper.RoundAmount(discountAmount); - return discountAmount; + return RoundingHelper.RoundAmount(discountAmount); //round amount as it is displayed in this way and rounding errors my happen } + protected virtual RewardPoints GetRedeemableRewardPoints(decimal rewardpointsbase, RewardPoints redeemedRewardPoints, Customer customer, bool? useRewardPoints = null, int ? rewardPointsOfOrder = null) + { + if (_rewardPointsSettings.Enabled && rewardpointsbase > decimal.Zero) + { + if (!rewardPointsOfOrder.HasValue) + { + if (!useRewardPoints.HasValue) + useRewardPoints = customer.GetAttribute(SystemCustomerAttributeNames.UseRewardPointsDuringCheckout, _genericAttributeService, _storeContext.CurrentStore.Id); + if (useRewardPoints.Value) + { + var rewardPointsBalance = _rewardPointService.GetRewardPointsBalance(customer.Id, _storeContext.CurrentStore.Id); + + //taxable points. + if ( _rewardPointService.CheckMinimumRewardPointsToUseRequirement(rewardPointsBalance.Points)) + { + if (rewardpointsbase > rewardPointsBalance.Amount) + { + redeemedRewardPoints.Points = rewardPointsBalance.Points; + } + else + { + if (rewardpointsbase > decimal.Zero) + redeemedRewardPoints.Points = _rewardPointService.ConvertAmountToRewardPoints(rewardpointsbase); + } + rewardpointsbase -= redeemedRewardPoints.Amount; + } + //purchased reward points (can be always spent) + if (rewardpointsbase > rewardPointsBalance.AmountPurchased) + { + redeemedRewardPoints.PointsPurchased = rewardPointsBalance.PointsPurchased; + } + else + { + if (rewardpointsbase > decimal.Zero) + redeemedRewardPoints.PointsPurchased = _rewardPointService.ConvertAmountToRewardPoints(rewardpointsbase); + } + } + } + else + { //return corrected taxable points (only for updateorder total). Purchased points not needed here. + redeemedRewardPoints.Points = _rewardPointService.ConvertAmountToRewardPoints(Math.Min(_rewardPointService.ConvertRewardPointsToAmount(rewardPointsOfOrder ?? 0), rewardpointsbase)); + } + } + + return redeemedRewardPoints; + } #endregion #region Methods @@ -220,7 +266,7 @@ protected virtual decimal GetOrderTotalDiscount(Customer customer, decimal order /// Gets shopping cart subtotal /// /// Cart - /// A value indicating whether submitted prices do include tax + /// A value indicating whether calculated amounts should include tax /// Applied discount amount /// Applied discounts /// Sub total (without discount) @@ -231,32 +277,38 @@ public virtual void GetShoppingCartSubTotal(IList cart, out decimal subTotalWithoutDiscount, out decimal subTotalWithDiscount) { TaxSummary taxSummary; + decimal subTotalRewardPointsBaseAmount; + GetShoppingCartSubTotal(cart, includingTax, out discountAmount, out appliedDiscounts, - out subTotalWithoutDiscount, out subTotalWithDiscount, out taxSummary); + out subTotalWithoutDiscount, out subTotalWithDiscount, out taxSummary, + out subTotalRewardPointsBaseAmount); } /// /// Gets shopping cart subtotal /// /// Cart - /// A value indicating whether submitted prices do include tax + /// A value indicating whether calculated amounts should include tax /// Applied discount amount /// Applied discounts /// Sub total (without discount) /// Sub total (with discount) /// Tax rates summary (of order sub total) + /// Subtotal base amount for earned reward points calculation public virtual void GetShoppingCartSubTotal(IList cart, bool includingTax, out decimal discountAmount, out List appliedDiscounts, out decimal subTotalWithoutDiscount, out decimal subTotalWithDiscount, - out TaxSummary taxSummary) + out TaxSummary taxSummary, + out decimal subTotalEarnedRewardPointsBaseAmount) { taxSummary = new TaxSummary(includingTax); discountAmount = decimal.Zero; appliedDiscounts = new List(); subTotalWithoutDiscount = decimal.Zero; subTotalWithDiscount = decimal.Zero; + subTotalEarnedRewardPointsBaseAmount = decimal.Zero; decimal subTotalAmount = decimal.Zero; @@ -270,25 +322,30 @@ public virtual void GetShoppingCartSubTotal(IList cart, foreach (var shoppingCartItem in cart) { decimal itemAmount = decimal.Zero; - decimal taxRate; + decimal taxRate = decimal.Zero; //restored cartitem - if (shoppingCartItem.VatRate.HasValue && shoppingCartItem.SubTotalExclTax != null && shoppingCartItem.SubTotalInclTax != null) + if (shoppingCartItem.TaxRate.HasValue && shoppingCartItem.SubTotalExclTax != null && shoppingCartItem.SubTotalInclTax != null) { itemAmount = includingTax ? shoppingCartItem.SubTotalInclTax ?? 0 : shoppingCartItem.SubTotalExclTax ?? 0; - taxRate = shoppingCartItem.VatRate ?? 0; + taxRate = shoppingCartItem.TaxRate ?? 0; subTotalAmount += itemAmount; } //normal item else { decimal sciSubTotal = _priceCalculationService.GetSubTotal(shoppingCartItem); - itemAmount = _taxService.GetProductPrice(shoppingCartItem.Product, sciSubTotal, includingTax, customer, out taxRate); + var newAttributesXml = shoppingCartItem.AttributesXml; + itemAmount = _taxService.GetProductPrice(shoppingCartItem.Product, sciSubTotal, out taxRate, ref newAttributesXml, includingTax); + shoppingCartItem.AttributesXml = newAttributesXml; subTotalAmount += itemAmount; } + //reward points base + subTotalEarnedRewardPointsBaseAmount += !shoppingCartItem.Product.ExcludeFromRewardPoints ? itemAmount : decimal.Zero; + //tax rates - var attributesXml = shoppingCartItem.AttributesXml; + string attributesXml = shoppingCartItem.AttributesXml; SortedDictionary attributeTaxWeight = !String.IsNullOrEmpty(attributesXml) ? _productAttributeParser.ParseTaxAttribute(attributesXml) : null; if (attributeTaxWeight == null || !attributeTaxWeight.Any()) { @@ -313,6 +370,7 @@ public virtual void GetShoppingCartSubTotal(IList cart, decimal taxRate; decimal itemAmount = _taxService.GetCheckoutAttributePrice(attributeValue, includingTax, customer, out taxRate); subTotalAmount += itemAmount; + subTotalEarnedRewardPointsBaseAmount += itemAmount; //tax rates if (itemAmount > decimal.Zero) //taxRate > decimal.Zero && taxSummary.AddRate(taxRate, itemAmount); @@ -321,15 +379,23 @@ public virtual void GetShoppingCartSubTotal(IList cart, } //subtotal discount - var subDisc = GetOrderSubtotalDiscount(customer, subTotalAmount, out appliedDiscounts); + var subDisc = RoundingHelper.RoundAmount(GetOrderSubtotalDiscount(customer, subTotalAmount, out appliedDiscounts)); //round discount, otherwise rounding errors may happen if (subDisc != decimal.Zero) + { taxSummary.SetSubtotalDiscAmount(subDisc); + //adjust earned reward points base amount + if (subTotalAmount != decimal.Zero) + subTotalEarnedRewardPointsBaseAmount = subTotalEarnedRewardPointsBaseAmount * (1 - subDisc / subTotalAmount); + if (_shoppingCartSettings.RoundPricesDuringCalculation) + subTotalEarnedRewardPointsBaseAmount = RoundingHelper.RoundAmount(subTotalEarnedRewardPointsBaseAmount); + } + + //overall totals - taxSummary.CalculateAmounts(); discountAmount = taxSummary.TotalSubTotalDiscAmount; subTotalWithoutDiscount = taxSummary.TotalSubTotalAmount; - subTotalWithDiscount = taxSummary.TotalAmountIncludingVAT; + subTotalWithDiscount = includingTax ? taxSummary.TotalAmountIncludingTax : taxSummary.TotalAmount; if (subTotalWithDiscount < decimal.Zero) subTotalWithDiscount = decimal.Zero; @@ -357,11 +423,22 @@ public virtual void UpdateOrderTotals(UpdateOrderParameters updateOrderParameter List orderAppliedDiscounts; List subTotalAppliedDiscounts; List shippingAppliedDiscounts; + List appliedGiftCards; TaxSummary taxSummaryIncl; TaxSummary taxSummaryExcl; - decimal shoppingCartTax = GetTaxTotal(restoredCart, false, out taxSummaryExcl, out orderAppliedDiscounts, out subTotalAppliedDiscounts, out shippingAppliedDiscounts); - shoppingCartTax = GetTaxTotal(restoredCart, true, out taxSummaryIncl, out orderAppliedDiscounts, out subTotalAppliedDiscounts, out shippingAppliedDiscounts); + RewardPoints redeemedRewardPointsIncl; + RewardPoints redeemedRewardPointsExcl; + bool earnedRewardPointsAreTaxable = _rewardPointsSettings.EarnedRewardPointsAreTaxable; + + //reward points of order + var rewardPointsOfOrder = _rewardPointService.GetRewardPointsHistory(customer.Id, true).FirstOrDefault(history => history.UsedWithOrder == updatedOrder); + var redeemedPointsToOrderAmount = rewardPointsOfOrder != null && earnedRewardPointsAreTaxable ? -rewardPointsOfOrder.Points : 0; + decimal earnedRewardPointsBaseAmountIncl; + decimal earnedRewardPointsBaseAmountExcl; + + decimal shoppingCartTaxExcl = GetTaxTotal(restoredCart, false, out taxSummaryExcl, out orderAppliedDiscounts, out subTotalAppliedDiscounts, out shippingAppliedDiscounts, out appliedGiftCards, out redeemedRewardPointsExcl, out earnedRewardPointsBaseAmountExcl, useRewardPoints: false, rewardPointsOfOrder: redeemedPointsToOrderAmount); + decimal shoppingCartTaxIncl = GetTaxTotal(restoredCart, true, out taxSummaryIncl, out orderAppliedDiscounts, out subTotalAppliedDiscounts, out shippingAppliedDiscounts, out appliedGiftCards, out redeemedRewardPointsIncl, out earnedRewardPointsBaseAmountIncl, useRewardPoints: false, rewardPointsOfOrder: redeemedPointsToOrderAmount); #region discounts foreach (var discount in orderAppliedDiscounts) @@ -384,14 +461,21 @@ public virtual void UpdateOrderTotals(UpdateOrderParameters updateOrderParameter updatedOrder.OrderSubtotalInclTax = taxSummaryIncl.TotalSubTotalAmount; updatedOrder.OrderSubTotalDiscountExclTax = taxSummaryExcl.TotalSubTotalDiscAmount; updatedOrder.OrderSubTotalDiscountInclTax = taxSummaryIncl.TotalSubTotalDiscAmount; - updatedOrder.OrderShippingExclTax = taxSummaryExcl.TotalShippingAmount; - updatedOrder.OrderShippingInclTax = taxSummaryIncl.TotalShippingAmount; - updatedOrder.OrderTax = shoppingCartTax; - updatedOrder.OrderDiscount = includingTax ? taxSummaryIncl.TotalInvDiscAmount : taxSummaryExcl.TotalInvDiscAmount; + updatedOrder.OrderShippingExclTax = taxSummaryExcl.TotalShippingAmountTaxable ?? decimal.Zero; + updatedOrder.OrderShippingInclTax = taxSummaryIncl.TotalShippingAmountTaxable ?? decimal.Zero; + updatedOrder.OrderShippingNonTaxable = includingTax ? (taxSummaryIncl.TotalShippingAmountNonTaxable ?? decimal.Zero) : (taxSummaryExcl.TotalShippingAmountNonTaxable ?? decimal.Zero); + updatedOrder.OrderTax = includingTax ? shoppingCartTaxIncl : shoppingCartTaxExcl; + updatedOrder.OrderDiscount = taxSummaryExcl.TotalInvDiscAmount; + updatedOrder.OrderDiscountIncl = taxSummaryIncl.TotalInvDiscAmount; updatedOrder.OrderAmount = includingTax ? taxSummaryIncl.TotalAmount : taxSummaryExcl.TotalAmount; - updatedOrder.OrderAmountIncl = includingTax ? taxSummaryIncl.TotalAmountIncludingVAT : taxSummaryExcl.TotalAmountIncludingVAT; + updatedOrder.OrderAmountIncl = includingTax ? taxSummaryIncl.TotalAmountIncludingTax : taxSummaryExcl.TotalAmountIncludingTax; + updatedOrder.EarnedRewardPointsBaseAmountExcl = earnedRewardPointsBaseAmountExcl; + updatedOrder.EarnedRewardPointsBaseAmountIncl = earnedRewardPointsBaseAmountIncl; + updatedOrder.PaymentMethodAdditionalFeeInclTax = taxSummaryIncl.TotalPaymentFeeAmountTaxable; + updatedOrder.PaymentMethodAdditionalFeeExclTax = taxSummaryExcl.TotalPaymentFeeAmountTaxable; + updatedOrder.PaymentMethodAdditionalFeeNonTaxable = includingTax ? (taxSummaryIncl.TotalPaymentFeeAmountNonTaxable ?? decimal.Zero) : (taxSummaryExcl.TotalPaymentFeeAmountNonTaxable ?? decimal.Zero); - var total = includingTax ? taxSummaryIncl.TotalAmountIncludingVAT : taxSummaryExcl.TotalAmountIncludingVAT; //gets updated later on + var total = includingTax ? taxSummaryIncl.TotalAmountIncludingTax : taxSummaryExcl.TotalAmountIncludingTax; //gets updated later on //get shopping cart item which has been updated var updatedShoppingCartItem = restoredCart.FirstOrDefault(shoppingCartItem => shoppingCartItem.Id == updatedOrderItem.Id); @@ -406,7 +490,7 @@ public virtual void UpdateOrderTotals(UpdateOrderParameters updateOrderParameter updatedOrderItem.PriceExclTax = updateOrderParameters.SubTotalExclTax; updatedOrderItem.PriceInclTax = updateOrderParameters.SubTotalInclTax; updatedOrderItem.Quantity = restoredCart.FirstOrDefault(item => item.Id == updatedOrderItem.Id).Quantity; - updatedOrderItem.VatRate = restoredCart.FirstOrDefault(item => item.Id == updatedOrderItem.Id).VatRate ?? 0; + updatedOrderItem.TaxRate = restoredCart.FirstOrDefault(item => item.Id == updatedOrderItem.Id).TaxRate ?? 0; } #endregion @@ -527,28 +611,61 @@ public virtual void UpdateOrderTotals(UpdateOrderParameters updateOrderParameter } //reward points - var rewardPointsOfOrder = _rewardPointService.GetRewardPointsHistory(customer.Id, true).FirstOrDefault(history => history.UsedWithOrder == updatedOrder); if (rewardPointsOfOrder != null) { - var rewardPoints = -rewardPointsOfOrder.Points; - var rewardPointsAmount = ConvertRewardPointsToAmount(rewardPoints); - if (total < rewardPointsAmount) + var rewardPoints = new RewardPoints(_rewardPointService) { - rewardPoints = ConvertAmountToRewardPoints(total); - rewardPointsAmount = total; + Points = -rewardPointsOfOrder.Points, + PointsPurchased = -rewardPointsOfOrder.PointsPurchased + }; + + var redeemedRewardPoints = includingTax ? redeemedRewardPointsIncl : redeemedRewardPointsExcl; + + if (earnedRewardPointsAreTaxable && redeemedRewardPoints.Amount != rewardPoints.Amount) + { + rewardPoints.Points = redeemedRewardPoints.Points; + var corrtotal = Math.Max(total - redeemedRewardPoints.Amount, decimal.Zero); + + if (corrtotal < rewardPoints.AmountPurchased) + { + rewardPoints.PointsPurchased = _rewardPointService.ConvertAmountToRewardPoints(corrtotal); + } } - if (total > decimal.Zero) - total -= rewardPointsAmount; + else + { + //purchased points have been applied to order total. To get base for reward points add them again. + var corrtotal = Math.Max(total - rewardPoints.AmountPurchased, decimal.Zero); + if (corrtotal < rewardPoints.Amount) + { + rewardPoints.Points = _rewardPointService.ConvertAmountToRewardPoints(corrtotal); + } + corrtotal = Math.Max(total - rewardPoints.Amount, decimal.Zero); + if (corrtotal < rewardPoints.AmountPurchased) + { + rewardPoints.PointsPurchased = _rewardPointService.ConvertAmountToRewardPoints(corrtotal); + } + } + + total += -rewardPoints.Amount + -rewardPoints.AmountPurchased; + var corrRewardPoints = new RewardPoints(_rewardPointService); //uncomment here for the return unused reward points if new order total less redeemed reward points amount - if (rewardPoints < -rewardPointsOfOrder.Points) - _rewardPointService.AddRewardPointsHistoryEntry(customer, -rewardPointsOfOrder.Points - rewardPoints, _storeContext.CurrentStore.Id, "Return unused reward points"); + if (rewardPoints.Points < -rewardPointsOfOrder.Points || rewardPoints.PointsPurchased < -rewardPointsOfOrder.PointsPurchased) + { + corrRewardPoints.Points = -rewardPointsOfOrder.Points - rewardPoints.Points; + corrRewardPoints.PointsPurchased = -rewardPointsOfOrder.PointsPurchased - rewardPoints.PointsPurchased; + _rewardPointService.AddRewardPointsHistoryEntry(customer, corrRewardPoints, _storeContext.CurrentStore.Id, "Returned unused reward points due to order correction."); + } //comment end - if (rewardPointsAmount != rewardPointsOfOrder.UsedAmount) + if (rewardPoints.Amount != rewardPointsOfOrder.UsedAmount || rewardPoints.AmountPurchased != rewardPointsOfOrder.UsedAmountPurchased) { - rewardPointsOfOrder.UsedAmount = rewardPointsAmount; - rewardPointsOfOrder.Points = -rewardPoints; + rewardPointsOfOrder.UsedAmount = rewardPoints.Amount; + rewardPointsOfOrder.Points = -rewardPoints.Points; + rewardPointsOfOrder.UsedAmountPurchased = rewardPoints.AmountPurchased; + rewardPointsOfOrder.PointsPurchased = -rewardPoints.PointsPurchased; + rewardPointsOfOrder.PointsBalance += -corrRewardPoints.Points; + rewardPointsOfOrder.PointsBalancePurchased = -corrRewardPoints.PointsPurchased; _rewardPointService.UpdateRewardPointsHistoryEntry(rewardPointsOfOrder); } } @@ -668,7 +785,7 @@ public virtual decimal AdjustShippingRate(decimal shippingRate, /// Gets shopping cart shipping total ///
    /// Cart - /// A value indicating whether submitted prices do include tax + /// A value indicating whether calculated amounts should include tax /// Shipping total public virtual decimal? GetShoppingCartShippingTotal(IList cart, bool includingTax) { @@ -680,7 +797,7 @@ public virtual decimal AdjustShippingRate(decimal shippingRate, /// Gets shopping cart shipping total ///
    /// Cart - /// A value indicating whether submitted prices do include tax + /// A value indicating whether calculated amounts should include tax /// Applied tax rate /// Shipping total public virtual decimal? GetShoppingCartShippingTotal(IList cart, bool includingTax, @@ -694,7 +811,7 @@ public virtual decimal AdjustShippingRate(decimal shippingRate, /// Gets shopping cart shipping total /// /// Cart - /// A value indicating whether submitted prices do include tax + /// A value indicating whether calculated amounts should include tax /// Applied tax rate /// Applied discounts /// Shipping total @@ -705,16 +822,22 @@ public virtual decimal AdjustShippingRate(decimal shippingRate, decimal? shippingTotalTaxed = null; appliedDiscounts = new List(); taxRate = decimal.Zero; - + var customer = cart.GetCustomer(); + + bool isFreeShipping = IsFreeShipping(cart); if (isFreeShipping) - return decimal.Zero; + { + //we always need a taxrate, get it from GetShippingPrice with price Zero + return _taxService.GetShippingPrice(decimal.Zero, includingTax, customer, out taxRate); + } ShippingOption shippingOption = null; if (customer != null) shippingOption = customer.GetAttribute(SystemCustomerAttributeNames.SelectedShippingOption, _genericAttributeService, _storeContext.CurrentStore.Id); + if (shippingOption != null) { @@ -755,6 +878,7 @@ public virtual decimal AdjustShippingRate(decimal shippingRate, } } + if (fixedRate.HasValue) { //adjust shipping rate @@ -785,10 +909,6 @@ public virtual decimal AdjustShippingRate(decimal shippingRate, return shippingTotalTaxed; } - - - - /// /// Gets tax /// @@ -804,19 +924,27 @@ public virtual decimal GetTaxTotal(IList cart, out TaxSummary List appliedDiscounts; List subTotalAppliedDiscounts; List shippingAppliedDiscounts; + List appliedGiftCards; bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; - return GetTaxTotal(cart, includingTax, out taxSummary, out appliedDiscounts, out subTotalAppliedDiscounts, out shippingAppliedDiscounts, usePaymentMethodAdditionalFee); + RewardPoints redeemedRewardPoints; + decimal earnedRewardPointsBaseAmount; + return GetTaxTotal(cart, includingTax, out taxSummary, out appliedDiscounts, out subTotalAppliedDiscounts, out shippingAppliedDiscounts, out appliedGiftCards, out redeemedRewardPoints, out earnedRewardPointsBaseAmount, usePaymentMethodAdditionalFee); } /// /// Gets tax /// /// Shopping cart - /// A value indicating whether submitted prices do include tax + /// A value indicating whether calculated amounts should include tax /// Tax rates summary /// Applied invoice discounts /// Applied subtotal discounts /// Applied shipping discounts + /// Applied gift cards + /// Taxable reward points to redeem + /// Reward points base amount for earned points /// A value indicating whether we should use payment method additional fee when calculating tax + /// A value indicating reward points should be used; null to detect current choice of the customer + /// Only in the case of updating a stored order: give the reward points amount used in the order. /// Tax total public virtual decimal GetTaxTotal(IList cart, bool includingTax, @@ -824,9 +952,17 @@ public virtual decimal GetTaxTotal(IList cart, out List appliedDiscounts, out List subTotalAppliedDiscounts, out List shippingAppliedDiscounts, - bool usePaymentMethodAdditionalFee = true) + out List appliedGiftCards, + out RewardPoints redeemedRewardPoints, + out decimal earnedRewardPointsBaseAmount, + bool usePaymentMethodAdditionalFee = true, + bool? useRewardPoints = null, + int? rewardPointsOfOrder = null + ) { + redeemedRewardPoints = new RewardPoints(_rewardPointService); + if (cart == null) throw new ArgumentNullException("cart"); @@ -848,59 +984,160 @@ public virtual decimal GetTaxTotal(IList cart, GetShoppingCartSubTotal(cart, includingTax, out orderSubTotalDiscountAmount, out subTotalAppliedDiscounts, out subTotalWithoutDiscountBase, out subTotalWithDiscountBase, - out taxSummary); + out taxSummary, out earnedRewardPointsBaseAmount); decimal taxRate; //shipping decimal? shippingAmount = GetShoppingCartShippingTotal(cart, includingTax, out taxRate, out shippingAppliedDiscounts); - if ((shippingAmount ?? 0) > decimal.Zero && _taxSettings.ShippingIsTaxable) - taxSummary.AddRate(taxRate, shippingAmount: shippingAmount ?? 0); + if ((shippingAmount ?? decimal.Zero) >= decimal.Zero) //pass also zero, otherwise taxsummary does not know that there is a shipping + if (_taxSettings.ShippingIsTaxable) + { + taxSummary.AddRate(taxRate, shippingAmount: shippingAmount ?? decimal.Zero); + } + else + { + taxSummary.TotalShippingAmountNonTaxable = shippingAmount; //no need to recalulate without tax. GetShippingPrice returns price without tax when _taxSettings.ShippingIsTaxable = false. + } - //payment method additional fee - taxSummary.CalculateAmounts(); - decimal discountbaseFee = includingTax ? taxSummary.TotalAmountIncludingVAT : taxSummary.TotalAmount; + if (_rewardPointsSettings.AwardPointsIncludeShipping) + earnedRewardPointsBaseAmount += shippingAmount ?? decimal.Zero; - if (usePaymentMethodAdditionalFee && !String.IsNullOrEmpty(paymentMethodSystemName)) + + //Order total invoice discount + + //note if shipping is not taxable then no ivDiscount is calculated for it. It would be possible to do it separately. It can't be added here as tax amount would get changed. + //please note also, then when considering it, ivDiscount must be split + decimal discountbase = includingTax ? taxSummary.TotalAmountIncludingTax : taxSummary.TotalAmount; + var ivDiscount = GetOrderTotalDiscount(customer, discountbase, out appliedDiscounts); + if (ivDiscount != decimal.Zero) + taxSummary.SetTotalInvDiscAmount(Math.Min(ivDiscount, discountbase), discountbase); + + earnedRewardPointsBaseAmount -= ivDiscount; + + + #region reward points + //redeemed reward points applied to order amount (taxable) and payment amount + //we anticipate non taxable reward points here for taxable payment fee calculation + //if hasRewardPointsProduct, then don't redeem points. Would lead to discount plus it generates new reward points when buying a reward points product and paying with purchased points. + if (cart.HasRewardPointsProduct()) + earnedRewardPointsBaseAmount = decimal.Zero; + + if (_rewardPointsSettings.Enabled && earnedRewardPointsBaseAmount > decimal.Zero) { - decimal paymentMethodAdditionalFee = _taxService.GetPaymentMethodAdditionalFee(_paymentService.GetAdditionalHandlingFee(cart, paymentMethodSystemName), includingTax, customer, out taxRate); + if (!useRewardPoints.HasValue) + useRewardPoints = customer.GetAttribute(SystemCustomerAttributeNames.UseRewardPointsDuringCheckout, _genericAttributeService, _storeContext.CurrentStore.Id); - if (paymentMethodAdditionalFee != decimal.Zero ) - //tfc case of taxable fee > 0 - if (paymentMethodAdditionalFee > decimal.Zero && _taxSettings.PaymentMethodAdditionalFeeIsTaxable) - taxSummary.AddRate(taxRate, paymentfeeAmount: paymentMethodAdditionalFee); - //tfc case fee < 0 is considered as discount or as surcharge when not taxable + if (useRewardPoints.Value) + { + redeemedRewardPoints = GetRedeemableRewardPoints(earnedRewardPointsBaseAmount, redeemedRewardPoints, customer, useRewardPoints, rewardPointsOfOrder); + decimal consumedEarnedPointsAmount = Math.Min(redeemedRewardPoints.Amount, earnedRewardPointsBaseAmount); + if (_rewardPointsSettings.EarnedRewardPointsAreTaxable && redeemedRewardPoints.Amount != decimal.Zero) + taxSummary.SetRewardPointsDiscAmount(consumedEarnedPointsAmount, earnedRewardPointsBaseAmount); else - taxSummary.SetPaymentFeeDiscAmount(paymentMethodAdditionalFee, discountbaseFee); + taxSummary.TotalRewardPointsAmountNonTaxable = consumedEarnedPointsAmount; + + earnedRewardPointsBaseAmount -= consumedEarnedPointsAmount; + + taxSummary.TotalRewardPointsAmountPurchased = redeemedRewardPoints.AmountPurchased; + + if (!_rewardPointsSettings.AwardPointsExcludePurchasedRewardPoints) + earnedRewardPointsBaseAmount -= redeemedRewardPoints.AmountPurchased; + } } - //Order total invoice discount should be considered in tax calculation - taxSummary.CalculateAmounts(); - decimal discountbase = includingTax ? taxSummary.TotalAmountIncludingVAT : taxSummary.TotalAmount; - var ivDiscount = GetOrderTotalDiscount(customer, discountbase, out appliedDiscounts); - if (ivDiscount != decimal.Zero) - taxSummary.SetTotalInvDiscAmount(ivDiscount, discountbase); + #endregion + //we anticipate gift cards here for taxable payment fee calculation + #region Applied gift cards + + //let's apply gift cards now (gift cards that can be used) + decimal giftCardBase = taxSummary.TotalPaymentAmount; + appliedGiftCards = new List(); + if (!cart.IsRecurring()) + { + //we don't apply gift cards for recurring products + var giftCards = _giftCardService.GetActiveGiftCardsAppliedByCustomer(customer); + if (giftCards != null) + foreach (var gc in giftCards) + if (giftCardBase > decimal.Zero) + { + decimal remainingAmount = gc.GetGiftCardRemainingAmount(); + decimal amountCanBeUsed = giftCardBase > remainingAmount ? + remainingAmount : + giftCardBase; + + //round + if (_shoppingCartSettings.RoundPricesDuringCalculation) + amountCanBeUsed = RoundingHelper.RoundAmount(amountCanBeUsed); + + //reduce base + giftCardBase -= amountCanBeUsed; + taxSummary.TotalGiftcardsAmount += amountCanBeUsed; + + if (!_rewardPointsSettings.AwardPointsExcludeGiftCard) + earnedRewardPointsBaseAmount -= amountCanBeUsed; + + var appliedGiftCard = new AppliedGiftCard(); + appliedGiftCard.GiftCard = gc; + appliedGiftCard.AmountCanBeUsed = amountCanBeUsed; + appliedGiftCards.Add(appliedGiftCard); + } + } + if (!_rewardPointsSettings.AwardPointsExcludeGiftCard) + earnedRewardPointsBaseAmount -= taxSummary.TotalGiftcardsAmount ?? decimal.Zero; + + #endregion + + #region Payment Fee + //payment method additional fee + if (usePaymentMethodAdditionalFee && !String.IsNullOrEmpty(paymentMethodSystemName)) + { + decimal additionalFee = _paymentService.GetAdditionalHandlingFee(cart, paymentMethodSystemName); + decimal paymentMethodAdditionalFee = _taxService.GetPaymentMethodAdditionalFee(additionalFee, includingTax, customer, out taxRate); //if not taxable returns price + + if (_taxSettings.PaymentMethodAdditionalFeeIsTaxable) + { + if (paymentMethodAdditionalFee != decimal.Zero) + //case of normal taxable fee > 0, taxcategory != 0 + if (paymentMethodAdditionalFee > decimal.Zero && _taxSettings.PaymentMethodAdditionalFeeTaxClassId != 0) + taxSummary.AddRate(taxRate, paymentFeeAmount: paymentMethodAdditionalFee); + //case of fee < 0 considered as discount or > 0 considered as surcharge when taxcategory is not set + else + { + decimal discountbaseFee = taxSummary.TotalBaseAmountForPaymentFeeCalculation; + taxSummary.SetPaymentFeeOrDiscAmount(paymentMethodAdditionalFee, discountbaseFee); + } + } + else + taxSummary.TotalPaymentFeeAmountNonTaxable = paymentMethodAdditionalFee; + + if (_rewardPointsSettings.AwardPointsIncludePaymentMethodAdditionalFee && paymentMethodAdditionalFee > decimal.Zero) + earnedRewardPointsBaseAmount += paymentMethodAdditionalFee; + else + //independent of AwardPointsIncludePaymentMethodAdditionalFee setting, reduce for a possible discount + earnedRewardPointsBaseAmount += paymentMethodAdditionalFee; + } + #endregion //add at least one tax rate (0%) if (!taxSummary.TaxRates.Any()) taxSummary.AddRate(0, 0); - taxSummary.CalculateAmounts(); + //total tax + decimal taxTotal = taxSummary.TotalAmountTax; - //summarize taxes - decimal taxTotal = taxSummary.TotalAmountVAT; //ensure that tax is equal or greater than zero if (taxTotal < decimal.Zero) taxTotal = decimal.Zero; + //ensure that base amount for earning reward points is not negative + if (earnedRewardPointsBaseAmount < decimal.Zero) + earnedRewardPointsBaseAmount = decimal.Zero; + return taxTotal; } - - - - /// /// Gets shopping cart total /// @@ -915,10 +1152,10 @@ public virtual decimal GetTaxTotal(IList cart, List appliedDiscounts; List subTotalAppliedDiscounts; List shippingAppliedDiscounts; - int redeemedRewardPoints; - decimal redeemedRewardPointsAmount; + RewardPoints redeemedRewardPoints; List appliedGiftCards; TaxSummary taxSummary; + decimal earnedRewardPointsBaseAmount; bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; return GetShoppingCartTotal(cart, @@ -928,8 +1165,8 @@ public virtual decimal GetTaxTotal(IList cart, out shippingAppliedDiscounts, out appliedGiftCards, out redeemedRewardPoints, - out redeemedRewardPointsAmount, out taxSummary, + out earnedRewardPointsBaseAmount, includingTax, useRewardPoints, usePaymentMethodAdditionalFee); @@ -944,10 +1181,10 @@ public virtual decimal GetTaxTotal(IList cart, /// Applied subtotal discounts /// Applied shipping discounts /// Applied gift cards - /// Reward points to redeem - /// Reward points amount in primary store currency to redeem + /// Redeemed reward points /// Tax summary - /// A value indicating whether submitted prices do include tax + /// Base amount for reward points to earn + /// A value indicating whether calculated amounts should include tax /// A value indicating reward points should be used; null to detect current choice of the customer /// A value indicating whether we should use payment method additional fee when calculating order total /// Shopping cart total;Null if shopping cart total couldn't be calculated now @@ -956,206 +1193,43 @@ public virtual decimal GetTaxTotal(IList cart, out List subTotalAppliedDiscounts, out List shippingAppliedDiscounts, out List appliedGiftCards, - out int redeemedRewardPoints, out decimal redeemedRewardPointsAmount, + out RewardPoints redeemedRewardPoints, out TaxSummary taxSummary, - bool includingTax, + out decimal earnedRewardPointsBaseAmount, + bool? includingTax = null, bool? useRewardPoints = null, bool usePaymentMethodAdditionalFee = true) { - redeemedRewardPoints = 0; - redeemedRewardPointsAmount = decimal.Zero; + redeemedRewardPoints = new RewardPoints(_rewardPointService); - #region order totals and tax //order totals and tax - decimal shoppingCartTax = GetTaxTotal(cart, includingTax, out taxSummary, out appliedDiscounts, out subTotalAppliedDiscounts, out shippingAppliedDiscounts, usePaymentMethodAdditionalFee); - decimal orderSubTotalDiscountAmount = taxSummary.TotalSubTotalDiscAmount; - decimal subTotalWithoutDiscountBase = taxSummary.TotalSubTotalAmount; - decimal subTotalWithDiscountBase = taxSummary.TotalAmountIncludingVAT; - - //shipping - decimal? shoppingCartShipping = taxSummary.TotalShippingAmount; - - //payment method additional fee - decimal paymentMethodAdditionalFee = taxSummary.TotalPaymentFeeAmount; - - //order total - decimal resultTemp = taxSummary.TotalAmountIncludingVAT; - if (resultTemp < decimal.Zero) - resultTemp = decimal.Zero; - if (_shoppingCartSettings.RoundPricesDuringCalculation) - resultTemp = RoundingHelper.RoundAmount(resultTemp); - - //Order total discount - //it has reduced vat + decimal shoppingCartTax = GetTaxTotal(cart, includingTax ?? _workContext.TaxDisplayType == TaxDisplayType.IncludingTax, out taxSummary, out appliedDiscounts, out subTotalAppliedDiscounts, out shippingAppliedDiscounts, out appliedGiftCards, out redeemedRewardPoints, out earnedRewardPointsBaseAmount, usePaymentMethodAdditionalFee, useRewardPoints); discountAmount = taxSummary.TotalInvDiscAmount; - #endregion - - #region Applied gift cards - var customer = cart.GetCustomer(); - //let's apply gift cards now (gift cards that can be used) - appliedGiftCards = new List(); - if (!cart.IsRecurring()) + //No shipping method found, hence total can't be calculated + if (cart.RequiresShipping() && !taxSummary.TotalShippingAmount.HasValue) { - //we don't apply gift cards for recurring products - var giftCards = _giftCardService.GetActiveGiftCardsAppliedByCustomer(customer); - if (giftCards != null) - foreach (var gc in giftCards) - if (resultTemp > decimal.Zero) - { - decimal remainingAmount = gc.GetGiftCardRemainingAmount(); - decimal amountCanBeUsed = resultTemp > remainingAmount ? - remainingAmount : - resultTemp; - - //reduce subtotal - resultTemp -= amountCanBeUsed; - - var appliedGiftCard = new AppliedGiftCard(); - appliedGiftCard.GiftCard = gc; - appliedGiftCard.AmountCanBeUsed = amountCanBeUsed; - appliedGiftCards.Add(appliedGiftCard); - } - } - - #endregion - - if (resultTemp < decimal.Zero) - resultTemp = decimal.Zero; - if (_shoppingCartSettings.RoundPricesDuringCalculation) - resultTemp = RoundingHelper.RoundAmount(resultTemp); - - if (!shoppingCartShipping.HasValue) - { - //we have errors + //we have no total -> display total on checkout return null; } - decimal orderTotal = resultTemp; - - #region Reward points + decimal orderTotal = taxSummary.TotalPaymentAmount; - if (_rewardPointsSettings.Enabled) - { - if (!useRewardPoints.HasValue) - useRewardPoints = customer.GetAttribute(SystemCustomerAttributeNames.UseRewardPointsDuringCheckout, _genericAttributeService, _storeContext.CurrentStore.Id); - if (useRewardPoints.Value) - { - int rewardPointsBalance = _rewardPointService.GetRewardPointsBalance(customer.Id, _storeContext.CurrentStore.Id); - if (CheckMinimumRewardPointsToUseRequirement(rewardPointsBalance)) - { - decimal rewardPointsBalanceAmount = ConvertRewardPointsToAmount(rewardPointsBalance); - if (orderTotal > decimal.Zero) - { - if (orderTotal > rewardPointsBalanceAmount) - { - redeemedRewardPoints = rewardPointsBalance; - redeemedRewardPointsAmount = rewardPointsBalanceAmount; - } - else - { - redeemedRewardPointsAmount = orderTotal; - redeemedRewardPoints = ConvertAmountToRewardPoints(redeemedRewardPointsAmount); - } - } - } - } - } - - #endregion + //due to rounding of reward points and different reward points base amount corrections, ordertotal and earnedRewardPointsBaseAmount can be negative + if (orderTotal < decimal.Zero) + orderTotal = decimal.Zero; - orderTotal = orderTotal - redeemedRewardPointsAmount; if (_shoppingCartSettings.RoundPricesDuringCalculation) + { orderTotal = RoundingHelper.RoundAmount(orderTotal); + earnedRewardPointsBaseAmount = RoundingHelper.RoundAmount(earnedRewardPointsBaseAmount); + } return orderTotal; } - - - - /// - /// Converts existing reward points to amount - /// - /// Reward points - /// Converted value - public virtual decimal ConvertRewardPointsToAmount(int rewardPoints) - { - if (rewardPoints <= 0) - return decimal.Zero; - - var result = rewardPoints * _rewardPointsSettings.ExchangeRate; - if (_shoppingCartSettings.RoundPricesDuringCalculation) - result = RoundingHelper.RoundAmount(result); - return result; - } - - /// - /// Converts an amount to reward points - /// - /// Amount - /// Converted value - public virtual int ConvertAmountToRewardPoints(decimal amount) - { - int result = 0; - if (amount <= 0) - return 0; - - if (_rewardPointsSettings.ExchangeRate > 0) - result = (int)Math.Ceiling(amount / _rewardPointsSettings.ExchangeRate); - return result; - } - - /// - /// Gets a value indicating whether a customer has minimum amount of reward points to use (if enabled) - /// - /// Reward points to check - /// true - reward points could use; false - cannot be used. - public virtual bool CheckMinimumRewardPointsToUseRequirement(int rewardPoints) - { - if (_rewardPointsSettings.MinimumRewardPointsToUse <= 0) - return true; - - return rewardPoints >= _rewardPointsSettings.MinimumRewardPointsToUse; - } - - /// - /// Calculate how order total (maximum amount) for which reward points could be earned/reduced - /// - /// Order shipping (including tax) - /// Order total - /// Applicable order total - public virtual decimal CalculateApplicableOrderTotalForRewardPoints(decimal orderShippingInclTax, decimal orderTotal) - { - //do you give reward points for order total? or do you exclude shipping? - //since shipping costs vary some of store owners don't give reward points based on shipping total - //you can put your custom logic here - return orderTotal - orderShippingInclTax; - } - /// - /// Calculate how much reward points will be earned/reduced based on certain amount spent - /// - /// Customer - /// Amount (in primary store currency) - /// Number of reward points - public virtual int CalculateRewardPoints(Customer customer, decimal amount) - { - if (!_rewardPointsSettings.Enabled) - return 0; - - if (_rewardPointsSettings.PointsForPurchases_Amount <= decimal.Zero) - return 0; - - //ensure that reward points are applied only to registered users - if (customer == null || customer.IsGuest()) - return 0; - - var points = (int)Math.Truncate(amount / _rewardPointsSettings.PointsForPurchases_Amount * _rewardPointsSettings.PointsForPurchases_Points); - return points; - } - #endregion } } diff --git a/src/Libraries/Nop.Services/Orders/RewardPointService.cs b/src/Libraries/Nop.Services/Orders/RewardPointService.cs index babc354407d..b42ec9d0ed5 100644 --- a/src/Libraries/Nop.Services/Orders/RewardPointService.cs +++ b/src/Libraries/Nop.Services/Orders/RewardPointService.cs @@ -5,6 +5,7 @@ using Nop.Core.Domain.Customers; using Nop.Core.Domain.Orders; using Nop.Services.Events; +using Nop.Services.Catalog; namespace Nop.Services.Orders { @@ -19,6 +20,7 @@ public partial class RewardPointService : IRewardPointService private readonly RewardPointsSettings _rewardPointsSettings; private readonly IStoreContext _storeContext; private readonly IEventPublisher _eventPublisher; + private readonly ShoppingCartSettings _shoppingCartSettings; #endregion @@ -31,15 +33,18 @@ public partial class RewardPointService : IRewardPointService /// Reward points settings /// Store context /// Event published + /// Shopping cart settings public RewardPointService(IRepository rphRepository, RewardPointsSettings rewardPointsSettings, IStoreContext storeContext, - IEventPublisher eventPublisher) + IEventPublisher eventPublisher, + ShoppingCartSettings shoppingCartSettings) { this._rphRepository = rphRepository; this._rewardPointsSettings = rewardPointsSettings; this._storeContext = storeContext; this._eventPublisher = eventPublisher; + this._shoppingCartSettings = shoppingCartSettings; } #endregion @@ -56,7 +61,8 @@ protected void UpdateRewardPointsBalance(IQueryable query) query = query.OrderBy(rph => rph.CreatedOnUtc).ThenBy(rph => rph.Id); //get has not yet activated points, but it's time to do it - var notActivatedRph = query.Where(rph => !rph.PointsBalance.HasValue && rph.CreatedOnUtc < DateTime.UtcNow).ToList(); + //no need to search for !rph.PointsBalancePurchased.HasValue additionally to !rph.PointsBalance as both values will be set contemporarily + var notActivatedRph = query.Where(rph => (!rph.PointsBalance.HasValue) && rph.CreatedOnUtc < DateTime.UtcNow).ToList(); //nothing to update if (!notActivatedRph.Any()) @@ -64,17 +70,107 @@ protected void UpdateRewardPointsBalance(IQueryable query) //get current points balance, LINQ to entities does not support Last method, thus order by desc and use First one var lastActive = query.OrderByDescending(rph => rph.CreatedOnUtc).ThenByDescending(rph => rph.Id).FirstOrDefault(rph => rph.PointsBalance.HasValue); + //no need to get lastActivePurchased + //var lastActivePurchased = query.OrderByDescending(rph => rph.CreatedOnUtc).ThenByDescending(rph => rph.Id).FirstOrDefault(rph => rph.PointsBalancePurchased.HasValue); var currentPointsBalance = lastActive != null ? lastActive.PointsBalance : 0; + var currentPointsBalancePurchased = lastActive != null ? lastActive.PointsBalancePurchased : 0; //update appropriate records foreach (var rph in notActivatedRph) { rph.PointsBalance = currentPointsBalance + rph.Points; + rph.PointsBalancePurchased = currentPointsBalancePurchased + rph.PointsPurchased; UpdateRewardPointsHistoryEntry(rph); currentPointsBalance = rph.PointsBalance; + currentPointsBalancePurchased = rph.PointsBalancePurchased; } } + /// + /// Converts existing reward points to amount + /// + /// Reward points + /// Converted value + public virtual decimal ConvertRewardPointsToAmount(int rewardPoints) + { + //convert all + //if (rewardPoints <= 0) + // return decimal.Zero; + + var result = rewardPoints * _rewardPointsSettings.ExchangeRate; + if (_shoppingCartSettings.RoundPricesDuringCalculation) + result = RoundingHelper.RoundAmount(result); + return result; + } + + /// + /// Converts an amount to reward points + /// + /// Amount + /// Converted value + public virtual int ConvertAmountToRewardPoints(decimal amount, decimal? overridenExchangeRate = null) + { + int result = 0; + //convert all + //if (amount <= 0) + // return 0; + var exchangeRate = overridenExchangeRate.HasValue ? overridenExchangeRate.Value : _rewardPointsSettings.ExchangeRate; + + if (exchangeRate > 0) + result = (int)Math.Ceiling(amount / exchangeRate); + return result; + } + + /// + /// Gets a value indicating whether a customer has minimum amount of reward points to use (if enabled) + /// + /// Reward points to check + /// true - reward points could use; false - cannot be used. + public virtual bool CheckMinimumRewardPointsToUseRequirement(int rewardPoints) + { + if (_rewardPointsSettings.MinimumRewardPointsToUse <= 0) + return true; + + return rewardPoints >= _rewardPointsSettings.MinimumRewardPointsToUse; + } + + /// + /// Calculate how much reward points will be earned/reduced based on certain amount spent + /// + /// Customer + /// Base amount (in primary store currency) for points calculation + /// Number of reward points + public virtual int CalculateRewardPoints(Customer customer, decimal amount) + { + if (!_rewardPointsSettings.Enabled) + return 0; + + if (_rewardPointsSettings.PointsForPurchases_Amount <= decimal.Zero) + return 0; + + //ensure that reward points are applied only to registered users + if (customer == null || customer.IsGuest()) + return 0; + + var points = (int)Math.Truncate(amount / _rewardPointsSettings.PointsForPurchases_Amount * _rewardPointsSettings.PointsForPurchases_Points); + return points; + } + + /// + /// Calculate base amount for reward points calculation + /// + /// Base amount (in primary store currency) for points calculation + /// Amount of used purchased reward points + /// base amount + public virtual decimal GetRewardPointsBaseAmount(decimal amount, decimal purchasedPointsAmount) + { + if (_rewardPointsSettings.EarnRewardPointsOnlyWhenUsingPurchasedRewardPoints) + { + return Math.Min(amount, purchasedPointsAmount); + } + else + return amount; + } #endregion #region Methods @@ -119,15 +215,16 @@ public virtual IPagedList GetRewardPointsHistory(int custom /// Add reward points history record /// /// Customer - /// Number of points to add + /// Reward points to add /// Store identifier /// Message /// The order for which points were redeemed (spent) as a payment - /// Used amount + /// Flag that indicates if passed amounts in points are used amounts /// Date and time of activating reward points; pass null to immediately activating + /// OrderItem used to purchase reward points /// Reward points history entry identifier - public virtual int AddRewardPointsHistoryEntry(Customer customer, int points, int storeId, string message = "", - Order usedWithOrder = null, decimal usedAmount = 0M, DateTime? activatingDate = null) + public virtual int AddRewardPointsHistoryEntry(Customer customer, RewardPoints rewardPoints, int storeId, string message = "", + Order usedWithOrder = null, bool hasUsedAmount = false, DateTime? activatingDate = null, OrderItem orderItem = null) { if (customer == null) throw new ArgumentNullException("customer"); @@ -135,16 +232,26 @@ public virtual int AddRewardPointsHistoryEntry(Customer customer, int points, in if (storeId <= 0) throw new ArgumentException("Store ID should be valid"); + RewardPoints rewardPointsBalance = new RewardPoints(this); + + if (!activatingDate.HasValue) + rewardPointsBalance = GetRewardPointsBalance(customer.Id, storeId); + var rph = new RewardPointsHistory { Customer = customer, StoreId = storeId, UsedWithOrder = usedWithOrder, - Points = points, - PointsBalance = activatingDate.HasValue ? null : (int?)(GetRewardPointsBalance(customer.Id, storeId) + points), - UsedAmount = usedAmount, + Points = rewardPoints.Points, + PointsPurchased = rewardPoints.PointsPurchased, + PointsBalance = activatingDate.HasValue ? null : (int?)(rewardPointsBalance.Points + rewardPoints.Points), + PointsBalancePurchased = activatingDate.HasValue ? null : (int?)(rewardPointsBalance.PointsPurchased + rewardPoints.PointsPurchased), + //revert sign of used amounts as rph entry has opposite sign + UsedAmount = hasUsedAmount ? -rewardPoints.Amount : 0M, + UsedAmountPurchased = hasUsedAmount ? -rewardPoints.AmountPurchased : 0M, Message = message, - CreatedOnUtc = activatingDate ?? DateTime.UtcNow + CreatedOnUtc = activatingDate ?? DateTime.UtcNow, + PurchasedWithOrderItem = orderItem }; _rphRepository.Insert(rph); @@ -160,9 +267,11 @@ public virtual int AddRewardPointsHistoryEntry(Customer customer, int points, in /// /// Customer identifier /// Store identifier; pass - /// Balance - public virtual int GetRewardPointsBalance(int customerId, int storeId) + /// RewardPoints class + public virtual RewardPoints GetRewardPointsBalance(int customerId, int storeId) { + var result = new RewardPoints(this); + var query = _rphRepository.Table; if (customerId > 0) query = query.Where(rph => rph.CustomerId == customerId); @@ -178,7 +287,11 @@ public virtual int GetRewardPointsBalance(int customerId, int storeId) query = query.OrderByDescending(rph => rph.CreatedOnUtc).ThenByDescending(rph => rph.Id); var lastRph = query.FirstOrDefault(); - return lastRph != null && lastRph.PointsBalance.HasValue ? lastRph.PointsBalance.Value : 0; + var totBalance = lastRph != null && lastRph.PointsBalance.HasValue ? lastRph.PointsBalance.Value : 0; + var totBalancePurchased = lastRph != null && lastRph.PointsBalancePurchased.HasValue ? lastRph.PointsBalancePurchased.Value : 0; + result.Points = totBalance; + result.PointsPurchased = totBalancePurchased; + return result; } @@ -227,4 +340,4 @@ public virtual void UpdateRewardPointsHistoryEntry(RewardPointsHistory rewardPoi #endregion } -} +} \ No newline at end of file diff --git a/src/Libraries/Nop.Services/Orders/RewardPoints.cs b/src/Libraries/Nop.Services/Orders/RewardPoints.cs new file mode 100644 index 00000000000..baf5004406a --- /dev/null +++ b/src/Libraries/Nop.Services/Orders/RewardPoints.cs @@ -0,0 +1,83 @@ + + +using System; + +namespace Nop.Services.Orders +{ + /// + /// Reward points + /// + public class RewardPoints + { + #region Fields + private readonly IRewardPointService _rewardPointService; + #endregion + #region Ctor + /// + /// Ctor + /// + /// Reward point service + public RewardPoints(IRewardPointService rewardPointService) + { + this._rewardPointService = rewardPointService; + } + #endregion + /// + /// Points + /// + public int Points { get; set; } = 0; + /// + /// Points Amount. Same sign as points. + /// + public decimal Amount + { + get { return _rewardPointService.ConvertRewardPointsToAmount(Points); } + } + /// + /// Purchased Points + /// + public int PointsPurchased { get; set; } = 0; + /// + /// Purchased Points Amount. Same sign as points purchased. + /// + public decimal AmountPurchased + { + get { return _rewardPointService.ConvertRewardPointsToAmount(PointsPurchased); } + } + + /// + /// Total Points + /// + public int PointsTotal + { + get { return Points + PointsPurchased; } + } + /// + /// Total Points Amount + /// + public decimal AmountTotal + { + get { return Amount + AmountPurchased; } + } + /// + /// Total Points + /// + public int PointsTotalCorrectedForMinPointsToUse + { + get { return (_rewardPointService.CheckMinimumRewardPointsToUseRequirement(Math.Abs(Points)) ? Points : 0 ) + PointsPurchased; } + } + /// + /// Total Points Amount + /// + public decimal AmountTotalCorrectedForMinPointsToUse + { + get { return (_rewardPointService.CheckMinimumRewardPointsToUseRequirement(Math.Abs(Points)) ? Amount : decimal.Zero ) + AmountPurchased; } + } + + public void RevertSign() + { + Points = -Points; + PointsPurchased = -PointsPurchased; + } + } +} diff --git a/src/Libraries/Nop.Services/Orders/ShoppingCartExtensions.cs b/src/Libraries/Nop.Services/Orders/ShoppingCartExtensions.cs index eb9bb66a302..3ff04beb449 100644 --- a/src/Libraries/Nop.Services/Orders/ShoppingCartExtensions.cs +++ b/src/Libraries/Nop.Services/Orders/ShoppingCartExtensions.cs @@ -1,143 +1,159 @@ -using System.Collections.Generic; -using System.Linq; -using Nop.Core; -using Nop.Core.Domain.Catalog; -using Nop.Core.Domain.Customers; -using Nop.Core.Domain.Orders; -using Nop.Core.Infrastructure; -using Nop.Services.Localization; - -namespace Nop.Services.Orders -{ - /// - /// Represents a shopping cart - /// - public static class ShoppingCartExtensions - { - /// - /// Indicates whether the shopping cart requires shipping - /// - /// Shopping cart - /// True if the shopping cart requires shipping; otherwise, false. - public static bool RequiresShipping(this IList shoppingCart) - { - foreach (var shoppingCartItem in shoppingCart) - if (shoppingCartItem.IsShipEnabled) - return true; - return false; - } - - /// - /// Gets a number of product in the cart - /// - /// Shopping cart - /// Result - public static int GetTotalProducts(this IList shoppingCart) - { - int result = 0; - foreach (ShoppingCartItem sci in shoppingCart) - { - result += sci.Quantity; - } - return result; - } - - /// - /// Gets a value indicating whether shopping cart is recurring - /// - /// Shopping cart - /// Result - public static bool IsRecurring(this IList shoppingCart) - { - foreach (ShoppingCartItem sci in shoppingCart) - { - var product = sci.Product; - if (product != null && product.IsRecurring) - return true; - } - return false; - } - - /// - /// Get a recurring cycle information - /// - /// Shopping cart - /// Localization service - /// Cycle length - /// Cycle period - /// Total cycles - /// Error (if exists); otherwise, empty string - public static string GetRecurringCycleInfo(this IList shoppingCart, - ILocalizationService localizationService, - out int cycleLength, out RecurringProductCyclePeriod cyclePeriod, out int totalCycles) - { - cycleLength = 0; - cyclePeriod = 0; - totalCycles = 0; - - int? _cycleLength = null; - RecurringProductCyclePeriod? _cyclePeriod = null; - int? _totalCycles = null; - - foreach (var sci in shoppingCart) - { - var product= sci.Product; - if (product == null) - { - throw new NopException(string.Format("Product (Id={0}) cannot be loaded", sci.ProductId)); - } - - if (product.IsRecurring) - { - string conflictError = localizationService.GetResource("ShoppingCart.ConflictingShipmentSchedules"); - - //cycle length - if (_cycleLength.HasValue && _cycleLength.Value != product.RecurringCycleLength) - return conflictError; - _cycleLength = product.RecurringCycleLength; - - //cycle period - if (_cyclePeriod.HasValue && _cyclePeriod.Value != product.RecurringCyclePeriod) - return conflictError; - _cyclePeriod = product.RecurringCyclePeriod; - - //total cycles - if (_totalCycles.HasValue && _totalCycles.Value != product.RecurringTotalCycles) - return conflictError; - _totalCycles = product.RecurringTotalCycles; - } - } - - if (_cycleLength.HasValue && _cyclePeriod.HasValue && _totalCycles.HasValue) - { - cycleLength = _cycleLength.Value; - cyclePeriod = _cyclePeriod.Value; - totalCycles = _totalCycles.Value; - } - - return ""; - } - - /// - /// Get customer of shopping cart - /// - /// Shopping cart - /// Customer of shopping cart - public static Customer GetCustomer(this IList shoppingCart) - { - if (!shoppingCart.Any()) - return null; - - return shoppingCart[0].Customer; - } - - public static IEnumerable LimitPerStore(this IEnumerable cart, int storeId) - { - var shoppingCartSettings = EngineContext.Current.Resolve(); - if (shoppingCartSettings.CartsSharedBetweenStores) - return cart; - - return cart.Where(x => x.StoreId == storeId); - } - } -} +using System.Collections.Generic; +using System.Linq; +using Nop.Core; +using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Customers; +using Nop.Core.Domain.Orders; +using Nop.Core.Infrastructure; +using Nop.Services.Localization; + +namespace Nop.Services.Orders +{ + /// + /// Represents a shopping cart + /// + public static class ShoppingCartExtensions + { + /// + /// Indicates whether the shopping cart requires shipping + /// + /// Shopping cart + /// True if the shopping cart requires shipping; otherwise, false. + public static bool RequiresShipping(this IList shoppingCart) + { + foreach (var shoppingCartItem in shoppingCart) + if (shoppingCartItem.IsShipEnabled) + return true; + return false; + } + + /// + /// Gets a number of product in the cart + /// + /// Shopping cart + /// Result + public static int GetTotalProducts(this IList shoppingCart) + { + int result = 0; + foreach (ShoppingCartItem sci in shoppingCart) + { + result += sci.Quantity; + } + return result; + } + + /// + /// Gets a value indicating whether shopping cart is recurring + /// + /// Shopping cart + /// Result + public static bool IsRecurring(this IList shoppingCart) + { + foreach (ShoppingCartItem sci in shoppingCart) + { + var product = sci.Product; + if (product != null && product.IsRecurring) + return true; + } + return false; + } + + /// + /// Get a recurring cycle information + /// + /// Shopping cart + /// Localization service + /// Cycle length + /// Cycle period + /// Total cycles + /// Error (if exists); otherwise, empty string + public static string GetRecurringCycleInfo(this IList shoppingCart, + ILocalizationService localizationService, + out int cycleLength, out RecurringProductCyclePeriod cyclePeriod, out int totalCycles) + { + cycleLength = 0; + cyclePeriod = 0; + totalCycles = 0; + + int? _cycleLength = null; + RecurringProductCyclePeriod? _cyclePeriod = null; + int? _totalCycles = null; + + foreach (var sci in shoppingCart) + { + var product= sci.Product; + if (product == null) + { + throw new NopException(string.Format("Product (Id={0}) cannot be loaded", sci.ProductId)); + } + + if (product.IsRecurring) + { + string conflictError = localizationService.GetResource("ShoppingCart.ConflictingShipmentSchedules"); + + //cycle length + if (_cycleLength.HasValue && _cycleLength.Value != product.RecurringCycleLength) + return conflictError; + _cycleLength = product.RecurringCycleLength; + + //cycle period + if (_cyclePeriod.HasValue && _cyclePeriod.Value != product.RecurringCyclePeriod) + return conflictError; + _cyclePeriod = product.RecurringCyclePeriod; + + //total cycles + if (_totalCycles.HasValue && _totalCycles.Value != product.RecurringTotalCycles) + return conflictError; + _totalCycles = product.RecurringTotalCycles; + } + } + + if (_cycleLength.HasValue && _cyclePeriod.HasValue && _totalCycles.HasValue) + { + cycleLength = _cycleLength.Value; + cyclePeriod = _cyclePeriod.Value; + totalCycles = _totalCycles.Value; + } + + return ""; + } + + /// + /// Get customer of shopping cart + /// + /// Shopping cart + /// Customer of shopping cart + public static Customer GetCustomer(this IList shoppingCart) + { + if (!shoppingCart.Any()) + return null; + + return shoppingCart[0].Customer; + } + + public static IEnumerable LimitPerStore(this IEnumerable cart, int storeId) + { + var shoppingCartSettings = EngineContext.Current.Resolve(); + if (shoppingCartSettings.CartsSharedBetweenStores) + return cart; + + return cart.Where(x => x.StoreId == storeId); + } + + /// + /// Indicates whether the shopping cart has at least one purchased reward points product + /// + /// Shopping cart + /// True if the shopping cart conatins one reward points product; otherwise, false. + public static bool HasRewardPointsProduct(this IList shoppingCart) + { + foreach (ShoppingCartItem sci in shoppingCart) + { + var product = sci.Product; + if (product != null && product.IsRewardPoints) + return true; + } + return false; + } + } +} diff --git a/src/Libraries/Nop.Services/Orders/UpdateOrderParameters.cs b/src/Libraries/Nop.Services/Orders/UpdateOrderParameters.cs index 897b1266ad6..287c1f8c70a 100644 --- a/src/Libraries/Nop.Services/Orders/UpdateOrderParameters.cs +++ b/src/Libraries/Nop.Services/Orders/UpdateOrderParameters.cs @@ -76,6 +76,6 @@ public UpdateOrderParameters() /// public PickupPoint PickupPoint { get; set; } - public decimal VatRate { get; set; } + public decimal TaxRate { get; set; } } } diff --git a/src/Libraries/Nop.Services/Tax/ITaxService.cs b/src/Libraries/Nop.Services/Tax/ITaxService.cs index 53a86303cce..8834a811ec1 100644 --- a/src/Libraries/Nop.Services/Tax/ITaxService.cs +++ b/src/Libraries/Nop.Services/Tax/ITaxService.cs @@ -1,266 +1,287 @@ -using System; -using System.Collections.Generic; -using Nop.Core.Domain.Catalog; -using Nop.Core.Domain.Common; -using Nop.Core.Domain.Customers; -using Nop.Core.Domain.Orders; -using Nop.Core.Domain.Tax; - -namespace Nop.Services.Tax -{ - /// - /// Tax service - /// - public partial interface ITaxService - { - #region Tax providers - - /// - /// Load active tax provider - /// - /// Load records allowed only to a specified customer; pass null to ignore ACL permissions - /// Active tax provider - ITaxProvider LoadActiveTaxProvider(Customer customer = null); - - /// - /// Load tax provider by system name - /// - /// System name - /// Found tax provider - ITaxProvider LoadTaxProviderBySystemName(string systemName); - - /// - /// Load all tax providers - /// - /// Load records allowed only to a specified customer; pass null to ignore ACL permissions - /// Tax providers - IList LoadAllTaxProviders(Customer customer = null); - - #endregion - - #region Product price - - /// - /// Gets price - /// - /// Product - /// Price - /// Tax rate - /// Price - decimal GetProductPrice(Product product, decimal price, - out decimal taxRate); - - /// - /// Gets price - /// - /// Product - /// Price - /// Customer - /// Tax rate - /// Price - decimal GetProductPrice(Product product, decimal price, - Customer customer, out decimal taxRate); - - /// - /// Gets price - /// - /// Product - /// Price - /// A value indicating whether calculated price should include tax - /// Customer - /// Tax rate - /// Price - decimal GetProductPrice(Product product, decimal price, - bool includingTax, Customer customer, out decimal taxRate); - - /// - /// Gets price - /// - /// Product - /// Tax category identifier - /// Price - /// A value indicating whether calculated price should include tax - /// Customer - /// A value indicating whether price already includes tax - /// Tax rate - /// Price - decimal GetProductPrice(Product product, int taxCategoryId, decimal price, - bool includingTax, Customer customer, - bool priceIncludesTax, out decimal taxRate); - - #endregion - - #region Shipping price - - /// - /// Gets shipping price - /// - /// Price - /// Customer - /// Price - decimal GetShippingPrice(decimal price, Customer customer); - - /// - /// Gets shipping price - /// - /// Price - /// A value indicating whether calculated price should include tax - /// Customer - /// Price - decimal GetShippingPrice(decimal price, bool includingTax, Customer customer); - - /// - /// Gets shipping price - /// - /// Price - /// A value indicating whether calculated price should include tax - /// Customer - /// Tax rate - /// Price - decimal GetShippingPrice(decimal price, bool includingTax, Customer customer, out decimal taxRate); - - #endregion - - #region Payment additional fee - - /// - /// Gets payment method additional handling fee - /// - /// Price - /// Customer - /// Price - decimal GetPaymentMethodAdditionalFee(decimal price, Customer customer); - - /// - /// Gets payment method additional handling fee - /// - /// Price - /// A value indicating whether calculated price should include tax - /// Customer - /// Price - decimal GetPaymentMethodAdditionalFee(decimal price, bool includingTax, Customer customer); - - /// - /// Gets payment method additional handling fee - /// - /// Price - /// A value indicating whether calculated price should include tax - /// Customer - /// Tax rate - /// Price - decimal GetPaymentMethodAdditionalFee(decimal price, bool includingTax, Customer customer, out decimal taxRate); - - #endregion - - #region Checkout attribute price - - /// - /// Gets checkout attribute value price - /// - /// Checkout attribute value - /// Price - decimal GetCheckoutAttributePrice(CheckoutAttributeValue cav); - - /// - /// Gets checkout attribute value price - /// - /// Checkout attribute value - /// Customer - /// Price - decimal GetCheckoutAttributePrice(CheckoutAttributeValue cav, Customer customer); - - /// - /// Gets checkout attribute value price - /// - /// Checkout attribute value - /// A value indicating whether calculated price should include tax - /// Customer - /// Price - decimal GetCheckoutAttributePrice(CheckoutAttributeValue cav, - bool includingTax, Customer customer); - - /// - /// Gets checkout attribute value price - /// - /// Checkout attribute value - /// A value indicating whether calculated price should include tax - /// Customer - /// Tax rate - /// Price - decimal GetCheckoutAttributePrice(CheckoutAttributeValue cav, - bool includingTax, Customer customer, out decimal taxRate); - - #endregion - - #region VAT - - /// - /// Gets VAT Number status - /// - /// Two letter ISO code of a country and VAT number (e.g. GB 111 1111 111) - /// VAT Number status - VatNumberStatus GetVatNumberStatus(string fullVatNumber); - - /// - /// Gets VAT Number status - /// - /// Two letter ISO code of a country and VAT number (e.g. GB 111 1111 111) - /// Name (if received) - /// Address (if received) - /// VAT Number status - VatNumberStatus GetVatNumberStatus(string fullVatNumber, - out string name, out string address); - /// - /// Gets VAT Number status - /// - /// Two letter ISO code of a country - /// VAT number - /// VAT Number status - VatNumberStatus GetVatNumberStatus(string twoLetterIsoCode, string vatNumber); - - /// - /// Gets VAT Number status - /// - /// Two letter ISO code of a country - /// VAT number - /// Name (if received) - /// Address (if received) - /// VAT Number status - VatNumberStatus GetVatNumberStatus(string twoLetterIsoCode, string vatNumber, - out string name, out string address); - - /// - /// Performs a basic check of a VAT number for validity - /// - /// Two letter ISO code of a country - /// VAT number - /// Company name - /// Address - /// Exception - /// VAT number status - VatNumberStatus DoVatCheck(string twoLetterIsoCode, string vatNumber, - out string name, out string address, out Exception exception); - - #endregion - - #region Exempts - - /// - /// Gets a value indicating whether a product is tax exempt - /// - /// Product - /// Customer - /// A value indicating whether a product is tax exempt - bool IsTaxExempt(Product product, Customer customer); - - /// - /// Gets a value indicating whether EU VAT exempt (the European Union Value Added Tax) - /// - /// Address - /// Customer - /// Result - bool IsVatExempt(Address address, Customer customer); - - #endregion - } -} +using System; +using System.Collections.Generic; +using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Common; +using Nop.Core.Domain.Customers; +using Nop.Core.Domain.Orders; +using Nop.Core.Domain.Tax; + +namespace Nop.Services.Tax +{ + /// + /// Tax service + /// + public partial interface ITaxService + { + #region Tax providers + + /// + /// Load active tax provider + /// + /// Load records allowed only to a specified customer; pass null to ignore ACL permissions + /// Active tax provider + ITaxProvider LoadActiveTaxProvider(Customer customer = null); + + /// + /// Load tax provider by system name + /// + /// System name + /// Found tax provider + ITaxProvider LoadTaxProviderBySystemName(string systemName); + + /// + /// Load all tax providers + /// + /// Load records allowed only to a specified customer; pass null to ignore ACL permissions + /// Tax providers + IList LoadAllTaxProviders(Customer customer = null); + + #endregion + + #region Product price + + /// + /// Gets price + /// + /// Product + /// Price + /// Tax rate + /// Price + decimal GetProductPrice(Product product, decimal price, + out decimal taxRate); + + /// + /// Gets price using tax attributes + /// + /// Product + /// Price + /// Tax rate + /// Product atrributes (XML format) + /// Optional value indicating whether calculated price should include tax + /// Price + decimal GetProductPrice(Product product, decimal price, + out decimal taxRate, ref string attributesXml, bool? includingTax = null); + + /// + /// Gets price + /// + /// Product + /// Price + /// Customer + /// Tax rate + /// Price + decimal GetProductPrice(Product product, decimal price, + Customer customer, out decimal taxRate); + + /// + /// Gets price + /// + /// Product + /// Price + /// A value indicating whether calculated price should include tax + /// Customer + /// Tax rate + /// Price + decimal GetProductPrice(Product product, decimal price, + bool includingTax, Customer customer, out decimal taxRate); + + /// + /// Gets price + /// + /// Product + /// Tax category identifier + /// Price + /// A value indicating whether calculated price should include tax + /// Customer + /// A value indicating whether price already includes tax + /// Tax rate + /// Product atrributes (XML format) + /// Price + decimal GetProductPrice(Product product, int taxCategoryId, decimal price, + bool includingTax, Customer customer, + bool priceIncludesTax, out decimal taxRate, ref string attributesXml); + + #endregion + + #region Shipping price + + /// + /// Gets shipping price + /// + /// Price + /// Customer + /// Price + decimal GetShippingPrice(decimal price, Customer customer); + + /// + /// Gets shipping price + /// + /// Price + /// A value indicating whether calculated price should include tax + /// Customer + /// Price + decimal GetShippingPrice(decimal price, bool includingTax, Customer customer); + + /// + /// Gets shipping price + /// + /// Price + /// A value indicating whether calculated price should include tax + /// Customer + /// Tax rate + /// Price + decimal GetShippingPrice(decimal price, bool includingTax, Customer customer, out decimal taxRate); + + #endregion + + #region Payment additional fee + + /// + /// Gets payment method additional handling fee + /// + /// Price + /// Customer + /// Price + decimal GetPaymentMethodAdditionalFee(decimal price, Customer customer); + + /// + /// Gets payment method additional handling fee + /// + /// Price + /// A value indicating whether calculated price should include tax + /// Customer + /// Price + decimal GetPaymentMethodAdditionalFee(decimal price, bool includingTax, Customer customer); + + /// + /// Gets payment method additional handling fee + /// + /// Price + /// A value indicating whether calculated price should include tax + /// Customer + /// Tax rate + /// Price + decimal GetPaymentMethodAdditionalFee(decimal price, bool includingTax, Customer customer, out decimal taxRate); + + #endregion + + #region Checkout attribute price + + /// + /// Gets checkout attribute value price + /// + /// Checkout attribute value + /// Price + decimal GetCheckoutAttributePrice(CheckoutAttributeValue cav); + + /// + /// Gets checkout attribute value price + /// + /// Checkout attribute value + /// Customer + /// Price + decimal GetCheckoutAttributePrice(CheckoutAttributeValue cav, Customer customer); + + /// + /// Gets checkout attribute value price + /// + /// Checkout attribute value + /// A value indicating whether calculated price should include tax + /// Customer + /// Price + decimal GetCheckoutAttributePrice(CheckoutAttributeValue cav, + bool includingTax, Customer customer); + + /// + /// Gets checkout attribute value price + /// + /// Checkout attribute value + /// A value indicating whether calculated price should include tax + /// Customer + /// Tax rate + /// Price + decimal GetCheckoutAttributePrice(CheckoutAttributeValue cav, + bool includingTax, Customer customer, out decimal taxRate); + + #endregion + + #region VAT + + /// + /// Gets VAT Number status + /// + /// Two letter ISO code of a country and VAT number (e.g. GB 111 1111 111) + /// VAT Number status + VatNumberStatus GetVatNumberStatus(string fullVatNumber); + + /// + /// Gets VAT Number status + /// + /// Two letter ISO code of a country and VAT number (e.g. GB 111 1111 111) + /// Name (if received) + /// Address (if received) + /// VAT Number status + VatNumberStatus GetVatNumberStatus(string fullVatNumber, + out string name, out string address); + /// + /// Gets VAT Number status + /// + /// Two letter ISO code of a country + /// VAT number + /// VAT Number status + VatNumberStatus GetVatNumberStatus(string twoLetterIsoCode, string vatNumber); + + /// + /// Gets VAT Number status + /// + /// Two letter ISO code of a country + /// VAT number + /// Name (if received) + /// Address (if received) + /// VAT Number status + VatNumberStatus GetVatNumberStatus(string twoLetterIsoCode, string vatNumber, + out string name, out string address); + + /// + /// Performs a basic check of a VAT number for validity + /// + /// Two letter ISO code of a country + /// VAT number + /// Company name + /// Address + /// Exception + /// VAT number status + VatNumberStatus DoVatCheck(string twoLetterIsoCode, string vatNumber, + out string name, out string address, out Exception exception); + + #endregion + + #region Exempts + + /// + /// Gets a value indicating whether a product is tax exempt + /// + /// Product + /// Customer + /// A value indicating whether a product is tax exempt + bool IsTaxExempt(Product product, Customer customer); + + /// + /// Gets a value indicating whether EU VAT exempt (the European Union Value Added Tax) + /// + /// Address + /// Customer + /// Result + bool IsVatExempt(Address address, Customer customer); + + #endregion + + #region utilities + /// + /// Get a value indicating whether prices do include tax + /// + /// Result + bool PricesIncludeTax(); + #endregion + } +} diff --git a/src/Libraries/Nop.Services/Tax/TaxService.cs b/src/Libraries/Nop.Services/Tax/TaxService.cs index dd1f7186159..927955e2ec4 100644 --- a/src/Libraries/Nop.Services/Tax/TaxService.cs +++ b/src/Libraries/Nop.Services/Tax/TaxService.cs @@ -1,850 +1,933 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using Nop.Core; -using Nop.Core.Domain.Catalog; -using Nop.Core.Domain.Common; -using Nop.Core.Domain.Customers; -using Nop.Core.Domain.Directory; -using Nop.Core.Domain.Orders; -using Nop.Core.Domain.Shipping; -using Nop.Core.Domain.Tax; -using Nop.Core.Plugins; -using Nop.Services.Common; -using Nop.Services.Directory; -using Nop.Services.Logging; - -namespace Nop.Services.Tax -{ - /// - /// Tax service - /// - public partial class TaxService : ITaxService - { - #region Fields - - private readonly IAddressService _addressService; - private readonly IWorkContext _workContext; - private readonly IStoreContext _storeContext; - private readonly TaxSettings _taxSettings; - private readonly IPluginFinder _pluginFinder; - private readonly IGeoLookupService _geoLookupService; - private readonly ICountryService _countryService; - private readonly IStateProvinceService _stateProvinceService; - private readonly ILogger _logger; - private readonly CustomerSettings _customerSettings; - private readonly ShippingSettings _shippingSettings; - private readonly AddressSettings _addressSettings; - - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Address service - /// Work context - /// Store context - /// Tax settings - /// Plugin finder - /// GEO lookup service - /// Country service - /// State province service - /// Logger service - /// Customer settings - /// Shipping settings - /// Address settings - public TaxService(IAddressService addressService, - IWorkContext workContext, - IStoreContext storeContext, - TaxSettings taxSettings, - IPluginFinder pluginFinder, - IGeoLookupService geoLookupService, - ICountryService countryService, - IStateProvinceService stateProvinceService, - ILogger logger, - CustomerSettings customerSettings, - ShippingSettings shippingSettings, - AddressSettings addressSettings) - { - this._addressService = addressService; - this._workContext = workContext; - this._storeContext = storeContext; - this._taxSettings = taxSettings; - this._pluginFinder = pluginFinder; - this._geoLookupService = geoLookupService; - this._countryService = countryService; - this._stateProvinceService = stateProvinceService; - this._logger = logger; - this._customerSettings = customerSettings; - this._shippingSettings = shippingSettings; - this._addressSettings = addressSettings; - } - - #endregion - - #region Utilities - - /// - /// Get a value indicating whether a customer is consumer (a person, not a company) located in Europe Union - /// - /// Customer - /// Result - protected virtual bool IsEuConsumer(Customer customer) - { - if (customer == null) - throw new ArgumentNullException("customer"); - - Country country = null; - - //get country from billing address - if (_addressSettings.CountryEnabled && customer.BillingAddress != null) - country = customer.BillingAddress.Country; - - //get country specified during registration? - if (country == null && _customerSettings.CountryEnabled) - { - var countryId = customer.GetAttribute(SystemCustomerAttributeNames.CountryId); - country = _countryService.GetCountryById(countryId); - } - - //get country by IP address - if (country == null) - { - var ipAddress = customer.LastIpAddress; - //ipAddress = _webHelper.GetCurrentIpAddress(); - var countryIsoCode = _geoLookupService.LookupCountryIsoCode(ipAddress); - country = _countryService.GetCountryByTwoLetterIsoCode(countryIsoCode); - } - - //we cannot detect country - if (country == null) - return false; - - //outside EU - if (!country.SubjectToVat) - return false; - - //company (business) or consumer? - var customerVatStatus = (VatNumberStatus)customer.GetAttribute(SystemCustomerAttributeNames.VatNumberStatusId); - if (customerVatStatus == VatNumberStatus.Valid) - return false; - - //TODO: use specified company name? (both address and registration one) - - //consumer - return true; - } - - /// - /// Create request for tax calculation - /// - /// Product - /// Tax category identifier - /// Customer - /// Price - /// Package for tax calculation - protected virtual CalculateTaxRequest CreateCalculateTaxRequest(Product product, - int taxCategoryId, Customer customer, decimal price) - { - if (customer == null) - throw new ArgumentNullException("customer"); - - var calculateTaxRequest = new CalculateTaxRequest - { - Customer = customer, - Product = product, - Price = price, - TaxCategoryId = taxCategoryId > 0 ? taxCategoryId : (product != null ? product.TaxCategoryId : 0) - }; - - var basedOn = _taxSettings.TaxBasedOn; - - //new EU VAT rules starting January 1st 2015 - //find more info at http://ec.europa.eu/taxation_customs/taxation/vat/how_vat_works/telecom/index_en.htm#new_rules - var overridenBasedOn = _taxSettings.EuVatEnabled //EU VAT enabled? - && product != null && product.IsTelecommunicationsOrBroadcastingOrElectronicServices //telecommunications, broadcasting and electronic services? - && DateTime.UtcNow > new DateTime(2015, 1, 1, 0, 0, 0, DateTimeKind.Utc) //January 1st 2015 passed? - && IsEuConsumer(customer); //Europe Union consumer? - if (overridenBasedOn) - { - //We must charge VAT in the EU country where the customer belongs (not where the business is based) - basedOn = TaxBasedOn.BillingAddress; - } - - //tax is based on pickup point address - if (!overridenBasedOn && _taxSettings.TaxBasedOnPickupPointAddress && _shippingSettings.AllowPickUpInStore) - { - var pickupPoint = customer.GetAttribute(SystemCustomerAttributeNames.SelectedPickupPoint, _storeContext.CurrentStore.Id); - if (pickupPoint != null) - { - var country = _countryService.GetCountryByTwoLetterIsoCode(pickupPoint.CountryCode); - var state = _stateProvinceService.GetStateProvinceByAbbreviation(pickupPoint.StateAbbreviation); - - calculateTaxRequest.Address = new Address - { - Address1 = pickupPoint.Address, - City = pickupPoint.City, - Country = country, - CountryId = country.Return(c => c.Id, 0), - StateProvince = state, - StateProvinceId = state.Return(sp => sp.Id, 0), - ZipPostalCode = pickupPoint.ZipPostalCode, - CreatedOnUtc = DateTime.UtcNow - }; - - return calculateTaxRequest; - } - } - - if (basedOn == TaxBasedOn.BillingAddress && customer.BillingAddress == null || - basedOn == TaxBasedOn.ShippingAddress && customer.ShippingAddress == null) - { - basedOn = TaxBasedOn.DefaultAddress; - } - - switch (basedOn) - { - case TaxBasedOn.BillingAddress: - calculateTaxRequest.Address = customer.BillingAddress; - break; - case TaxBasedOn.ShippingAddress: - calculateTaxRequest.Address = customer.ShippingAddress; - break; - case TaxBasedOn.DefaultAddress: - default: - calculateTaxRequest.Address = _addressService.GetAddressById(_taxSettings.DefaultTaxAddressId); - break; - } - - return calculateTaxRequest; - } - - /// - /// Calculated price - /// - /// Price - /// Percent - /// Increase - /// New price - protected virtual decimal CalculatePrice(decimal price, decimal percent, bool increase) - { - if (percent == decimal.Zero) - return price; - - decimal result; - if (increase) - { - result = price * (1 + percent / 100); - } - else - { - result = price / (1 + percent / 100); //MF 13.11.16: is the same but written in a complicated way: price - (price) / (100 + percent) * percent; - } - return result; - } - - /// - /// Gets tax rate - /// - /// Product - /// Tax category identifier - /// Customer - /// Price (taxable value) - /// Calculated tax rate - /// A value indicating whether a request is taxable - protected virtual void GetTaxRate(Product product, int taxCategoryId, - Customer customer, decimal price, out decimal taxRate, out bool isTaxable) - { - taxRate = decimal.Zero; - isTaxable = true; - - //active tax provider - var activeTaxProvider = LoadActiveTaxProvider(customer); - if (activeTaxProvider == null) - return; - - //tax request - var calculateTaxRequest = CreateCalculateTaxRequest(product, taxCategoryId, customer, price); - - //tax exempt - if (IsTaxExempt(product, calculateTaxRequest.Customer)) - { - isTaxable = false; - } - //make EU VAT exempt validation (the European Union Value Added Tax) - if (isTaxable && - _taxSettings.EuVatEnabled && - IsVatExempt(calculateTaxRequest.Address, calculateTaxRequest.Customer)) - { - //VAT is not chargeable - isTaxable = false; - } - - //get tax rate - var calculateTaxResult = activeTaxProvider.GetTaxRate(calculateTaxRequest); - if (calculateTaxResult.Success) - { - //ensure that tax is equal or greater than zero - if (calculateTaxResult.TaxRate < decimal.Zero) - calculateTaxResult.TaxRate = decimal.Zero; - - taxRate = calculateTaxResult.TaxRate; - } - else - if (_taxSettings.LogErrors) - { - foreach (var error in calculateTaxResult.Errors) - { - _logger.Error(string.Format("{0} - {1}", activeTaxProvider.PluginDescriptor.FriendlyName, error), null, customer); - } - } - } - - #endregion - - #region Methods - - #region Tax providers - - /// - /// Load active tax provider - /// - /// Load records allowed only to a specified customer; pass null to ignore ACL permissions - /// Active tax provider - public virtual ITaxProvider LoadActiveTaxProvider(Customer customer = null) - { - var taxProvider = LoadTaxProviderBySystemName(_taxSettings.ActiveTaxProviderSystemName); - if (taxProvider == null) - taxProvider = LoadAllTaxProviders(customer).FirstOrDefault(); - return taxProvider; - } - - /// - /// Load tax provider by system name - /// - /// System name - /// Found tax provider - public virtual ITaxProvider LoadTaxProviderBySystemName(string systemName) - { - var descriptor = _pluginFinder.GetPluginDescriptorBySystemName(systemName); - if (descriptor != null) - return descriptor.Instance(); - - return null; - } - - /// - /// Load all tax providers - /// - /// Load records allowed only to a specified customer; pass null to ignore ACL permissions - /// Tax providers - public virtual IList LoadAllTaxProviders(Customer customer = null) - { - return _pluginFinder.GetPlugins(customer: customer).ToList(); - } - - #endregion - - #region Product price - - /// - /// Gets price - /// - /// Product - /// Price - /// Tax rate - /// Price - public virtual decimal GetProductPrice(Product product, decimal price, - out decimal taxRate) - { - var customer = _workContext.CurrentCustomer; - return GetProductPrice(product, price, customer, out taxRate); - } - - /// - /// Gets price - /// - /// Product - /// Price - /// Customer - /// Tax rate - /// Price - public virtual decimal GetProductPrice(Product product, decimal price, - Customer customer, out decimal taxRate) - { - bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; - return GetProductPrice(product, price, includingTax, customer, out taxRate); - } - - /// - /// Gets price - /// - /// Product - /// Price - /// A value indicating whether calculated price should include tax - /// Customer - /// Tax rate - /// Price - public virtual decimal GetProductPrice(Product product, decimal price, - bool includingTax, Customer customer, out decimal taxRate) - { - bool priceIncludesTax = _taxSettings.PricesIncludeTax; - int taxCategoryId = 0; - return GetProductPrice(product, taxCategoryId, price, includingTax, - customer, priceIncludesTax, out taxRate); - } - - /// - /// Gets price - /// - /// Product - /// Tax category identifier - /// Price - /// A value indicating whether calculated price should include tax - /// Customer - /// A value indicating whether price already includes tax - /// Tax rate - /// Price - public virtual decimal GetProductPrice(Product product, int taxCategoryId, - decimal price, bool includingTax, Customer customer, - bool priceIncludesTax, out decimal taxRate) - { - bool isTaxable; - //taxrate should be always passed - GetTaxRate(product, taxCategoryId, customer, price, out taxRate, out isTaxable); - - //no need to calculate tax rate if passed "price" is 0 - if (price == decimal.Zero) - { - return price; - } - - if (priceIncludesTax) - { - //"price" already includes tax - if (includingTax) - { - //we should calculate price WITH tax - if (!isTaxable) - { - //but our request is not taxable - //hence we should calculate price WITHOUT tax - price = CalculatePrice(price, taxRate, false); - } - } - else - { - //we should calculate price WITHOUT tax - price = CalculatePrice(price, taxRate, false); - } - } - else - { - //"price" doesn't include tax - if (includingTax) - { - //we should calculate price WITH tax - //do it only when price is taxable - if (isTaxable) - { - price = CalculatePrice(price, taxRate, true); - } - } - } - - - if (!isTaxable) - { - //we return 0% tax rate in case a request is not taxable - taxRate = decimal.Zero; - } - - //allowed to support negative price adjustments - //if (price < decimal.Zero) - // price = decimal.Zero; - - return price; - } - - #endregion - - #region Shipping price - - /// - /// Gets shipping price - /// - /// Price - /// Customer - /// Price - public virtual decimal GetShippingPrice(decimal price, Customer customer) - { - bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; - return GetShippingPrice(price, includingTax, customer); - } - - /// - /// Gets shipping price - /// - /// Price - /// A value indicating whether calculated price should include tax - /// Customer - /// Price - public virtual decimal GetShippingPrice(decimal price, bool includingTax, Customer customer) - { - decimal taxRate; - return GetShippingPrice(price, includingTax, customer, out taxRate); - } - - /// - /// Gets shipping price - /// - /// Price - /// A value indicating whether calculated price should include tax - /// Customer - /// Tax rate - /// Price - public virtual decimal GetShippingPrice(decimal price, bool includingTax, Customer customer, out decimal taxRate) - { - taxRate = decimal.Zero; - - if (!_taxSettings.ShippingIsTaxable) - { - return price; - } - int taxClassId = _taxSettings.ShippingTaxClassId; - bool priceIncludesTax = _taxSettings.ShippingPriceIncludesTax; - return GetProductPrice(null, taxClassId, price, includingTax, customer, - priceIncludesTax, out taxRate); - } - - #endregion - - #region Payment additional fee - - /// - /// Gets payment method additional handling fee - /// - /// Price - /// Customer - /// Price - public virtual decimal GetPaymentMethodAdditionalFee(decimal price, Customer customer) - { - bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; - return GetPaymentMethodAdditionalFee(price, includingTax, customer); - } - - /// - /// Gets payment method additional handling fee - /// - /// Price - /// A value indicating whether calculated price should include tax - /// Customer - /// Price - public virtual decimal GetPaymentMethodAdditionalFee(decimal price, bool includingTax, Customer customer) - { - decimal taxRate; - return GetPaymentMethodAdditionalFee(price, includingTax, - customer, out taxRate); - } - - /// - /// Gets payment method additional handling fee - /// - /// Price - /// A value indicating whether calculated price should include tax - /// Customer - /// Tax rate - /// Price - public virtual decimal GetPaymentMethodAdditionalFee(decimal price, bool includingTax, Customer customer, out decimal taxRate) - { - taxRate = decimal.Zero; - - if (!_taxSettings.PaymentMethodAdditionalFeeIsTaxable) - { - return price; - } - int taxClassId = _taxSettings.PaymentMethodAdditionalFeeTaxClassId; - bool priceIncludesTax = _taxSettings.PaymentMethodAdditionalFeeIncludesTax; - return GetProductPrice(null, taxClassId, price, includingTax, customer, - priceIncludesTax, out taxRate); - } - - #endregion - - #region Checkout attribute price - - /// - /// Gets checkout attribute value price - /// - /// Checkout attribute value - /// Price - public virtual decimal GetCheckoutAttributePrice(CheckoutAttributeValue cav) - { - var customer = _workContext.CurrentCustomer; - return GetCheckoutAttributePrice(cav, customer); - } - - /// - /// Gets checkout attribute value price - /// - /// Checkout attribute value - /// Customer - /// Price - public virtual decimal GetCheckoutAttributePrice(CheckoutAttributeValue cav, Customer customer) - { - bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; - return GetCheckoutAttributePrice(cav, includingTax, customer); - } - - /// - /// Gets checkout attribute value price - /// - /// Checkout attribute value - /// A value indicating whether calculated price should include tax - /// Customer - /// Price - public virtual decimal GetCheckoutAttributePrice(CheckoutAttributeValue cav, - bool includingTax, Customer customer) - { - decimal taxRate; - return GetCheckoutAttributePrice(cav, includingTax, customer, out taxRate); - } - - /// - /// Gets checkout attribute value price - /// - /// Checkout attribute value - /// A value indicating whether calculated price should include tax - /// Customer - /// Tax rate - /// Price - public virtual decimal GetCheckoutAttributePrice(CheckoutAttributeValue cav, - bool includingTax, Customer customer, out decimal taxRate) - { - if (cav == null) - throw new ArgumentNullException("cav"); - - taxRate = decimal.Zero; - - decimal price = cav.PriceAdjustment; - if (cav.CheckoutAttribute.IsTaxExempt) - { - return price; - } - - bool priceIncludesTax = _taxSettings.PricesIncludeTax; - int taxClassId = cav.CheckoutAttribute.TaxCategoryId; - return GetProductPrice(null, taxClassId, price, includingTax, customer, - priceIncludesTax, out taxRate); - } - - #endregion - - #region VAT - - /// - /// Gets VAT Number status - /// - /// Two letter ISO code of a country and VAT number (e.g. GB 111 1111 111) - /// VAT Number status - public virtual VatNumberStatus GetVatNumberStatus(string fullVatNumber) - { - string name, address; - return GetVatNumberStatus(fullVatNumber, out name, out address); - } - - /// - /// Gets VAT Number status - /// - /// Two letter ISO code of a country and VAT number (e.g. GB 111 1111 111) - /// Name (if received) - /// Address (if received) - /// VAT Number status - public virtual VatNumberStatus GetVatNumberStatus(string fullVatNumber, - out string name, out string address) - { - name = string.Empty; - address = string.Empty; - - if (String.IsNullOrWhiteSpace(fullVatNumber)) - return VatNumberStatus.Empty; - fullVatNumber = fullVatNumber.Trim(); - - //GB 111 1111 111 or GB 1111111111 - //more advanced regex - http://codeigniter.com/wiki/European_Vat_Checker - var r = new Regex(@"^(\w{2})(.*)"); - var match = r.Match(fullVatNumber); - if (!match.Success) - return VatNumberStatus.Invalid; - var twoLetterIsoCode = match.Groups[1].Value; - var vatNumber = match.Groups[2].Value; - - return GetVatNumberStatus(twoLetterIsoCode, vatNumber, out name, out address); - } - - /// - /// Gets VAT Number status - /// - /// Two letter ISO code of a country - /// VAT number - /// VAT Number status - public virtual VatNumberStatus GetVatNumberStatus(string twoLetterIsoCode, string vatNumber) - { - string name, address; - return GetVatNumberStatus(twoLetterIsoCode, vatNumber, out name, out address); - } - - /// - /// Gets VAT Number status - /// - /// Two letter ISO code of a country - /// VAT number - /// Name (if received) - /// Address (if received) - /// VAT Number status - public virtual VatNumberStatus GetVatNumberStatus(string twoLetterIsoCode, string vatNumber, - out string name, out string address) - { - name = string.Empty; - address = string.Empty; - - if (String.IsNullOrEmpty(twoLetterIsoCode)) - return VatNumberStatus.Empty; - - if (String.IsNullOrEmpty(vatNumber)) - return VatNumberStatus.Empty; - - if (_taxSettings.EuVatAssumeValid) - return VatNumberStatus.Valid; - - if (!_taxSettings.EuVatUseWebService) - return VatNumberStatus.Unknown; - - Exception exception; - return DoVatCheck(twoLetterIsoCode, vatNumber, out name, out address, out exception); - } - - /// - /// Performs a basic check of a VAT number for validity - /// - /// Two letter ISO code of a country - /// VAT number - /// Company name - /// Address - /// Exception - /// VAT number status - public virtual VatNumberStatus DoVatCheck(string twoLetterIsoCode, string vatNumber, - out string name, out string address, out Exception exception) - { - name = string.Empty; - address = string.Empty; - - if (vatNumber == null) - vatNumber = string.Empty; - vatNumber = vatNumber.Trim().Replace(" ", ""); - - if (twoLetterIsoCode == null) - twoLetterIsoCode = string.Empty; - if (!String.IsNullOrEmpty(twoLetterIsoCode)) - //The service returns INVALID_INPUT for country codes that are not uppercase. - twoLetterIsoCode = twoLetterIsoCode.ToUpper(); - - EuropaCheckVatService.checkVatService s = null; - - try - { - bool valid; - - s = new EuropaCheckVatService.checkVatService(); - s.checkVat(ref twoLetterIsoCode, ref vatNumber, out valid, out name, out address); - exception = null; - return valid ? VatNumberStatus.Valid : VatNumberStatus.Invalid; - } - catch (Exception ex) - { - name = address = string.Empty; - exception = ex; - return VatNumberStatus.Unknown; - } - finally - { - if (name == null) - name = string.Empty; - - if (address == null) - address = string.Empty; - - if (s != null) - s.Dispose(); - } - } - - #endregion - - #region Exempts - - /// - /// Gets a value indicating whether a product is tax exempt - /// - /// Product - /// Customer - /// A value indicating whether a product is tax exempt - public virtual bool IsTaxExempt(Product product, Customer customer) - { - if (customer != null) - { - if (customer.IsTaxExempt) - return true; - - if (customer.CustomerRoles.Where(cr => cr.Active).Any(cr => cr.TaxExempt)) - return true; - } - - if (product == null) - { - return false; - } - - if (product.IsTaxExempt) - { - return true; - } - - return false; - } - - /// - /// Gets a value indicating whether EU VAT exempt (the European Union Value Added Tax) - /// - /// Address - /// Customer - /// Result - public virtual bool IsVatExempt(Address address, Customer customer) - { - if (!_taxSettings.EuVatEnabled) - return false; - - if (address == null || address.Country == null || customer == null) - return false; - - - if (!address.Country.SubjectToVat) - // VAT not chargeable if shipping outside VAT zone - return true; - - // VAT not chargeable if address, customer and config meet our VAT exemption requirements: - // returns true if this customer is VAT exempt because they are shipping within the EU but outside our shop country, they have supplied a validated VAT number, and the shop is configured to allow VAT exemption - var customerVatStatus = (VatNumberStatus) customer.GetAttribute(SystemCustomerAttributeNames.VatNumberStatusId); - return address.CountryId != _taxSettings.EuVatShopCountryId && - customerVatStatus == VatNumberStatus.Valid && - _taxSettings.EuVatAllowVatExemption; - } - - #endregion - - #endregion - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using Nop.Core; +using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Common; +using Nop.Core.Domain.Customers; +using Nop.Core.Domain.Directory; +using Nop.Core.Domain.Orders; +using Nop.Core.Domain.Shipping; +using Nop.Core.Domain.Tax; +using Nop.Core.Plugins; +using Nop.Services.Common; +using Nop.Services.Directory; +using Nop.Services.Logging; +using Nop.Services.Catalog; + +namespace Nop.Services.Tax +{ + /// + /// Tax service + /// + public partial class TaxService : ITaxService + { + #region Fields + + private readonly IAddressService _addressService; + private readonly IWorkContext _workContext; + private readonly IStoreContext _storeContext; + private readonly TaxSettings _taxSettings; + private readonly IPluginFinder _pluginFinder; + private readonly IGeoLookupService _geoLookupService; + private readonly ICountryService _countryService; + private readonly IStateProvinceService _stateProvinceService; + private readonly ILogger _logger; + private readonly CustomerSettings _customerSettings; + private readonly ShippingSettings _shippingSettings; + private readonly AddressSettings _addressSettings; + private readonly IProductAttributeParser _productAttributeParser; + + #endregion + + #region Ctor + + /// + /// Ctor + /// + /// Address service + /// Work context + /// Store context + /// Tax settings + /// Plugin finder + /// GEO lookup service + /// Country service + /// State province service + /// Logger service + /// Customer settings + /// Shipping settings + /// Address settings + /// Product attribute parser + public TaxService(IAddressService addressService, + IWorkContext workContext, + IStoreContext storeContext, + TaxSettings taxSettings, + IPluginFinder pluginFinder, + IGeoLookupService geoLookupService, + ICountryService countryService, + IStateProvinceService stateProvinceService, + ILogger logger, + CustomerSettings customerSettings, + ShippingSettings shippingSettings, + AddressSettings addressSettings, + IProductAttributeParser productAttributeParser) + { + this._addressService = addressService; + this._workContext = workContext; + this._storeContext = storeContext; + this._taxSettings = taxSettings; + this._pluginFinder = pluginFinder; + this._geoLookupService = geoLookupService; + this._countryService = countryService; + this._stateProvinceService = stateProvinceService; + this._logger = logger; + this._customerSettings = customerSettings; + this._shippingSettings = shippingSettings; + this._addressSettings = addressSettings; + this._productAttributeParser = productAttributeParser; + } + + #endregion + + #region Utilities + + /// + /// Get a value indicating whether a customer is consumer (a person, not a company) located in Europe Union + /// + /// Customer + /// Result + protected virtual bool IsEuConsumer(Customer customer) + { + if (customer == null) + throw new ArgumentNullException("customer"); + + Country country = null; + + //get country from billing address + if (_addressSettings.CountryEnabled && customer.BillingAddress != null) + country = customer.BillingAddress.Country; + + //get country specified during registration? + if (country == null && _customerSettings.CountryEnabled) + { + var countryId = customer.GetAttribute(SystemCustomerAttributeNames.CountryId); + country = _countryService.GetCountryById(countryId); + } + + //get country by IP address + if (country == null) + { + var ipAddress = customer.LastIpAddress; + //ipAddress = _webHelper.GetCurrentIpAddress(); + var countryIsoCode = _geoLookupService.LookupCountryIsoCode(ipAddress); + country = _countryService.GetCountryByTwoLetterIsoCode(countryIsoCode); + } + + //we cannot detect country + if (country == null) + return false; + + //outside EU + if (!country.SubjectToVat) + return false; + + //company (business) or consumer? + var customerVatStatus = (VatNumberStatus)customer.GetAttribute(SystemCustomerAttributeNames.VatNumberStatusId); + if (customerVatStatus == VatNumberStatus.Valid) + return false; + + //TODO: use specified company name? (both address and registration one) + + //consumer + return true; + } + + /// + /// Get a value indicating whether prices do include tax + /// + /// Result + public virtual bool PricesIncludeTax() + { + return _taxSettings.PricesIncludeTax; + } + /// + /// Create request for tax calculation + /// + /// Product + /// Tax category identifier + /// Customer + /// Price + /// Package for tax calculation + protected virtual CalculateTaxRequest CreateCalculateTaxRequest(Product product, + int taxCategoryId, Customer customer, decimal price) + { + if (customer == null) + throw new ArgumentNullException("customer"); + + var calculateTaxRequest = new CalculateTaxRequest + { + Customer = customer, + Product = product, + Price = price, + TaxCategoryId = taxCategoryId > 0 ? taxCategoryId : (product != null ? product.TaxCategoryId : 0) + }; + + var basedOn = _taxSettings.TaxBasedOn; + + //new EU VAT rules starting January 1st 2015 + //find more info at http://ec.europa.eu/taxation_customs/taxation/vat/how_vat_works/telecom/index_en.htm#new_rules + var overridenBasedOn = _taxSettings.EuVatEnabled //EU VAT enabled? + && product != null && product.IsTelecommunicationsOrBroadcastingOrElectronicServices //telecommunications, broadcasting and electronic services? + && DateTime.UtcNow > new DateTime(2015, 1, 1, 0, 0, 0, DateTimeKind.Utc) //January 1st 2015 passed? + && IsEuConsumer(customer); //Europe Union consumer? + if (overridenBasedOn) + { + //We must charge VAT in the EU country where the customer belongs (not where the business is based) + basedOn = TaxBasedOn.BillingAddress; + } + + //tax is based on pickup point address + if (!overridenBasedOn && _taxSettings.TaxBasedOnPickupPointAddress && _shippingSettings.AllowPickUpInStore) + { + var pickupPoint = customer.GetAttribute(SystemCustomerAttributeNames.SelectedPickupPoint, _storeContext.CurrentStore.Id); + if (pickupPoint != null) + { + var country = _countryService.GetCountryByTwoLetterIsoCode(pickupPoint.CountryCode); + var state = _stateProvinceService.GetStateProvinceByAbbreviation(pickupPoint.StateAbbreviation); + + calculateTaxRequest.Address = new Address + { + Address1 = pickupPoint.Address, + City = pickupPoint.City, + Country = country, + CountryId = country.Return(c => c.Id, 0), + StateProvince = state, + StateProvinceId = state.Return(sp => sp.Id, 0), + ZipPostalCode = pickupPoint.ZipPostalCode, + CreatedOnUtc = DateTime.UtcNow + }; + + return calculateTaxRequest; + } + } + + if (basedOn == TaxBasedOn.BillingAddress && customer.BillingAddress == null || + basedOn == TaxBasedOn.ShippingAddress && customer.ShippingAddress == null) + { + basedOn = TaxBasedOn.DefaultAddress; + } + + switch (basedOn) + { + case TaxBasedOn.BillingAddress: + calculateTaxRequest.Address = customer.BillingAddress; + break; + case TaxBasedOn.ShippingAddress: + calculateTaxRequest.Address = customer.ShippingAddress; + break; + case TaxBasedOn.DefaultAddress: + default: + calculateTaxRequest.Address = _addressService.GetAddressById(_taxSettings.DefaultTaxAddressId); + break; + } + + return calculateTaxRequest; + } + + /// + /// Calculated price + /// + /// Price + /// Percent + /// Increase + /// New price + protected virtual decimal CalculatePrice(decimal price, decimal percent, bool increase) + { + if (percent == decimal.Zero) + return price; + + decimal result; + if (increase) + { + result = price * (1 + percent / 100); + } + else + { + result = price / (1 + percent / 100); //MF 13.11.16: is the same but written in a complicated way: price - (price) / (100 + percent) * percent; + } + return result; + } + + /// + /// Gets tax rate + /// + /// Product + /// Tax category identifier + /// Customer + /// Price (taxable value) + /// Calculated tax rate + /// A value indicating whether a request is taxable + protected virtual void GetTaxRate(Product product, int taxCategoryId, + Customer customer, decimal price, out decimal taxRate, out bool isTaxable) + { + taxRate = decimal.Zero; + isTaxable = true; + + //active tax provider + var activeTaxProvider = LoadActiveTaxProvider(customer); + if (activeTaxProvider == null) + return; + + //tax request + var calculateTaxRequest = CreateCalculateTaxRequest(product, taxCategoryId, customer, price); + + //tax exempt + if (IsTaxExempt(product, calculateTaxRequest.Customer)) + { + isTaxable = false; + } + //make EU VAT exempt validation (the European Union Value Added Tax) + if (isTaxable && + _taxSettings.EuVatEnabled && + IsVatExempt(calculateTaxRequest.Address, calculateTaxRequest.Customer)) + { + //VAT is not chargeable + isTaxable = false; + } + + //get tax rate + var calculateTaxResult = activeTaxProvider.GetTaxRate(calculateTaxRequest); + if (calculateTaxResult.Success) + { + //ensure that tax is equal or greater than zero + if (calculateTaxResult.TaxRate < decimal.Zero) + calculateTaxResult.TaxRate = decimal.Zero; + + taxRate = calculateTaxResult.TaxRate; + } + else + if (_taxSettings.LogErrors) + { + foreach (var error in calculateTaxResult.Errors) + { + _logger.Error(string.Format("{0} - {1}", activeTaxProvider.PluginDescriptor.FriendlyName, error), null, customer); + } + } + } + + #endregion + + #region Methods + + #region Tax providers + + /// + /// Load active tax provider + /// + /// Load records allowed only to a specified customer; pass null to ignore ACL permissions + /// Active tax provider + public virtual ITaxProvider LoadActiveTaxProvider(Customer customer = null) + { + var taxProvider = LoadTaxProviderBySystemName(_taxSettings.ActiveTaxProviderSystemName); + if (taxProvider == null) + taxProvider = LoadAllTaxProviders(customer).FirstOrDefault(); + return taxProvider; + } + + /// + /// Load tax provider by system name + /// + /// System name + /// Found tax provider + public virtual ITaxProvider LoadTaxProviderBySystemName(string systemName) + { + var descriptor = _pluginFinder.GetPluginDescriptorBySystemName(systemName); + if (descriptor != null) + return descriptor.Instance(); + + return null; + } + + /// + /// Load all tax providers + /// + /// Load records allowed only to a specified customer; pass null to ignore ACL permissions + /// Tax providers + public virtual IList LoadAllTaxProviders(Customer customer = null) + { + return _pluginFinder.GetPlugins(customer: customer).ToList(); + } + + #endregion + + #region Product price + + /// + /// Gets price + /// + /// Product + /// Price + /// Tax rate + /// Price + public virtual decimal GetProductPrice(Product product, decimal price, + out decimal taxRate) + { + var customer = _workContext.CurrentCustomer; + return GetProductPrice(product, price, customer, out taxRate); + } + + /// + /// Gets price using tax attributes + /// + /// Product + /// Price + /// Tax rate + /// Product atrributes (XML format) + /// Optional value indicating whether calculated price should include tax + /// Price + public virtual decimal GetProductPrice(Product product, decimal price, + out decimal taxRate, ref string attributesXml, bool? includingTax = null) + { + var customer = _workContext.CurrentCustomer; + bool priceIncludesTax = _taxSettings.PricesIncludeTax; + int taxCategoryId = 0; + return GetProductPrice(product, taxCategoryId, price, + includingTax ?? _workContext.TaxDisplayType == TaxDisplayType.IncludingTax, + customer, priceIncludesTax, out taxRate, ref attributesXml); + } + /// + /// Gets price + /// + /// Product + /// Price + /// Customer + /// Tax rate + /// Price + public virtual decimal GetProductPrice(Product product, decimal price, + Customer customer, out decimal taxRate) + { + bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; + return GetProductPrice(product, price, includingTax, customer, out taxRate); + } + + /// + /// Gets price + /// + /// Product + /// Price + /// A value indicating whether calculated price should include tax + /// Customer + /// Tax rate + /// Price + public virtual decimal GetProductPrice(Product product, decimal price, + bool includingTax, Customer customer, out decimal taxRate) + { + bool priceIncludesTax = _taxSettings.PricesIncludeTax; + int taxCategoryId = 0; + string attributesXml = ""; + return GetProductPrice(product, taxCategoryId, price, includingTax, + customer, priceIncludesTax, out taxRate, ref attributesXml); + } + + /// + /// Gets price + /// + /// Product + /// Tax category identifier + /// Price + /// A value indicating whether calculated price should include tax + /// Customer + /// A value indicating whether price already includes tax + /// Tax rate + /// Product atrributes (XML format) + /// Price + public virtual decimal GetProductPrice(Product product, int taxCategoryId, + decimal price, bool includingTax, Customer customer, + bool priceIncludesTax, out decimal taxRate, ref string attributesXml) + { + bool isTaxable; + //taxrate should be always passed + GetTaxRate(product, taxCategoryId, customer, price, out taxRate, out isTaxable); + + //get taxWeights of product attributes + SortedDictionary attributeTaxWeight = !String.IsNullOrEmpty(attributesXml) ? _productAttributeParser.ParseTaxAttribute(attributesXml) : null; + + //normal price or no need for taxinfo to attributesXml + if (String.IsNullOrEmpty(attributesXml) || attributeTaxWeight == null || !attributeTaxWeight.Any()) + { + price = GetPrice(price, includingTax, priceIncludesTax, taxRate, isTaxable); + } + //add taxinfo to attributesXml + else + { + int numtaxRates = attributeTaxWeight.Count(); + taxRate = attributeTaxWeight.FirstOrDefault().Key; // pass the first taxRate as it might be different from product taxrate. Will be used when #rates == 1 + + //need to change attributeXML (otherwise already set by unitprice calculation) + if (priceIncludesTax != includingTax) + { + //no need to change attributeXML when only one rate + if (numtaxRates == 1) + { + price = GetPrice(price, includingTax, priceIncludesTax, taxRate, isTaxable); + } + else + { + var taxWeightSummary = new TaxSummary(includingTax); + foreach (KeyValuePair kvp in attributeTaxWeight) + { + decimal attribtaxRate = kvp.Key; + decimal rateWeight = kvp.Value; + var priceWeighted = price * rateWeight; + var attributePrice = RoundingHelper.RoundAmount(GetPrice(priceWeighted, includingTax, priceIncludesTax, attribtaxRate, isTaxable)); + taxWeightSummary.AddRate(attribtaxRate, attributePrice); + } + + taxWeightSummary.CalculateWeights(); + attributesXml = _productAttributeParser.AddTaxAttribute(attributesXml, taxWeightSummary); + price = includingTax ? taxWeightSummary.TotalAmountIncludingTax : taxWeightSummary.TotalAmount; + } + } + } + return price; + } + + protected virtual decimal GetPrice(decimal price, bool includingTax, + bool priceIncludesTax, decimal taxRate, bool isTaxable) + { + //no need to calculate new price if passed "price" is 0 + if (price == decimal.Zero) + { + return price; + } + + if (priceIncludesTax) + { + //"price" already includes tax + if (includingTax) + { + //we should calculate price WITH tax + if (!isTaxable) + { + //but our request is not taxable + //hence we should calculate price WITHOUT tax + price = CalculatePrice(price, taxRate, false); + } + } + else + { + //we should calculate price WITHOUT tax + price = CalculatePrice(price, taxRate, false); + } + } + else + { + //"price" doesn't include tax + if (includingTax) + { + //we should calculate price WITH tax + //do it only when price is taxable + if (isTaxable) + { + price = CalculatePrice(price, taxRate, true); + } + } + } + + + if (!isTaxable) + { + //we return 0% tax rate in case a request is not taxable + taxRate = decimal.Zero; + } + + //commented out to allow negative price adjustments + //if (price < decimal.Zero) + // price = decimal.Zero; + + return price; + } + + #endregion + + #region Shipping price + + /// + /// Gets shipping price + /// + /// Price + /// Customer + /// Price + public virtual decimal GetShippingPrice(decimal price, Customer customer) + { + bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; + return GetShippingPrice(price, includingTax, customer); + } + + /// + /// Gets shipping price + /// + /// Price + /// A value indicating whether calculated price should include tax + /// Customer + /// Price + public virtual decimal GetShippingPrice(decimal price, bool includingTax, Customer customer) + { + decimal taxRate; + return GetShippingPrice(price, includingTax, customer, out taxRate); + } + + /// + /// Gets shipping price + /// + /// Price + /// A value indicating whether calculated price should include tax + /// Customer + /// Tax rate + /// Price + public virtual decimal GetShippingPrice(decimal price, bool includingTax, Customer customer, out decimal taxRate) + { + taxRate = decimal.Zero; + + if (!_taxSettings.ShippingIsTaxable) + { + return price; + } + int taxClassId = _taxSettings.ShippingTaxClassId; + bool priceIncludesTax = _taxSettings.ShippingPriceIncludesTax; + string attributesXml = ""; + return GetProductPrice(null, taxClassId, price, includingTax, customer, + priceIncludesTax, out taxRate, ref attributesXml); + } + + #endregion + + #region Payment additional fee + + /// + /// Gets payment method additional handling fee + /// + /// Price + /// Customer + /// Price + public virtual decimal GetPaymentMethodAdditionalFee(decimal price, Customer customer) + { + bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; + return GetPaymentMethodAdditionalFee(price, includingTax, customer); + } + + /// + /// Gets payment method additional handling fee + /// + /// Price + /// A value indicating whether calculated price should include tax + /// Customer + /// Price + public virtual decimal GetPaymentMethodAdditionalFee(decimal price, bool includingTax, Customer customer) + { + decimal taxRate; + return GetPaymentMethodAdditionalFee(price, includingTax, + customer, out taxRate); + } + + /// + /// Gets payment method additional handling fee + /// + /// Price + /// A value indicating whether calculated price should include tax + /// Customer + /// Tax rate + /// Price + public virtual decimal GetPaymentMethodAdditionalFee(decimal price, bool includingTax, Customer customer, out decimal taxRate) + { + taxRate = decimal.Zero; + + if (!_taxSettings.PaymentMethodAdditionalFeeIsTaxable) + { + return price; + } + int taxClassId = _taxSettings.PaymentMethodAdditionalFeeTaxClassId; + bool priceIncludesTax = _taxSettings.PaymentMethodAdditionalFeeIncludesTax; + string attributesXml = ""; + return GetProductPrice(null, taxClassId, price, includingTax, customer, + priceIncludesTax, out taxRate, ref attributesXml); + } + + #endregion + + #region Checkout attribute price + + /// + /// Gets checkout attribute value price + /// + /// Checkout attribute value + /// Price + public virtual decimal GetCheckoutAttributePrice(CheckoutAttributeValue cav) + { + var customer = _workContext.CurrentCustomer; + return GetCheckoutAttributePrice(cav, customer); + } + + /// + /// Gets checkout attribute value price + /// + /// Checkout attribute value + /// Customer + /// Price + public virtual decimal GetCheckoutAttributePrice(CheckoutAttributeValue cav, Customer customer) + { + bool includingTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; + return GetCheckoutAttributePrice(cav, includingTax, customer); + } + + /// + /// Gets checkout attribute value price + /// + /// Checkout attribute value + /// A value indicating whether calculated price should include tax + /// Customer + /// Price + public virtual decimal GetCheckoutAttributePrice(CheckoutAttributeValue cav, + bool includingTax, Customer customer) + { + decimal taxRate; + return GetCheckoutAttributePrice(cav, includingTax, customer, out taxRate); + } + + /// + /// Gets checkout attribute value price + /// + /// Checkout attribute value + /// A value indicating whether calculated price should include tax + /// Customer + /// Tax rate + /// Price + public virtual decimal GetCheckoutAttributePrice(CheckoutAttributeValue cav, + bool includingTax, Customer customer, out decimal taxRate) + { + if (cav == null) + throw new ArgumentNullException("cav"); + + taxRate = decimal.Zero; + + decimal price = cav.PriceAdjustment; + if (cav.CheckoutAttribute.IsTaxExempt) + { + return price; + } + + bool priceIncludesTax = _taxSettings.PricesIncludeTax; + int taxClassId = cav.CheckoutAttribute.TaxCategoryId; + string attributesXml = ""; + return GetProductPrice(null, taxClassId, price, includingTax, customer, + priceIncludesTax, out taxRate, ref attributesXml); + } + + #endregion + + #region VAT + + /// + /// Gets VAT Number status + /// + /// Two letter ISO code of a country and VAT number (e.g. GB 111 1111 111) + /// VAT Number status + public virtual VatNumberStatus GetVatNumberStatus(string fullVatNumber) + { + string name, address; + return GetVatNumberStatus(fullVatNumber, out name, out address); + } + + /// + /// Gets VAT Number status + /// + /// Two letter ISO code of a country and VAT number (e.g. GB 111 1111 111) + /// Name (if received) + /// Address (if received) + /// VAT Number status + public virtual VatNumberStatus GetVatNumberStatus(string fullVatNumber, + out string name, out string address) + { + name = string.Empty; + address = string.Empty; + + if (String.IsNullOrWhiteSpace(fullVatNumber)) + return VatNumberStatus.Empty; + fullVatNumber = fullVatNumber.Trim(); + + //GB 111 1111 111 or GB 1111111111 + //more advanced regex - http://codeigniter.com/wiki/European_Vat_Checker + var r = new Regex(@"^(\w{2})(.*)"); + var match = r.Match(fullVatNumber); + if (!match.Success) + return VatNumberStatus.Invalid; + var twoLetterIsoCode = match.Groups[1].Value; + var vatNumber = match.Groups[2].Value; + + return GetVatNumberStatus(twoLetterIsoCode, vatNumber, out name, out address); + } + + /// + /// Gets VAT Number status + /// + /// Two letter ISO code of a country + /// VAT number + /// VAT Number status + public virtual VatNumberStatus GetVatNumberStatus(string twoLetterIsoCode, string vatNumber) + { + string name, address; + return GetVatNumberStatus(twoLetterIsoCode, vatNumber, out name, out address); + } + + /// + /// Gets VAT Number status + /// + /// Two letter ISO code of a country + /// VAT number + /// Name (if received) + /// Address (if received) + /// VAT Number status + public virtual VatNumberStatus GetVatNumberStatus(string twoLetterIsoCode, string vatNumber, + out string name, out string address) + { + name = string.Empty; + address = string.Empty; + + if (String.IsNullOrEmpty(twoLetterIsoCode)) + return VatNumberStatus.Empty; + + if (String.IsNullOrEmpty(vatNumber)) + return VatNumberStatus.Empty; + + if (_taxSettings.EuVatAssumeValid) + return VatNumberStatus.Valid; + + if (!_taxSettings.EuVatUseWebService) + return VatNumberStatus.Unknown; + + Exception exception; + return DoVatCheck(twoLetterIsoCode, vatNumber, out name, out address, out exception); + } + + /// + /// Performs a basic check of a VAT number for validity + /// + /// Two letter ISO code of a country + /// VAT number + /// Company name + /// Address + /// Exception + /// VAT number status + public virtual VatNumberStatus DoVatCheck(string twoLetterIsoCode, string vatNumber, + out string name, out string address, out Exception exception) + { + name = string.Empty; + address = string.Empty; + + if (vatNumber == null) + vatNumber = string.Empty; + vatNumber = vatNumber.Trim().Replace(" ", ""); + + if (twoLetterIsoCode == null) + twoLetterIsoCode = string.Empty; + if (!String.IsNullOrEmpty(twoLetterIsoCode)) + //The service returns INVALID_INPUT for country codes that are not uppercase. + twoLetterIsoCode = twoLetterIsoCode.ToUpper(); + + EuropaCheckVatService.checkVatService s = null; + + try + { + bool valid; + + s = new EuropaCheckVatService.checkVatService(); + s.checkVat(ref twoLetterIsoCode, ref vatNumber, out valid, out name, out address); + exception = null; + return valid ? VatNumberStatus.Valid : VatNumberStatus.Invalid; + } + catch (Exception ex) + { + name = address = string.Empty; + exception = ex; + return VatNumberStatus.Unknown; + } + finally + { + if (name == null) + name = string.Empty; + + if (address == null) + address = string.Empty; + + if (s != null) + s.Dispose(); + } + } + + #endregion + + #region Exempts + + /// + /// Gets a value indicating whether a product is tax exempt + /// + /// Product + /// Customer + /// A value indicating whether a product is tax exempt + public virtual bool IsTaxExempt(Product product, Customer customer) + { + if (customer != null) + { + if (customer.IsTaxExempt) + return true; + + if (customer.CustomerRoles.Where(cr => cr.Active).Any(cr => cr.TaxExempt)) + return true; + } + + if (product == null) + { + return false; + } + + if (product.IsTaxExempt) + { + return true; + } + + return false; + } + + /// + /// Gets a value indicating whether EU VAT exempt (the European Union Value Added Tax) + /// + /// Address + /// Customer + /// Result + public virtual bool IsVatExempt(Address address, Customer customer) + { + if (!_taxSettings.EuVatEnabled) + return false; + + if (address == null || address.Country == null || customer == null) + return false; + + + if (!address.Country.SubjectToVat) + // VAT not chargeable if shipping outside VAT zone + return true; + + // VAT not chargeable if address, customer and config meet our VAT exemption requirements: + // returns true if this customer is VAT exempt because they are shipping within the EU but outside our shop country, they have supplied a validated VAT number, and the shop is configured to allow VAT exemption + var customerVatStatus = (VatNumberStatus) customer.GetAttribute(SystemCustomerAttributeNames.VatNumberStatusId); + return address.CountryId != _taxSettings.EuVatShopCountryId && + customerVatStatus == VatNumberStatus.Valid && + _taxSettings.EuVatAllowVatExemption; + } + + #endregion + + #endregion + } +} diff --git a/src/Libraries/Nop.Services/Tax/TaxSummary.cs b/src/Libraries/Nop.Services/Tax/TaxSummary.cs index 440e9077875..55820318f36 100644 --- a/src/Libraries/Nop.Services/Tax/TaxSummary.cs +++ b/src/Libraries/Nop.Services/Tax/TaxSummary.cs @@ -1,5 +1,6 @@  using Nop.Services.Catalog; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -7,286 +8,576 @@ namespace Nop.Services.Tax { + #region taxrateEntry /// - /// TaxRate entry to store amounts per vatrate + /// TaxRate entry to store amounts per taxrate /// all values are per rate and most of them calculated - /// only VatRate, ItemAmount, ShippingAmount and PaymentFeeAmount can be set + /// only TaxRate, ItemAmount, ShippingAmount and PaymentFeeAmount can be set /// public partial class TaxRateEntry { - public decimal VatRate { get; set; } - public decimal VatRateWeight { get; internal set; } //weight of VatRate used for product sets [it's (sum of associated products price per VatRate) / (total price of assoc. prod.) ] + public decimal TaxRate { get; set; } + public decimal TaxRateWeight { get; internal set; } //weight of TaxRate used for product sets [it's (sum of associated products price per TaxRate) / (total price of assoc. prod.) ] public decimal SubtotalAmount { get; set; } public decimal ShippingAmount { get; set; } - public decimal PaymentFeeAmount { get; set; } //can be negative when discounted - public decimal Amount { get; internal set; } //sum of above amounts - public decimal SubTotalDiscAmount { get; internal set; } + public decimal PaymentFeeAmount { get; set; } //when set as fee + public decimal EntryAmount //sum of above amounts + { + get { return SubtotalAmount + ShippingAmount + PaymentFeeAmount; } + } + public decimal SubTotalDiscountAmount { get; internal set; } public decimal InvoiceDiscountAmount { get; internal set; } - public decimal DiscountAmount { get; internal set; } //sum of above discounts - public decimal BaseAmount { get; internal set; } - public decimal VatAmount { get; internal set; } - public decimal AmountIncludingVAT { get; internal set; } + public decimal PaymentFeeDiscountAmount { get; set; } //when set as discount or surcharge + public decimal RewardPointsDiscountAmount { get; set; } //earned reward points + public decimal DiscountAmount//sum of above discount amounts + { + get { return SubTotalDiscountAmount + InvoiceDiscountAmount + PaymentFeeDiscountAmount + RewardPointsDiscountAmount; } + } + public decimal BaseAmount { get; internal set; } //base amount excl. tax + public decimal TaxAmount { get; internal set; } //tax + public decimal AmountIncludingTax { get; internal set; } //base + tax } + + #endregion + /// /// TaxSummary to store TaxRates and overall totals /// public partial class TaxSummary { + //private + decimal _totalSubTotalAmount; + decimal? _totalShippingAmountTaxable; + decimal _totalAmount; + decimal _totalAmountTax; + decimal _totalAmountIncludingTax; + decimal _totalPaymentFeeDiscAmount; + decimal _totalPaymentFeeAmount; + decimal _totalRewardPointsAmountTaxable; + decimal _totalSubTotalDiscAmount; + decimal _totalInvDiscAmount; + + //externally set amounts + decimal? _totalShippingAmountNonTaxable; + decimal? _totalRewardPointsAmountNonTaxable; + decimal? _totalPaymentFeeAmountNonTaxable; + decimal? _totalRewardPointsAmountPurchased; + decimal? _totalGiftcardsAmount; + #region Ctor /// /// Ctor /// - /// Indicates if given amounts do include tax - public TaxSummary(bool includingTax) + /// Indicates if given amounts do include tax + public TaxSummary(bool pricesIncludingTax) { TaxRates = new SortedDictionary(); - PricesIncludeTax = includingTax; - PercentageInvDiscount = decimal.Zero; - PercentageSubTotalDiscount = decimal.Zero; - PercentagePaymentFeeDiscount = decimal.Zero; - - TotalSubTotalAmount = decimal.Zero; - TotalShippingAmount = decimal.Zero; - TotalPaymentFeeAmount = decimal.Zero; - TotalAmount = decimal.Zero; - TotalAmountVAT = decimal.Zero; - TotalAmountIncludingVAT = decimal.Zero; - TotalSubTotalDiscAmount = decimal.Zero; - TotalInvDiscAmount = decimal.Zero; + PricesIncludeTax = pricesIncludingTax; } #endregion - public SortedDictionary TaxRates { get; set; } + public SortedDictionary TaxRates { get; private set; } public bool PricesIncludeTax { get; } - public decimal PercentageInvDiscount { get; private set; } - public decimal PercentageSubTotalDiscount { get; private set; } - public decimal PercentagePaymentFeeDiscount { get; private set; } - public decimal TotalSubTotalAmount { get; private set; } - public decimal TotalShippingAmount { get; private set; } - public decimal TotalPaymentFeeAmount { get; private set; } - public decimal TotalAmount { get; private set; } - public decimal TotalAmountVAT { get; private set; } - public decimal TotalAmountIncludingVAT { get; private set; } - public decimal TotalSubTotalDiscAmount { get; private set; } - public decimal TotalInvDiscAmount { get; private set; } - - private bool HasChanged = false; + public decimal PercentageInvDiscount { get; private set; } = decimal.Zero; //set via sub + public decimal PercentageSubTotalDiscount { get; private set; } = decimal.Zero; //set via sub + public decimal PercentageRewardPointsDiscount { get; private set; } = decimal.Zero; //earned points, set via sub + public decimal PercentagePaymentFeeOrDiscount { get; private set; } = decimal.Zero; //set via sub + public decimal TotalSubTotalAmount { get { if (HasChanges) CalculateAmounts(); return _totalSubTotalAmount; } } + public decimal? TotalShippingAmountTaxable { get { if (HasChanges) CalculateAmounts(); return _totalShippingAmountTaxable; } } + public decimal? TotalShippingAmountNonTaxable + { + get { return _totalShippingAmountNonTaxable; } + set + { + _totalShippingAmountNonTaxable = value; + if (value.HasValue) + { + _totalShippingAmountNonTaxable = RoundingHelper.RoundAmount(value.Value); + HasShipping = true; + } + } + } + public decimal? TotalShippingAmount { get { return GetSum(SumType.TotalShippingAmount); } } //sum of shipping amounts + public decimal TotalRewardPointsAmountTaxable { get { if (HasChanges) CalculateAmounts(); return _totalRewardPointsAmountTaxable; } } //set via percentage + public decimal? TotalRewardPointsAmountNonTaxable + { + get { return _totalRewardPointsAmountNonTaxable; } + set { _totalRewardPointsAmountNonTaxable = value.HasValue ? RoundingHelper.RoundAmount(value.Value) : value; } + } + public decimal TotalPaymentFeeAmount { get { if (HasChanges) CalculateAmounts(); return _totalPaymentFeeAmount; } } //set via percentage + public decimal? TotalPaymentFeeAmountNonTaxable + { + get { return _totalPaymentFeeAmountNonTaxable; } + set { _totalPaymentFeeAmountNonTaxable = value.HasValue ? RoundingHelper.RoundAmount(value.Value) : value; } + } + + public decimal TotalPaymentFeeDiscAmount { get { if (HasChanges) CalculateAmounts(); return _totalPaymentFeeDiscAmount; } } //set via percentage + public decimal TotalPaymentFeeAmountTaxable { get { return GetSum(SumType.TotalPaymentFeeAmountTaxable) ?? decimal.Zero; } } //sum of payment fees + public decimal TotalSubTotalDiscAmount { get { if (HasChanges) CalculateAmounts(); return _totalSubTotalDiscAmount; } } //set via percentage + public decimal TotalInvDiscAmount { get { if (HasChanges) CalculateAmounts(); return _totalInvDiscAmount; } } //set via percentage + public decimal TotalAmount { get { if (HasChanges) CalculateAmounts(); return _totalAmount; } } + public decimal TotalAmountTax { get { if (HasChanges) CalculateAmounts(); return _totalAmountTax; } } + public decimal TotalAmountIncludingTax { get { if (HasChanges) CalculateAmounts(); return _totalAmountIncludingTax; } } + public decimal? TotalRewardPointsAmountPurchased + { + get { return _totalRewardPointsAmountPurchased; } + set { _totalRewardPointsAmountPurchased = value.HasValue ? RoundingHelper.RoundAmount(value.Value) : value; } + } + public decimal? TotalGiftcardsAmount + { + get { return _totalGiftcardsAmount; } + set { _totalGiftcardsAmount = value.HasValue ? RoundingHelper.RoundAmount(value.Value) : value; } + } + public decimal TotalPaymentAmount { get { return GetSum(SumType.TotalPaymentAmount) ?? decimal.Zero; } } + public decimal TotalBaseAmountForPaymentFeeCalculation { get { return GetSum(SumType.TotalBaseAmountForPaymentFeeCalculation) ?? decimal.Zero; } } + public decimal TotalOrderAmount { get { return GetSum(SumType.TotalOrderAmount) ?? decimal.Zero; } } + public decimal TotalOrderAmountIncl { get { return GetSum(SumType.TotalOrderAmountIncl) ?? decimal.Zero; } } + + + private bool HasChanges; + private bool HasPaymentFeeTax + { + get + { + foreach (var tr in TaxRates) + { + if (tr.Value.PaymentFeeAmount != decimal.Zero) + return true; + } + return false; + } + } + private bool HasShipping { get; set; } = false; + + + #region sums + private enum SumType { TotalShippingAmount, TotalPaymentFeeAmountTaxable, TotalPaymentAmount, TotalBaseAmountForPaymentFeeCalculation, TotalOrderAmount, TotalOrderAmountIncl }; + private decimal? GetSum(SumType type) + { + + if (HasChanges) + CalculateAmounts(); + + switch (type) + { + case SumType.TotalShippingAmount: + if (TotalShippingAmountTaxable.HasValue && !TotalShippingAmountNonTaxable.HasValue) + return TotalShippingAmountTaxable; + if (!TotalShippingAmountTaxable.HasValue && TotalShippingAmountNonTaxable.HasValue) + return TotalShippingAmountNonTaxable; + + return TotalShippingAmountTaxable + TotalShippingAmountNonTaxable; + + case SumType.TotalPaymentFeeAmountTaxable: + return TotalPaymentFeeAmount - TotalPaymentFeeDiscAmount; + + case SumType.TotalPaymentAmount: + decimal amountPay = TotalAmountIncludingTax + + (TotalShippingAmountNonTaxable ?? decimal.Zero) + + (TotalPaymentFeeAmountNonTaxable ?? decimal.Zero) + - (TotalRewardPointsAmountNonTaxable ?? decimal.Zero) + - (TotalGiftcardsAmount ?? decimal.Zero) + - (TotalRewardPointsAmountPurchased ?? decimal.Zero); + + if (amountPay < decimal.Zero) + amountPay = decimal.Zero; + + return amountPay; + + case SumType.TotalBaseAmountForPaymentFeeCalculation: + return PricesIncludeTax ? TotalAmountIncludingTax : TotalAmount; + + case SumType.TotalOrderAmount: + decimal amountOrder = TotalAmount + + (TotalShippingAmountNonTaxable ?? decimal.Zero) + + (TotalPaymentFeeAmountNonTaxable ?? decimal.Zero) + - (TotalRewardPointsAmountNonTaxable ?? decimal.Zero); + + if (amountOrder < decimal.Zero) + amountOrder = decimal.Zero; + return amountOrder; + + case SumType.TotalOrderAmountIncl: + decimal amountOrderIncl = TotalAmountIncludingTax + + (TotalShippingAmountNonTaxable ?? decimal.Zero) + + (TotalPaymentFeeAmountNonTaxable ?? decimal.Zero) + - (TotalRewardPointsAmountNonTaxable ?? decimal.Zero); + + if (amountOrderIncl < decimal.Zero) + amountOrderIncl = decimal.Zero; + return amountOrderIncl; + + default: + return decimal.Zero; + } + } + + #endregion #region utilities public string GenerateTaxRateString() { return this.TaxRates.Aggregate(string.Empty, (current, next) => string.Format("{0}{1}:{2}:{3}:{4}:{5}:{6}; ", current, - next.Key.ToString(CultureInfo.InvariantCulture), - next.Value.Amount.ToString(CultureInfo.InvariantCulture), + next.Value.TaxRate.ToString(CultureInfo.InvariantCulture), + next.Value.EntryAmount.ToString(CultureInfo.InvariantCulture), next.Value.DiscountAmount.ToString(CultureInfo.InvariantCulture), next.Value.BaseAmount.ToString(CultureInfo.InvariantCulture), - next.Value.VatAmount.ToString(CultureInfo.InvariantCulture), - next.Value.AmountIncludingVAT.ToString(CultureInfo.InvariantCulture) + next.Value.TaxAmount.ToString(CultureInfo.InvariantCulture), + next.Value.AmountIncludingTax.ToString(CultureInfo.InvariantCulture) ) ); } public SortedDictionary GenerateOldTaxrateDict() { - return new SortedDictionary(TaxRates.ToDictionary(x => x.Key, x => x.Value.VatAmount)); + return new SortedDictionary(TaxRates.ToDictionary(x => x.Value.TaxRate, x => x.Value.TaxAmount)); + + } + + protected decimal CalcDiscountPercentage(ref decimal discAmount, decimal baseAmount) + { + decimal PercentageDiscount; + + if (baseAmount == decimal.Zero) + { + PercentageDiscount = decimal.Zero; + discAmount = decimal.Zero; + } + else + { + if (discAmount > baseAmount) + discAmount = baseAmount; + PercentageDiscount = discAmount / baseAmount * 100; + } + return PercentageDiscount; } + #endregion #region methods + + #region rates /// /// Add amounts to taxrate /// - /// Vat % + /// Vat % /// Item amount /// Shipping amount - /// Payment method fee amount - public void AddRate(decimal vatRate, decimal itemAmount = 0, decimal shippingAmount = 0, decimal paymentfeeAmount = 0) + /// Payment method fee amount + public void AddRate(decimal taxRate, decimal itemAmount = decimal.Zero, decimal? shippingAmount = null, decimal paymentFeeAmount = decimal.Zero) { - if (!TaxRates.ContainsKey(vatRate)) - TaxRates.Add(vatRate, new TaxRateEntry() + if (shippingAmount.HasValue) + HasShipping = true; + + if (!TaxRates.ContainsKey(taxRate)) + TaxRates.Add(taxRate, new TaxRateEntry() { - VatRate = vatRate, + TaxRate = taxRate, SubtotalAmount = itemAmount, - ShippingAmount = shippingAmount, - PaymentFeeAmount = paymentfeeAmount + ShippingAmount = shippingAmount ?? decimal.Zero, + PaymentFeeAmount = paymentFeeAmount }); else { - TaxRates[vatRate].SubtotalAmount += itemAmount; - TaxRates[vatRate].ShippingAmount += shippingAmount; - TaxRates[vatRate].PaymentFeeAmount += paymentfeeAmount; + TaxRates[taxRate].SubtotalAmount += itemAmount; + TaxRates[taxRate].ShippingAmount += shippingAmount ?? decimal.Zero; + TaxRates[taxRate].PaymentFeeAmount += paymentFeeAmount; } - HasChanged = true; + + HasChanges = true; } /// /// /// - /// Total Amount of Attributes + /// Total Amount of Attributes /// dictionary of taxRate weights on that amount - public void AddAttributeRate(decimal attribAmount, SortedDictionary attributeTaxWeight) + public void AddAttributeRate(decimal amount, SortedDictionary attributeTaxWeight) { - var dummyResult = ParseOrAddAttributeRate(attribAmount, attributeTaxWeight, doAdd: true); + var dummyResult = ApplyOrAddAttributeRate(amount, attributeTaxWeight, doAdd: true); } - - public SortedDictionary ParseAttributeRate(decimal attribAmount, SortedDictionary attributeTaxWeight) + /// + /// Parses taxWeights and calculates respective amounts + /// + /// amount to weight + /// tax weights + /// SortedDictionary of weighted amounts + public SortedDictionary ApplyAttributeRate(decimal amount, SortedDictionary attributeTaxWeight) { - return ParseOrAddAttributeRate(attribAmount, attributeTaxWeight, doAdd: false); + return ApplyOrAddAttributeRate(amount, attributeTaxWeight, doAdd: false); } - public SortedDictionary ParseOrAddAttributeRate(decimal attribAmount, SortedDictionary attributeTaxWeight, bool doAdd = true) + #endregion + + protected SortedDictionary ApplyOrAddAttributeRate(decimal amount, SortedDictionary attributeTaxWeight, bool doAdd = true) { var result = new SortedDictionary(); int i = 0; - int c = attributeTaxWeight.Count(); - decimal reminder = attribAmount; + var c = attributeTaxWeight.Count(); + decimal reminder = amount; foreach (KeyValuePair kvp in attributeTaxWeight) { i += 1; decimal vatpercentage = kvp.Key; decimal rateWeight = kvp.Value; - var attribAmountWeighted = RoundingHelper.RoundAmount(attribAmount * rateWeight); + var amountWeighted = RoundingHelper.RoundAmount(amount * rateWeight); if (i < c) { if (doAdd) - AddRate(vatpercentage, attribAmountWeighted); + AddRate(vatpercentage, amountWeighted); else - result.Add(kvp.Key, attribAmountWeighted); - reminder -= attribAmountWeighted; + result.Add(vatpercentage, amountWeighted); + reminder -= amountWeighted; } else { + reminder = RoundingHelper.RoundAmount(reminder); if (doAdd) AddRate(vatpercentage, reminder); else - result.Add(kvp.Key, reminder); + result.Add(vatpercentage, reminder); } } return result; } - public void SetSubtotalDiscAmount(decimal totalSubTotalDiscAmount, decimal totalAmount = 0) + + #region discount percentages + /// + /// Set subtotal discount amount. Converted internally to percentage + /// + /// Subtotal discount amount + /// Base amount to which apply discount + public void SetSubtotalDiscAmount(decimal totalSubTotalDiscAmount, decimal baseAmount = 0) + { + totalSubTotalDiscAmount = RoundingHelper.RoundAmount(totalSubTotalDiscAmount); + if (baseAmount == decimal.Zero) + baseAmount = TaxRates.Sum(x => x.Value.SubtotalAmount); + + totalSubTotalDiscAmount = Math.Min(totalSubTotalDiscAmount, baseAmount); + PercentageSubTotalDiscount = CalcDiscountPercentage(ref totalSubTotalDiscAmount, baseAmount); + _totalSubTotalDiscAmount = totalSubTotalDiscAmount; + + HasChanges = true; + } + + /// + /// Set redeemed reward points amount. Converted internally to a percentage + /// + /// Redeemed reward points amount + /// Base amount to which apply discount + public void SetRewardPointsDiscAmount(decimal totalRewardPointsAmount, decimal baseAmount = 0) { - if (totalAmount == 0) - totalAmount = TaxRates.Sum(x => x.Value.SubtotalAmount); - if (totalSubTotalDiscAmount > totalAmount) - totalSubTotalDiscAmount = totalAmount; - - if (totalAmount != decimal.Zero) - PercentageSubTotalDiscount = totalSubTotalDiscAmount / totalAmount * 100; - HasChanged = true; + totalRewardPointsAmount = RoundingHelper.RoundAmount(totalRewardPointsAmount); + totalRewardPointsAmount = Math.Min(totalRewardPointsAmount, baseAmount); + + PercentageRewardPointsDiscount = CalcDiscountPercentage(ref totalRewardPointsAmount, baseAmount); + _totalRewardPointsAmountTaxable = totalRewardPointsAmount; + + HasChanges = true; } + /// - /// set PaymentFee as discount (used for taxable fee as discount or surcharge) + /// Set PaymentFee as discount (negative amount) or surcharge (positive amount) (used for taxable fee as discount or surcharge) /// - /// Fee Amount - /// Base amount to which apply fee - public void SetPaymentFeeDiscAmount(decimal totalPaymentFeeDiscAmount, decimal totalAmount) + /// Fee Amount + /// Base amount to which apply discount fee + public void SetPaymentFeeOrDiscAmount(decimal totalPaymentFeeOrDiscAmount, decimal baseAmount) { - PercentagePaymentFeeDiscount = totalPaymentFeeDiscAmount / totalAmount * 100; - HasChanged = true; + totalPaymentFeeOrDiscAmount = RoundingHelper.RoundAmount(totalPaymentFeeOrDiscAmount); + totalPaymentFeeOrDiscAmount = Math.Min(totalPaymentFeeOrDiscAmount, baseAmount); + + //totalPaymentFeeDiscAmount is passed as negative number + var sign = Math.Sign(totalPaymentFeeOrDiscAmount); + totalPaymentFeeOrDiscAmount = Math.Abs(totalPaymentFeeOrDiscAmount); + _totalPaymentFeeAmount = decimal.Zero; + _totalPaymentFeeDiscAmount = decimal.Zero; + + PercentagePaymentFeeOrDiscount = CalcDiscountPercentage(ref totalPaymentFeeOrDiscAmount, baseAmount); + if (sign == -1) + _totalPaymentFeeDiscAmount = totalPaymentFeeOrDiscAmount; + else + _totalPaymentFeeAmount = totalPaymentFeeOrDiscAmount; + + HasChanges = true; } /// /// Set total invoice discount amount, it will be converted to an internal discount percentage /// /// invoice discount amount - public void SetTotalInvDiscAmount(decimal totalInvDiscAmount, decimal totalAmount) + /// Base amount to which apply discount + public void SetTotalInvDiscAmount(decimal totalInvDiscAmount, decimal baseAmount) { - if (totalInvDiscAmount > totalAmount) - totalInvDiscAmount = totalAmount; + totalInvDiscAmount = RoundingHelper.RoundAmount(totalInvDiscAmount); + totalInvDiscAmount = Math.Min(totalInvDiscAmount, baseAmount); + + PercentageInvDiscount = CalcDiscountPercentage(ref totalInvDiscAmount, baseAmount); + _totalInvDiscAmount = totalInvDiscAmount; - PercentageInvDiscount = totalInvDiscAmount / totalAmount * 100; - HasChanged = true; + HasChanges = true; } + #endregion + /// - /// calculate amounts and VAT + /// Calculate amounts and tax /// - public void CalculateAmounts() + private void CalculateAmounts() { - if (!HasChanged) + if (!HasChanges) return; //reset totals - TotalShippingAmount = decimal.Zero; - TotalPaymentFeeAmount = decimal.Zero; - TotalAmount = decimal.Zero; - TotalAmountVAT = decimal.Zero; - TotalAmountIncludingVAT = decimal.Zero; - TotalSubTotalAmount = decimal.Zero; - TotalSubTotalDiscAmount = decimal.Zero; - TotalInvDiscAmount = decimal.Zero; - HasChanged = false; + _totalSubTotalAmount = decimal.Zero; + if (HasShipping) + _totalShippingAmountTaxable = decimal.Zero; + else + _totalShippingAmountTaxable = null; + _totalAmount = decimal.Zero; + _totalAmountTax = decimal.Zero; + _totalAmountIncludingTax = decimal.Zero; + + HasChanges = false; + + //payment fee with tax + if (HasPaymentFeeTax) + { + _totalPaymentFeeAmount = decimal.Zero; + _totalPaymentFeeDiscAmount = decimal.Zero; + PercentagePaymentFeeOrDiscount = decimal.Zero; + } //init remainder - decimal remainderSubTotalDisc = decimal.Zero; - decimal remainderPaymentFeeDisc = decimal.Zero; - decimal remainderInvDisc = decimal.Zero; + decimal remainderSubTotalDisc = decimal.Zero; decimal sumSubTotalDisc = decimal.Zero; + decimal remainderInvDisc = decimal.Zero; decimal sumInvDisc = decimal.Zero; + decimal remainderRewardPointsDisc = decimal.Zero; decimal sumRewardPointsDisc = decimal.Zero; + decimal remainderPaymentFeeOrDisc = decimal.Zero; decimal sumPaymentFeeOrDisc = decimal.Zero; + + var n = TaxRates.Count(); + var i = 0; //calc and sum up tax foreach (KeyValuePair kvp in TaxRates) { decimal vatpercentage = kvp.Key; TaxRateEntry taxrate = kvp.Value; + i += 1; - //discounts + //subtotal discount if (PercentageSubTotalDiscount != 0) { - remainderSubTotalDisc += taxrate.SubtotalAmount * PercentageSubTotalDiscount / 100; - taxrate.SubTotalDiscAmount = RoundingHelper.RoundAmount(remainderSubTotalDisc); - remainderSubTotalDisc -= taxrate.SubTotalDiscAmount; + if (i == n) + { + var diff = TotalSubTotalDiscAmount - sumSubTotalDisc; + taxrate.SubTotalDiscountAmount = diff; + } + else + { + var discountbase = taxrate.SubtotalAmount; + remainderSubTotalDisc += discountbase * PercentageSubTotalDiscount / 100; + taxrate.SubTotalDiscountAmount = RoundingHelper.RoundAmount(remainderSubTotalDisc); + remainderSubTotalDisc -= taxrate.SubTotalDiscountAmount; + sumSubTotalDisc += taxrate.SubTotalDiscountAmount; + } } - if (PercentagePaymentFeeDiscount != 0) + //Invoice discount is in sequence to other discounts, i.e. applied to already discounted amounts + if (PercentageInvDiscount != 0) { - remainderPaymentFeeDisc += (taxrate.SubtotalAmount + taxrate.ShippingAmount - taxrate.SubTotalDiscAmount) * PercentagePaymentFeeDiscount / 100; - taxrate.PaymentFeeAmount = RoundingHelper.RoundAmount(remainderPaymentFeeDisc); - remainderPaymentFeeDisc -= taxrate.PaymentFeeAmount; + if (i == n) + { + var diff = TotalInvDiscAmount - sumInvDisc; + taxrate.InvoiceDiscountAmount = diff; + } + else + { + var discountbase = taxrate.SubtotalAmount - taxrate.SubTotalDiscountAmount + taxrate.ShippingAmount; + remainderInvDisc += discountbase * PercentageInvDiscount / 100; + taxrate.InvoiceDiscountAmount = RoundingHelper.RoundAmount(remainderInvDisc); + remainderInvDisc -= taxrate.InvoiceDiscountAmount; + sumInvDisc += taxrate.InvoiceDiscountAmount; + } } - //Invoice discount is in sequence to other discounts, i.e. applied to already discounted amounts - if (PercentageInvDiscount != 0) + //Reward points are in sequence to other discounts, i.e. applied to already discounted amounts + if (PercentageRewardPointsDiscount != 0) { - remainderInvDisc += (taxrate.SubtotalAmount + taxrate.ShippingAmount + taxrate.PaymentFeeAmount - taxrate.SubTotalDiscAmount) - * PercentageInvDiscount / 100; - taxrate.InvoiceDiscountAmount = RoundingHelper.RoundAmount(remainderInvDisc); - remainderInvDisc -= taxrate.InvoiceDiscountAmount; + if (i == n) + { + var diff =TotalRewardPointsAmountTaxable - sumRewardPointsDisc; + taxrate.RewardPointsDiscountAmount = diff; + } + else + { + var discountbase = taxrate.SubtotalAmount - taxrate.SubTotalDiscountAmount + taxrate.ShippingAmount - taxrate.InvoiceDiscountAmount; + remainderRewardPointsDisc += discountbase * PercentageRewardPointsDiscount / 100; + taxrate.RewardPointsDiscountAmount = RoundingHelper.RoundAmount(remainderRewardPointsDisc); + remainderRewardPointsDisc -= taxrate.RewardPointsDiscountAmount; + sumRewardPointsDisc += taxrate.RewardPointsDiscountAmount; + } } - //last remainder get's lost as it can't be considered anywhere else. This has no implication and only lowers or highers discount. + //percentage payment fee discount. + if (PercentagePaymentFeeOrDiscount != 0) + { + if (i == n) + { + var diff = TotalPaymentFeeAmount + TotalPaymentFeeDiscAmount - sumPaymentFeeOrDisc; //TotalPaymentFeeAmount and TotalPaymentFeeDiscAmount are mutual zero + taxrate.PaymentFeeAmount = TotalPaymentFeeAmount != decimal.Zero ? diff : decimal.Zero; + taxrate.PaymentFeeDiscountAmount = TotalPaymentFeeDiscAmount != decimal.Zero ? diff : decimal.Zero; - taxrate.Amount = taxrate.SubtotalAmount + taxrate.ShippingAmount + taxrate.PaymentFeeAmount; - taxrate.DiscountAmount = taxrate.SubTotalDiscAmount + taxrate.InvoiceDiscountAmount; + } + else + { + var discountbase = taxrate.SubtotalAmount - taxrate.SubTotalDiscountAmount + taxrate.ShippingAmount - taxrate.InvoiceDiscountAmount - taxrate.RewardPointsDiscountAmount; + remainderPaymentFeeOrDisc += discountbase * PercentagePaymentFeeOrDiscount / 100; + taxrate.PaymentFeeAmount = decimal.Zero; taxrate.PaymentFeeDiscountAmount = decimal.Zero; + if (TotalPaymentFeeAmount != decimal.Zero) + taxrate.PaymentFeeAmount = RoundingHelper.RoundAmount(remainderPaymentFeeOrDisc); + else + taxrate.PaymentFeeDiscountAmount = RoundingHelper.RoundAmount(remainderPaymentFeeOrDisc); + remainderPaymentFeeOrDisc -= taxrate.PaymentFeeAmount + taxrate.PaymentFeeDiscountAmount; + sumPaymentFeeOrDisc += taxrate.PaymentFeeAmount + taxrate.PaymentFeeDiscountAmount; + } + } - //VAT: always round VAT first - decimal rateamount = taxrate.Amount - taxrate.DiscountAmount; + //TAX: always round tax first + decimal rateamount = taxrate.EntryAmount - taxrate.DiscountAmount; if (PricesIncludeTax) { - taxrate.AmountIncludingVAT = rateamount; - taxrate.VatAmount = RoundingHelper.RoundAmount(taxrate.AmountIncludingVAT / (100 + vatpercentage) * vatpercentage); // this is (1+p/100) * p/100 - taxrate.BaseAmount = taxrate.AmountIncludingVAT - taxrate.VatAmount; + taxrate.AmountIncludingTax = rateamount; + taxrate.TaxAmount = RoundingHelper.RoundTax(taxrate.AmountIncludingTax / (100 + vatpercentage) * vatpercentage); // this is (1+p/100) * p/100 + taxrate.BaseAmount = taxrate.AmountIncludingTax - taxrate.TaxAmount; } else { taxrate.BaseAmount = rateamount; - taxrate.VatAmount = RoundingHelper.RoundAmount(taxrate.BaseAmount * vatpercentage / 100); - taxrate.AmountIncludingVAT = taxrate.BaseAmount + taxrate.VatAmount; + taxrate.TaxAmount = RoundingHelper.RoundTax(taxrate.BaseAmount * vatpercentage / 100); + taxrate.AmountIncludingTax = taxrate.BaseAmount + taxrate.TaxAmount; } - //totals - TotalSubTotalAmount += taxrate.SubtotalAmount; - TotalShippingAmount += taxrate.ShippingAmount; - TotalPaymentFeeAmount += taxrate.PaymentFeeAmount; + //taxrate totals + _totalSubTotalAmount += taxrate.SubtotalAmount; + if (HasShipping) //to maintain null + _totalShippingAmountTaxable += taxrate.ShippingAmount; - if (PercentageSubTotalDiscount != 0) - TotalSubTotalDiscAmount += taxrate.SubTotalDiscAmount; - if (PercentageInvDiscount != 0) - TotalInvDiscAmount += taxrate.InvoiceDiscountAmount; + if (PercentagePaymentFeeOrDiscount == 0) //sum only when not set as discount or surcharge + _totalPaymentFeeAmount += taxrate.PaymentFeeAmount; - TotalAmount += taxrate.BaseAmount; - TotalAmountVAT += taxrate.VatAmount; - TotalAmountIncludingVAT += taxrate.AmountIncludingVAT; + _totalAmount += taxrate.BaseAmount; + _totalAmountTax += taxrate.TaxAmount; + _totalAmountIncludingTax += taxrate.AmountIncludingTax; } + } + /// + /// Calculate tax weights used in attributes + /// + public void CalculateWeights() + { + if (HasChanges) + CalculateAmounts(); + + //calculate tax weights int i = 0; int c = TaxRates.Count(); decimal totWeight = decimal.Zero; @@ -297,12 +588,20 @@ public void CalculateAmounts() TaxRateEntry taxrate = kvp.Value; if (i < c) { - taxrate.VatRateWeight = (this.PricesIncludeTax ? (taxrate.AmountIncludingVAT / this.TotalAmountIncludingVAT) : (taxrate.BaseAmount / this.TotalAmount)); - totWeight += taxrate.VatRateWeight; + if (this.TotalAmount != 0) + { + taxrate.TaxRateWeight = PricesIncludeTax ? taxrate.AmountIncludingTax / this.TotalAmountIncludingTax : taxrate.BaseAmount / this.TotalAmount; + } + else + { + taxrate.TaxRateWeight = 0; + } + totWeight += taxrate.TaxRateWeight; } else { - taxrate.VatRateWeight = decimal.One - totWeight; //assure sum of VatRateWeight = 1 + //assure sum of TaxRateWeight = 1 + taxrate.TaxRateWeight = decimal.One - totWeight; } } } diff --git a/src/Plugins/Nop.Plugin.Payments.PayPalDirect/PayPalDirectPaymentProcessor.cs b/src/Plugins/Nop.Plugin.Payments.PayPalDirect/PayPalDirectPaymentProcessor.cs index 9af41128d1a..c283d1ad282 100644 --- a/src/Plugins/Nop.Plugin.Payments.PayPalDirect/PayPalDirectPaymentProcessor.cs +++ b/src/Plugins/Nop.Plugin.Payments.PayPalDirect/PayPalDirectPaymentProcessor.cs @@ -328,10 +328,14 @@ protected Item CreateItemForTotalDiscount(IList shoppingCart) decimal discountAmount; List giftCards; List discounts; - int rewardPoints; - decimal rewardPointsAmount; + RewardPoints rewardPoints; + TaxSummary taxSummary; + List subTotalAppliedDiscounts; + List shippingAppliedDiscounts; + decimal earnedRewardPointsBaseAmount; + var orderTotal = _orderTotalCalculationService.GetShoppingCartTotal(shoppingCart, out discountAmount, - out discounts, out giftCards, out rewardPoints, out rewardPointsAmount); + out discounts, out subTotalAppliedDiscounts, out shippingAppliedDiscounts, out giftCards, out rewardPoints, out taxSummary, out earnedRewardPointsBaseAmount); if (discountAmount <= decimal.Zero) return null; diff --git a/src/Plugins/Nop.Plugin.Widgets.GoogleAnalytics/Controllers/WidgetsGoogleAnalyticsController.cs b/src/Plugins/Nop.Plugin.Widgets.GoogleAnalytics/Controllers/WidgetsGoogleAnalyticsController.cs index 1f594580387..ca5518b8f25 100644 --- a/src/Plugins/Nop.Plugin.Widgets.GoogleAnalytics/Controllers/WidgetsGoogleAnalyticsController.cs +++ b/src/Plugins/Nop.Plugin.Widgets.GoogleAnalytics/Controllers/WidgetsGoogleAnalyticsController.cs @@ -1,264 +1,264 @@ -using System; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Web.Mvc; -using Nop.Core; -using Nop.Core.Domain.Orders; -using Nop.Plugin.Widgets.GoogleAnalytics.Models; -using Nop.Services.Catalog; -using Nop.Services.Common; -using Nop.Services.Configuration; -using Nop.Services.Localization; -using Nop.Services.Logging; -using Nop.Services.Orders; -using Nop.Services.Stores; -using Nop.Web.Framework.Controllers; - -namespace Nop.Plugin.Widgets.GoogleAnalytics.Controllers -{ - public class WidgetsGoogleAnalyticsController : BasePluginController - { - private const string ORDER_ALREADY_PROCESSED_ATTRIBUTE_NAME = "GoogleAnalytics.OrderAlreadyProcessed"; - private readonly IWorkContext _workContext; - private readonly IStoreContext _storeContext; - private readonly IStoreService _storeService; - private readonly ISettingService _settingService; - private readonly IOrderService _orderService; - private readonly ILogger _logger; - private readonly ICategoryService _categoryService; - private readonly IProductAttributeParser _productAttributeParser; - private readonly ILocalizationService _localizationService; - private readonly IGenericAttributeService _genericAttributeService; - - public WidgetsGoogleAnalyticsController(IWorkContext workContext, - IStoreContext storeContext, - IStoreService storeService, - ISettingService settingService, - IOrderService orderService, - ILogger logger, - ICategoryService categoryService, - IProductAttributeParser productAttributeParser, - ILocalizationService localizationService, - IGenericAttributeService genericAttributeService) - { - this._workContext = workContext; - this._storeContext = storeContext; - this._storeService = storeService; - this._settingService = settingService; - this._orderService = orderService; - this._logger = logger; - this._categoryService = categoryService; - this._productAttributeParser = productAttributeParser; - this._localizationService = localizationService; - this._genericAttributeService = genericAttributeService; - } - - [AdminAuthorize] - [ChildActionOnly] - public ActionResult Configure() - { - //load settings for a chosen store scope - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); - var googleAnalyticsSettings = _settingService.LoadSetting(storeScope); - var model = new ConfigurationModel(); - model.GoogleId = googleAnalyticsSettings.GoogleId; - model.TrackingScript = googleAnalyticsSettings.TrackingScript; - model.EcommerceScript = googleAnalyticsSettings.EcommerceScript; - model.EcommerceDetailScript = googleAnalyticsSettings.EcommerceDetailScript; - model.IncludingTax = googleAnalyticsSettings.IncludingTax; - model.ZoneId = googleAnalyticsSettings.WidgetZone; - model.AvailableZones.Add(new SelectListItem() { Text = "Before body end html tag", Value = "body_end_html_tag_before" }); - model.AvailableZones.Add(new SelectListItem() { Text = "Head html tag", Value = "head_html_tag" }); - - model.ActiveStoreScopeConfiguration = storeScope; - if (storeScope > 0) - { - model.GoogleId_OverrideForStore = _settingService.SettingExists(googleAnalyticsSettings, x => x.GoogleId, storeScope); - model.TrackingScript_OverrideForStore = _settingService.SettingExists(googleAnalyticsSettings, x => x.TrackingScript, storeScope); - model.EcommerceScript_OverrideForStore = _settingService.SettingExists(googleAnalyticsSettings, x => x.EcommerceScript, storeScope); - model.EcommerceDetailScript_OverrideForStore = _settingService.SettingExists(googleAnalyticsSettings, x => x.EcommerceDetailScript, storeScope); - model.IncludingTax_OverrideForStore = _settingService.SettingExists(googleAnalyticsSettings, x => x.IncludingTax, storeScope); - model.ZoneId_OverrideForStore = _settingService.SettingExists(googleAnalyticsSettings, x => x.WidgetZone, storeScope); - } - - return View("~/Plugins/Widgets.GoogleAnalytics/Views/Configure.cshtml", model); - } - - [HttpPost] - [AdminAuthorize] - [ChildActionOnly] - public ActionResult Configure(ConfigurationModel model) - { - //load settings for a chosen store scope - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); - var googleAnalyticsSettings = _settingService.LoadSetting(storeScope); - googleAnalyticsSettings.GoogleId = model.GoogleId; - googleAnalyticsSettings.TrackingScript = model.TrackingScript; - googleAnalyticsSettings.EcommerceScript = model.EcommerceScript; - googleAnalyticsSettings.EcommerceDetailScript = model.EcommerceDetailScript; - googleAnalyticsSettings.IncludingTax = model.IncludingTax; - googleAnalyticsSettings.WidgetZone = model.ZoneId; - - /* 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 */ - _settingService.SaveSettingOverridablePerStore(googleAnalyticsSettings, x => x.GoogleId, model.GoogleId_OverrideForStore, storeScope, false); - _settingService.SaveSettingOverridablePerStore(googleAnalyticsSettings, x => x.TrackingScript, model.TrackingScript_OverrideForStore, storeScope, false); - _settingService.SaveSettingOverridablePerStore(googleAnalyticsSettings, x => x.EcommerceScript, model.EcommerceScript_OverrideForStore, storeScope, false); - _settingService.SaveSettingOverridablePerStore(googleAnalyticsSettings, x => x.EcommerceDetailScript, model.EcommerceDetailScript_OverrideForStore, storeScope, false); - _settingService.SaveSettingOverridablePerStore(googleAnalyticsSettings, x => x.IncludingTax, model.IncludingTax_OverrideForStore, storeScope, false); - _settingService.SaveSettingOverridablePerStore(googleAnalyticsSettings, x => x.WidgetZone, model.ZoneId_OverrideForStore, storeScope, false); - - //now clear settings cache - _settingService.ClearCache(); - - SuccessNotification(_localizationService.GetResource("Admin.Plugins.Saved")); - - return Configure(); - } - - [ChildActionOnly] - public ActionResult PublicInfo(string widgetZone, object additionalData = null) - { - string globalScript = ""; - var routeData = ((System.Web.UI.Page)this.HttpContext.CurrentHandler).RouteData; - - try - { - var controller = routeData.Values["controller"]; - var action = routeData.Values["action"]; - - if (controller == null || action == null) - return Content(""); - - //Special case, if we are in last step of checkout, we can use order total for conversion value - if (controller.ToString().Equals("checkout", StringComparison.InvariantCultureIgnoreCase) && - action.ToString().Equals("completed", StringComparison.InvariantCultureIgnoreCase)) - { - var lastOrder = GetLastOrder(); - globalScript += GetEcommerceScript(lastOrder); - } - else - { - globalScript += GetEcommerceScript(null); - } - } - catch (Exception ex) - { - _logger.InsertLog(Core.Domain.Logging.LogLevel.Error, "Error creating scripts for google ecommerce tracking", ex.ToString()); - } - return Content(globalScript); - } - - private Order GetLastOrder() - { - var order = _orderService.SearchOrders(storeId: _storeContext.CurrentStore.Id, - customerId: _workContext.CurrentCustomer.Id, pageSize: 1).FirstOrDefault(); - return order; - } - - // - private string GetEcommerceScript(Order order) - { - var googleAnalyticsSettings = _settingService.LoadSetting(_storeContext.CurrentStore.Id); - var analyticsTrackingScript = googleAnalyticsSettings.TrackingScript + "\n"; - analyticsTrackingScript = analyticsTrackingScript.Replace("{GOOGLEID}", googleAnalyticsSettings.GoogleId); - - //ensure that ecommerce tracking code is renderred only once (avoid duplicated data in Google Analytics) - if (order != null && !order.GetAttribute(ORDER_ALREADY_PROCESSED_ATTRIBUTE_NAME)) - { - var usCulture = new CultureInfo("en-US"); - - var analyticsEcommerceScript = googleAnalyticsSettings.EcommerceScript + "\n"; - analyticsEcommerceScript = analyticsEcommerceScript.Replace("{GOOGLEID}", googleAnalyticsSettings.GoogleId); - analyticsEcommerceScript = analyticsEcommerceScript.Replace("{ORDERID}", order.Id.ToString()); - analyticsEcommerceScript = analyticsEcommerceScript.Replace("{SITE}", _storeContext.CurrentStore.Url.Replace("http://", "").Replace("/", "")); - analyticsEcommerceScript = analyticsEcommerceScript.Replace("{TOTAL}", order.OrderTotal.ToString("0.00", usCulture)); - analyticsEcommerceScript = analyticsEcommerceScript.Replace("{TAX}", order.OrderTax.ToString("0.00", usCulture)); - var orderShipping = googleAnalyticsSettings.IncludingTax ? order.OrderShippingInclTax : order.OrderShippingExclTax; - analyticsEcommerceScript = analyticsEcommerceScript.Replace("{SHIP}", orderShipping.ToString("0.00", usCulture)); - analyticsEcommerceScript = analyticsEcommerceScript.Replace("{CITY}", order.BillingAddress == null ? "" : FixIllegalJavaScriptChars(order.BillingAddress.City)); - analyticsEcommerceScript = analyticsEcommerceScript.Replace("{STATEPROVINCE}", order.BillingAddress == null || order.BillingAddress.StateProvince == null ? "" : FixIllegalJavaScriptChars(order.BillingAddress.StateProvince.Name)); - analyticsEcommerceScript = analyticsEcommerceScript.Replace("{COUNTRY}", order.BillingAddress == null || order.BillingAddress.Country == null ? "" : FixIllegalJavaScriptChars(order.BillingAddress.Country.Name)); - - var sb = new StringBuilder(); - foreach (var item in order.OrderItems) - { - string analyticsEcommerceDetailScript = googleAnalyticsSettings.EcommerceDetailScript; - //get category - string category = ""; - var defaultProductCategory = _categoryService.GetProductCategoriesByProductId(item.ProductId).FirstOrDefault(); - if (defaultProductCategory != null) - category = defaultProductCategory.Category.Name; - analyticsEcommerceDetailScript = analyticsEcommerceDetailScript.Replace("{ORDERID}", item.OrderId.ToString()); - //The SKU code is a required parameter for every item that is added to the transaction - analyticsEcommerceDetailScript = analyticsEcommerceDetailScript.Replace("{PRODUCTSKU}", FixIllegalJavaScriptChars(item.Product.FormatSku(item.AttributesXml, _productAttributeParser))); - analyticsEcommerceDetailScript = analyticsEcommerceDetailScript.Replace("{PRODUCTNAME}", FixIllegalJavaScriptChars(item.Product.Name)); - analyticsEcommerceDetailScript = analyticsEcommerceDetailScript.Replace("{CATEGORYNAME}", FixIllegalJavaScriptChars(category)); - var unitPrice = googleAnalyticsSettings.IncludingTax ? item.UnitPriceInclTax : item.UnitPriceExclTax; - analyticsEcommerceDetailScript = analyticsEcommerceDetailScript.Replace("{UNITPRICE}", unitPrice.ToString("0.00", usCulture)); - analyticsEcommerceDetailScript = analyticsEcommerceDetailScript.Replace("{QUANTITY}", item.Quantity.ToString()); - sb.AppendLine(analyticsEcommerceDetailScript); - } - - analyticsEcommerceScript = analyticsEcommerceScript.Replace("{DETAILS}", sb.ToString()); - - analyticsTrackingScript = analyticsTrackingScript.Replace("{ECOMMERCE}", analyticsEcommerceScript); - - _genericAttributeService.SaveAttribute(order, ORDER_ALREADY_PROCESSED_ATTRIBUTE_NAME, true); - } - else - { - analyticsTrackingScript = analyticsTrackingScript.Replace("{ECOMMERCE}", ""); - } - - return analyticsTrackingScript; - } - - private string FixIllegalJavaScriptChars(string text) - { - if (String.IsNullOrEmpty(text)) - return text; - - //replace ' with \' (http://stackoverflow.com/questions/4292761/need-to-url-encode-labels-when-tracking-events-with-google-analytics) - text = text.Replace("'", "\\'"); - return text; - } - } +using System; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Web.Mvc; +using Nop.Core; +using Nop.Core.Domain.Orders; +using Nop.Plugin.Widgets.GoogleAnalytics.Models; +using Nop.Services.Catalog; +using Nop.Services.Common; +using Nop.Services.Configuration; +using Nop.Services.Localization; +using Nop.Services.Logging; +using Nop.Services.Orders; +using Nop.Services.Stores; +using Nop.Web.Framework.Controllers; + +namespace Nop.Plugin.Widgets.GoogleAnalytics.Controllers +{ + public class WidgetsGoogleAnalyticsController : BasePluginController + { + private const string ORDER_ALREADY_PROCESSED_ATTRIBUTE_NAME = "GoogleAnalytics.OrderAlreadyProcessed"; + private readonly IWorkContext _workContext; + private readonly IStoreContext _storeContext; + private readonly IStoreService _storeService; + private readonly ISettingService _settingService; + private readonly IOrderService _orderService; + private readonly ILogger _logger; + private readonly ICategoryService _categoryService; + private readonly IProductAttributeParser _productAttributeParser; + private readonly ILocalizationService _localizationService; + private readonly IGenericAttributeService _genericAttributeService; + + public WidgetsGoogleAnalyticsController(IWorkContext workContext, + IStoreContext storeContext, + IStoreService storeService, + ISettingService settingService, + IOrderService orderService, + ILogger logger, + ICategoryService categoryService, + IProductAttributeParser productAttributeParser, + ILocalizationService localizationService, + IGenericAttributeService genericAttributeService) + { + this._workContext = workContext; + this._storeContext = storeContext; + this._storeService = storeService; + this._settingService = settingService; + this._orderService = orderService; + this._logger = logger; + this._categoryService = categoryService; + this._productAttributeParser = productAttributeParser; + this._localizationService = localizationService; + this._genericAttributeService = genericAttributeService; + } + + [AdminAuthorize] + [ChildActionOnly] + public ActionResult Configure() + { + //load settings for a chosen store scope + var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); + var googleAnalyticsSettings = _settingService.LoadSetting(storeScope); + var model = new ConfigurationModel(); + model.GoogleId = googleAnalyticsSettings.GoogleId; + model.TrackingScript = googleAnalyticsSettings.TrackingScript; + model.EcommerceScript = googleAnalyticsSettings.EcommerceScript; + model.EcommerceDetailScript = googleAnalyticsSettings.EcommerceDetailScript; + model.IncludingTax = googleAnalyticsSettings.IncludingTax; + model.ZoneId = googleAnalyticsSettings.WidgetZone; + model.AvailableZones.Add(new SelectListItem() { Text = "Before body end html tag", Value = "body_end_html_tag_before" }); + model.AvailableZones.Add(new SelectListItem() { Text = "Head html tag", Value = "head_html_tag" }); + + model.ActiveStoreScopeConfiguration = storeScope; + if (storeScope > 0) + { + model.GoogleId_OverrideForStore = _settingService.SettingExists(googleAnalyticsSettings, x => x.GoogleId, storeScope); + model.TrackingScript_OverrideForStore = _settingService.SettingExists(googleAnalyticsSettings, x => x.TrackingScript, storeScope); + model.EcommerceScript_OverrideForStore = _settingService.SettingExists(googleAnalyticsSettings, x => x.EcommerceScript, storeScope); + model.EcommerceDetailScript_OverrideForStore = _settingService.SettingExists(googleAnalyticsSettings, x => x.EcommerceDetailScript, storeScope); + model.IncludingTax_OverrideForStore = _settingService.SettingExists(googleAnalyticsSettings, x => x.IncludingTax, storeScope); + model.ZoneId_OverrideForStore = _settingService.SettingExists(googleAnalyticsSettings, x => x.WidgetZone, storeScope); + } + + return View("~/Plugins/Widgets.GoogleAnalytics/Views/Configure.cshtml", model); + } + + [HttpPost] + [AdminAuthorize] + [ChildActionOnly] + public ActionResult Configure(ConfigurationModel model) + { + //load settings for a chosen store scope + var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); + var googleAnalyticsSettings = _settingService.LoadSetting(storeScope); + googleAnalyticsSettings.GoogleId = model.GoogleId; + googleAnalyticsSettings.TrackingScript = model.TrackingScript; + googleAnalyticsSettings.EcommerceScript = model.EcommerceScript; + googleAnalyticsSettings.EcommerceDetailScript = model.EcommerceDetailScript; + googleAnalyticsSettings.IncludingTax = model.IncludingTax; + googleAnalyticsSettings.WidgetZone = model.ZoneId; + + /* 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 */ + _settingService.SaveSettingOverridablePerStore(googleAnalyticsSettings, x => x.GoogleId, model.GoogleId_OverrideForStore, storeScope, false); + _settingService.SaveSettingOverridablePerStore(googleAnalyticsSettings, x => x.TrackingScript, model.TrackingScript_OverrideForStore, storeScope, false); + _settingService.SaveSettingOverridablePerStore(googleAnalyticsSettings, x => x.EcommerceScript, model.EcommerceScript_OverrideForStore, storeScope, false); + _settingService.SaveSettingOverridablePerStore(googleAnalyticsSettings, x => x.EcommerceDetailScript, model.EcommerceDetailScript_OverrideForStore, storeScope, false); + _settingService.SaveSettingOverridablePerStore(googleAnalyticsSettings, x => x.IncludingTax, model.IncludingTax_OverrideForStore, storeScope, false); + _settingService.SaveSettingOverridablePerStore(googleAnalyticsSettings, x => x.WidgetZone, model.ZoneId_OverrideForStore, storeScope, false); + + //now clear settings cache + _settingService.ClearCache(); + + SuccessNotification(_localizationService.GetResource("Admin.Plugins.Saved")); + + return Configure(); + } + + [ChildActionOnly] + public ActionResult PublicInfo(string widgetZone, object additionalData = null) + { + string globalScript = ""; + var routeData = ((System.Web.UI.Page)this.HttpContext.CurrentHandler).RouteData; + + try + { + var controller = routeData.Values["controller"]; + var action = routeData.Values["action"]; + + if (controller == null || action == null) + return Content(""); + + //Special case, if we are in last step of checkout, we can use order total for conversion value + if (controller.ToString().Equals("checkout", StringComparison.InvariantCultureIgnoreCase) && + action.ToString().Equals("completed", StringComparison.InvariantCultureIgnoreCase)) + { + var lastOrder = GetLastOrder(); + globalScript += GetEcommerceScript(lastOrder); + } + else + { + globalScript += GetEcommerceScript(null); + } + } + catch (Exception ex) + { + _logger.InsertLog(Core.Domain.Logging.LogLevel.Error, "Error creating scripts for google ecommerce tracking", ex.ToString()); + } + return Content(globalScript); + } + + private Order GetLastOrder() + { + var order = _orderService.SearchOrders(storeId: _storeContext.CurrentStore.Id, + customerId: _workContext.CurrentCustomer.Id, pageSize: 1).FirstOrDefault(); + return order; + } + + // + private string GetEcommerceScript(Order order) + { + var googleAnalyticsSettings = _settingService.LoadSetting(_storeContext.CurrentStore.Id); + var analyticsTrackingScript = googleAnalyticsSettings.TrackingScript + "\n"; + analyticsTrackingScript = analyticsTrackingScript.Replace("{GOOGLEID}", googleAnalyticsSettings.GoogleId); + + //ensure that ecommerce tracking code is renderred only once (avoid duplicated data in Google Analytics) + if (order != null && !order.GetAttribute(ORDER_ALREADY_PROCESSED_ATTRIBUTE_NAME)) + { + var usCulture = new CultureInfo("en-US"); + + var analyticsEcommerceScript = googleAnalyticsSettings.EcommerceScript + "\n"; + analyticsEcommerceScript = analyticsEcommerceScript.Replace("{GOOGLEID}", googleAnalyticsSettings.GoogleId); + analyticsEcommerceScript = analyticsEcommerceScript.Replace("{ORDERID}", order.Id.ToString()); + analyticsEcommerceScript = analyticsEcommerceScript.Replace("{SITE}", _storeContext.CurrentStore.Url.Replace("http://", "").Replace("/", "")); + analyticsEcommerceScript = analyticsEcommerceScript.Replace("{TOTAL}", order.OrderTotal.ToString("0.00", usCulture)); + analyticsEcommerceScript = analyticsEcommerceScript.Replace("{TAX}", order.OrderTax.ToString("0.00", usCulture)); + var orderShipping = (googleAnalyticsSettings.IncludingTax ? order.OrderShippingInclTax : order.OrderShippingExclTax) + order.OrderShippingNonTaxable; + analyticsEcommerceScript = analyticsEcommerceScript.Replace("{SHIP}", orderShipping.ToString("0.00", usCulture)); + analyticsEcommerceScript = analyticsEcommerceScript.Replace("{CITY}", order.BillingAddress == null ? "" : FixIllegalJavaScriptChars(order.BillingAddress.City)); + analyticsEcommerceScript = analyticsEcommerceScript.Replace("{STATEPROVINCE}", order.BillingAddress == null || order.BillingAddress.StateProvince == null ? "" : FixIllegalJavaScriptChars(order.BillingAddress.StateProvince.Name)); + analyticsEcommerceScript = analyticsEcommerceScript.Replace("{COUNTRY}", order.BillingAddress == null || order.BillingAddress.Country == null ? "" : FixIllegalJavaScriptChars(order.BillingAddress.Country.Name)); + + var sb = new StringBuilder(); + foreach (var item in order.OrderItems) + { + string analyticsEcommerceDetailScript = googleAnalyticsSettings.EcommerceDetailScript; + //get category + string category = ""; + var defaultProductCategory = _categoryService.GetProductCategoriesByProductId(item.ProductId).FirstOrDefault(); + if (defaultProductCategory != null) + category = defaultProductCategory.Category.Name; + analyticsEcommerceDetailScript = analyticsEcommerceDetailScript.Replace("{ORDERID}", item.OrderId.ToString()); + //The SKU code is a required parameter for every item that is added to the transaction + analyticsEcommerceDetailScript = analyticsEcommerceDetailScript.Replace("{PRODUCTSKU}", FixIllegalJavaScriptChars(item.Product.FormatSku(item.AttributesXml, _productAttributeParser))); + analyticsEcommerceDetailScript = analyticsEcommerceDetailScript.Replace("{PRODUCTNAME}", FixIllegalJavaScriptChars(item.Product.Name)); + analyticsEcommerceDetailScript = analyticsEcommerceDetailScript.Replace("{CATEGORYNAME}", FixIllegalJavaScriptChars(category)); + var unitPrice = googleAnalyticsSettings.IncludingTax ? item.UnitPriceInclTax : item.UnitPriceExclTax; + analyticsEcommerceDetailScript = analyticsEcommerceDetailScript.Replace("{UNITPRICE}", unitPrice.ToString("0.00", usCulture)); + analyticsEcommerceDetailScript = analyticsEcommerceDetailScript.Replace("{QUANTITY}", item.Quantity.ToString()); + sb.AppendLine(analyticsEcommerceDetailScript); + } + + analyticsEcommerceScript = analyticsEcommerceScript.Replace("{DETAILS}", sb.ToString()); + + analyticsTrackingScript = analyticsTrackingScript.Replace("{ECOMMERCE}", analyticsEcommerceScript); + + _genericAttributeService.SaveAttribute(order, ORDER_ALREADY_PROCESSED_ATTRIBUTE_NAME, true); + } + else + { + analyticsTrackingScript = analyticsTrackingScript.Replace("{ECOMMERCE}", ""); + } + + return analyticsTrackingScript; + } + + private string FixIllegalJavaScriptChars(string text) + { + if (String.IsNullOrEmpty(text)) + return text; + + //replace ' with \' (http://stackoverflow.com/questions/4292761/need-to-url-encode-labels-when-tracking-events-with-google-analytics) + text = text.Replace("'", "\\'"); + return text; + } + } } \ No newline at end of file diff --git a/src/Presentation/Nop.Web/Administration/Controllers/AffiliateController.cs b/src/Presentation/Nop.Web/Administration/Controllers/AffiliateController.cs index 8d5726c3ca8..c587cf1d1b4 100644 --- a/src/Presentation/Nop.Web/Administration/Controllers/AffiliateController.cs +++ b/src/Presentation/Nop.Web/Administration/Controllers/AffiliateController.cs @@ -1,418 +1,419 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web.Mvc; -using Nop.Admin.Extensions; -using Nop.Admin.Models.Affiliates; -using Nop.Core; -using Nop.Core.Domain.Affiliates; -using Nop.Core.Domain.Directory; -using Nop.Core.Domain.Orders; -using Nop.Core.Domain.Payments; -using Nop.Core.Domain.Shipping; -using Nop.Services; -using Nop.Services.Affiliates; -using Nop.Services.Catalog; -using Nop.Services.Customers; -using Nop.Services.Directory; -using Nop.Services.Helpers; -using Nop.Services.Localization; -using Nop.Services.Logging; -using Nop.Services.Orders; -using Nop.Services.Security; -using Nop.Web.Framework.Controllers; -using Nop.Web.Framework.Kendoui; - -namespace Nop.Admin.Controllers -{ - public partial class AffiliateController : BaseAdminController - { - #region Fields - - private readonly ILocalizationService _localizationService; - private readonly IWorkContext _workContext; - private readonly IDateTimeHelper _dateTimeHelper; - private readonly IWebHelper _webHelper; - private readonly ICountryService _countryService; - private readonly IStateProvinceService _stateProvinceService; - private readonly IPriceFormatter _priceFormatter; - private readonly IAffiliateService _affiliateService; - private readonly ICustomerService _customerService; - private readonly IOrderService _orderService; - private readonly IPermissionService _permissionService; - private readonly ICustomerActivityService _customerActivityService; - - #endregion - - #region Constructors - - public AffiliateController(ILocalizationService localizationService, - IWorkContext workContext, IDateTimeHelper dateTimeHelper, IWebHelper webHelper, - ICountryService countryService, IStateProvinceService stateProvinceService, - IPriceFormatter priceFormatter, IAffiliateService affiliateService, - ICustomerService customerService, IOrderService orderService, - IPermissionService permissionService, - ICustomerActivityService customerActivityService) - { - this._localizationService = localizationService; - this._workContext = workContext; - this._dateTimeHelper = dateTimeHelper; - this._webHelper = webHelper; - this._countryService = countryService; - this._stateProvinceService = stateProvinceService; - this._priceFormatter = priceFormatter; - this._affiliateService = affiliateService; - this._customerService = customerService; - this._orderService = orderService; - this._permissionService = permissionService; - this._customerActivityService = customerActivityService; - } - - #endregion - - #region Utilities - - [NonAction] - protected virtual void PrepareAffiliateModel(AffiliateModel model, Affiliate affiliate, bool excludeProperties, - bool prepareEntireAddressModel = true) - { - if (model == null) - throw new ArgumentNullException("model"); - - if (affiliate != null) - { - model.Id = affiliate.Id; - model.Url = affiliate.GenerateUrl(_webHelper); - if (!excludeProperties) - { - model.AdminComment = affiliate.AdminComment; - model.FriendlyUrlName = affiliate.FriendlyUrlName; - model.Active = affiliate.Active; - model.Address = affiliate.Address.ToModel(); - } - } - - if (prepareEntireAddressModel) - { - model.Address.FirstNameEnabled = true; - model.Address.FirstNameRequired = true; - model.Address.LastNameEnabled = true; - model.Address.LastNameRequired = true; - model.Address.EmailEnabled = true; - model.Address.EmailRequired = true; - model.Address.CompanyEnabled = true; - model.Address.CountryEnabled = true; - model.Address.CountryRequired = true; - model.Address.StateProvinceEnabled = true; - model.Address.CityEnabled = true; - model.Address.CityRequired = true; - model.Address.StreetAddressEnabled = true; - model.Address.StreetAddressRequired = true; - model.Address.StreetAddress2Enabled = true; - model.Address.ZipPostalCodeEnabled = true; - model.Address.ZipPostalCodeRequired = true; - model.Address.PhoneEnabled = true; - model.Address.PhoneRequired = true; - model.Address.FaxEnabled = true; - - //address - model.Address.AvailableCountries.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.SelectCountry"), Value = "0" }); - foreach (var c in _countryService.GetAllCountries(showHidden: true)) - model.Address.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id.ToString(), Selected = (affiliate != null && c.Id == affiliate.Address.CountryId) }); - - var states = model.Address.CountryId.HasValue ? _stateProvinceService.GetStateProvincesByCountryId(model.Address.CountryId.Value, showHidden: true).ToList() : new List(); - if (states.Any()) - { - foreach (var s in states) - model.Address.AvailableStates.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString(), Selected = (affiliate != null && s.Id == affiliate.Address.StateProvinceId) }); - } - else - model.Address.AvailableStates.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.OtherNonUS"), Value = "0" }); - } - } - - #endregion - - #region Methods - - //list - public virtual ActionResult Index() - { - return RedirectToAction("List"); - } - - public virtual ActionResult List() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) - return AccessDeniedView(); - - var model = new AffiliateListModel(); - return View(model); - } - - [HttpPost] - public virtual ActionResult List(DataSourceRequest command, AffiliateListModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) - return AccessDeniedKendoGridJson(); - - var affiliates = _affiliateService.GetAllAffiliates(model.SearchFriendlyUrlName, - model.SearchFirstName, model.SearchLastName, - model.LoadOnlyWithOrders, model.OrdersCreatedFromUtc, model.OrdersCreatedToUtc, - command.Page - 1, command.PageSize, true); - - var gridModel = new DataSourceResult - { - Data = affiliates.Select(x => - { - var m = new AffiliateModel(); - PrepareAffiliateModel(m, x, false, false); - return m; - }), - Total = affiliates.TotalCount, - }; - return Json(gridModel); - } - - //create - public virtual ActionResult Create() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) - return AccessDeniedView(); - - var model = new AffiliateModel(); - PrepareAffiliateModel(model, null, false); - return View(model); - } - - [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] - [FormValueRequired("save", "save-continue")] - public virtual ActionResult Create(AffiliateModel model, bool continueEditing) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) - return AccessDeniedView(); - - if (ModelState.IsValid) - { - var affiliate = new Affiliate(); - - affiliate.Active = model.Active; - affiliate.AdminComment = model.AdminComment; - //validate friendly URL name - var friendlyUrlName = affiliate.ValidateFriendlyUrlName(model.FriendlyUrlName); - affiliate.FriendlyUrlName = friendlyUrlName; - affiliate.Address = model.Address.ToEntity(); - affiliate.Address.CreatedOnUtc = DateTime.UtcNow; - //some validation - if (affiliate.Address.CountryId == 0) - affiliate.Address.CountryId = null; - if (affiliate.Address.StateProvinceId == 0) - affiliate.Address.StateProvinceId = null; - _affiliateService.InsertAffiliate(affiliate); - - //activity log - _customerActivityService.InsertActivity("AddNewAffiliate", _localizationService.GetResource("ActivityLog.AddNewAffiliate"), affiliate.Id); - - SuccessNotification(_localizationService.GetResource("Admin.Affiliates.Added")); - return continueEditing ? RedirectToAction("Edit", new { id = affiliate.Id }) : RedirectToAction("List"); - } - - //If we got this far, something failed, redisplay form - PrepareAffiliateModel(model, null, true); - return View(model); - - } - - - //edit - public virtual ActionResult Edit(int id) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) - return AccessDeniedView(); - - var affiliate = _affiliateService.GetAffiliateById(id); - if (affiliate == null || affiliate.Deleted) - //No affiliate found with the specified id - return RedirectToAction("List"); - - var model = new AffiliateModel(); - PrepareAffiliateModel(model, affiliate, false); - return View(model); - } - - [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] - public virtual ActionResult Edit(AffiliateModel model, bool continueEditing) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) - return AccessDeniedView(); - - var affiliate = _affiliateService.GetAffiliateById(model.Id); - if (affiliate == null || affiliate.Deleted) - //No affiliate found with the specified id - return RedirectToAction("List"); - - if (ModelState.IsValid) - { - affiliate.Active = model.Active; - affiliate.AdminComment = model.AdminComment; - //validate friendly URL name - var friendlyUrlName = affiliate.ValidateFriendlyUrlName(model.FriendlyUrlName); - affiliate.FriendlyUrlName = friendlyUrlName; - affiliate.Address = model.Address.ToEntity(affiliate.Address); - //some validation - if (affiliate.Address.CountryId == 0) - affiliate.Address.CountryId = null; - if (affiliate.Address.StateProvinceId == 0) - affiliate.Address.StateProvinceId = null; - _affiliateService.UpdateAffiliate(affiliate); - - //activity log - _customerActivityService.InsertActivity("EditAffiliate", _localizationService.GetResource("ActivityLog.EditAffiliate"), affiliate.Id); - - SuccessNotification(_localizationService.GetResource("Admin.Affiliates.Updated")); - if (continueEditing) - { - //selected tab - SaveSelectedTabName(); - - return RedirectToAction("Edit", new {id = affiliate.Id}); - } - return RedirectToAction("List"); - } - - //If we got this far, something failed, redisplay form - PrepareAffiliateModel(model, affiliate, true); - return View(model); - } - - //delete - [HttpPost] - public virtual ActionResult Delete(int id) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) - return AccessDeniedView(); - - var affiliate = _affiliateService.GetAffiliateById(id); - if (affiliate == null) - //No affiliate found with the specified id - return RedirectToAction("List"); - - _affiliateService.DeleteAffiliate(affiliate); - - //activity log - _customerActivityService.InsertActivity("DeleteAffiliate", _localizationService.GetResource("ActivityLog.DeleteAffiliate"), affiliate.Id); - - SuccessNotification(_localizationService.GetResource("Admin.Affiliates.Deleted")); - return RedirectToAction("List"); - } - - [ChildActionOnly] - public virtual ActionResult AffiliatedOrderList(int affiliateId) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) - return Content(""); - - if (affiliateId == 0) - throw new Exception("Affliate ID cannot be 0"); - - var model = new AffiliatedOrderListModel(); - model.AffliateId = affiliateId; - - //order statuses - model.AvailableOrderStatuses = OrderStatus.Pending.ToSelectList(false).ToList(); - model.AvailableOrderStatuses.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - - //payment statuses - model.AvailablePaymentStatuses = PaymentStatus.Pending.ToSelectList(false).ToList(); - model.AvailablePaymentStatuses.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - - //shipping statuses - model.AvailableShippingStatuses = ShippingStatus.NotYetShipped.ToSelectList(false).ToList(); - model.AvailableShippingStatuses.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - - return PartialView(model); - } - [HttpPost] - public virtual ActionResult AffiliatedOrderListGrid(DataSourceRequest command, AffiliatedOrderListModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) - return AccessDeniedKendoGridJson(); - - var affiliate = _affiliateService.GetAffiliateById(model.AffliateId); - if (affiliate == null) - throw new ArgumentException("No affiliate found with the specified id"); - - DateTime? startDateValue = (model.StartDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); - - DateTime? endDateValue = (model.EndDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); - - var orderStatusIds = model.OrderStatusId > 0 ? new List() { model.OrderStatusId } : null; - var paymentStatusIds = model.PaymentStatusId > 0 ? new List() { model.PaymentStatusId } : null; - var shippingStatusIds = model.ShippingStatusId > 0 ? new List() { model.ShippingStatusId } : null; - - var orders = _orderService.SearchOrders( - createdFromUtc: startDateValue, - createdToUtc: endDateValue, - osIds: orderStatusIds, - psIds: paymentStatusIds, - ssIds: shippingStatusIds, - affiliateId: affiliate.Id, - pageIndex: command.Page - 1, - pageSize: command.PageSize); - var gridModel = new DataSourceResult - { - Data = orders.Select(order => - { - var orderModel = new AffiliateModel.AffiliatedOrderModel(); - orderModel.Id = order.Id; - orderModel.OrderStatus = order.OrderStatus.GetLocalizedEnum(_localizationService, _workContext); - orderModel.OrderStatusId = order.OrderStatusId; - orderModel.PaymentStatus = order.PaymentStatus.GetLocalizedEnum(_localizationService, _workContext); - orderModel.ShippingStatus = order.ShippingStatus.GetLocalizedEnum(_localizationService, _workContext); - orderModel.OrderTotal = _priceFormatter.FormatPrice(order.OrderTotal, true, false); - orderModel.CreatedOn = _dateTimeHelper.ConvertToUserTime(order.CreatedOnUtc, DateTimeKind.Utc); - orderModel.CustomOrderNumber = order.CustomOrderNumber; - - return orderModel; - }), - Total = orders.TotalCount - }; - - return Json(gridModel); - } - - - [HttpPost] - public virtual ActionResult AffiliatedCustomerList(int affiliateId, DataSourceRequest command) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) - return AccessDeniedKendoGridJson(); - - var affiliate = _affiliateService.GetAffiliateById(affiliateId); - if (affiliate == null) - throw new ArgumentException("No affiliate found with the specified id"); - - var customers = _customerService.GetAllCustomers( - affiliateId: affiliate.Id, - pageIndex: command.Page - 1, - pageSize: command.PageSize); - var gridModel = new DataSourceResult - { - Data = customers.Select(customer => - { - var customerModel = new AffiliateModel.AffiliatedCustomerModel(); - customerModel.Id = customer.Id; - customerModel.Name = customer.Email; - return customerModel; - }), - Total = customers.TotalCount - }; - - return Json(gridModel); - } - - #endregion - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web.Mvc; +using Nop.Admin.Extensions; +using Nop.Admin.Models.Affiliates; +using Nop.Core; +using Nop.Core.Domain.Affiliates; +using Nop.Core.Domain.Directory; +using Nop.Core.Domain.Orders; +using Nop.Core.Domain.Payments; +using Nop.Core.Domain.Shipping; +using Nop.Services; +using Nop.Services.Affiliates; +using Nop.Services.Catalog; +using Nop.Services.Customers; +using Nop.Services.Directory; +using Nop.Services.Helpers; +using Nop.Services.Localization; +using Nop.Services.Logging; +using Nop.Services.Orders; +using Nop.Services.Security; +using Nop.Web.Framework.Controllers; +using Nop.Web.Framework.Kendoui; + +namespace Nop.Admin.Controllers +{ + public partial class AffiliateController : BaseAdminController + { + #region Fields + + private readonly ILocalizationService _localizationService; + private readonly IWorkContext _workContext; + private readonly IDateTimeHelper _dateTimeHelper; + private readonly IWebHelper _webHelper; + private readonly ICountryService _countryService; + private readonly IStateProvinceService _stateProvinceService; + private readonly IPriceFormatter _priceFormatter; + private readonly IAffiliateService _affiliateService; + private readonly ICustomerService _customerService; + private readonly IOrderService _orderService; + private readonly IPermissionService _permissionService; + private readonly ICustomerActivityService _customerActivityService; + + #endregion + + #region Constructors + + public AffiliateController(ILocalizationService localizationService, + IWorkContext workContext, IDateTimeHelper dateTimeHelper, IWebHelper webHelper, + ICountryService countryService, IStateProvinceService stateProvinceService, + IPriceFormatter priceFormatter, IAffiliateService affiliateService, + ICustomerService customerService, IOrderService orderService, + IPermissionService permissionService, + ICustomerActivityService customerActivityService) + { + this._localizationService = localizationService; + this._workContext = workContext; + this._dateTimeHelper = dateTimeHelper; + this._webHelper = webHelper; + this._countryService = countryService; + this._stateProvinceService = stateProvinceService; + this._priceFormatter = priceFormatter; + this._affiliateService = affiliateService; + this._customerService = customerService; + this._orderService = orderService; + this._permissionService = permissionService; + this._customerActivityService = customerActivityService; + } + + #endregion + + #region Utilities + + [NonAction] + protected virtual void PrepareAffiliateModel(AffiliateModel model, Affiliate affiliate, bool excludeProperties, + bool prepareEntireAddressModel = true) + { + if (model == null) + throw new ArgumentNullException("model"); + + if (affiliate != null) + { + model.Id = affiliate.Id; + model.Url = affiliate.GenerateUrl(_webHelper); + if (!excludeProperties) + { + model.AdminComment = affiliate.AdminComment; + model.FriendlyUrlName = affiliate.FriendlyUrlName; + model.Active = affiliate.Active; + model.Address = affiliate.Address.ToModel(); + } + } + + if (prepareEntireAddressModel) + { + model.Address.FirstNameEnabled = true; + model.Address.FirstNameRequired = true; + model.Address.LastNameEnabled = true; + model.Address.LastNameRequired = true; + model.Address.EmailEnabled = true; + model.Address.EmailRequired = true; + model.Address.CompanyEnabled = true; + model.Address.CountryEnabled = true; + model.Address.CountryRequired = true; + model.Address.StateProvinceEnabled = true; + model.Address.StateProvinceRequired = true; + model.Address.CityEnabled = true; + model.Address.CityRequired = true; + model.Address.StreetAddressEnabled = true; + model.Address.StreetAddressRequired = true; + model.Address.StreetAddress2Enabled = true; + model.Address.ZipPostalCodeEnabled = true; + model.Address.ZipPostalCodeRequired = true; + model.Address.PhoneEnabled = true; + model.Address.PhoneRequired = true; + model.Address.FaxEnabled = true; + + //address + model.Address.AvailableCountries.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.SelectCountry"), Value = "0" }); + foreach (var c in _countryService.GetAllCountries(showHidden: true)) + model.Address.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id.ToString(), Selected = (affiliate != null && c.Id == affiliate.Address.CountryId) }); + + var states = model.Address.CountryId.HasValue ? _stateProvinceService.GetStateProvincesByCountryId(model.Address.CountryId.Value, showHidden: true).ToList() : new List(); + if (states.Any()) + { + foreach (var s in states) + model.Address.AvailableStates.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString(), Selected = (affiliate != null && s.Id == affiliate.Address.StateProvinceId) }); + } + else + model.Address.AvailableStates.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.OtherNonUS"), Value = "0" }); + } + } + + #endregion + + #region Methods + + //list + public virtual ActionResult Index() + { + return RedirectToAction("List"); + } + + public virtual ActionResult List() + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) + return AccessDeniedView(); + + var model = new AffiliateListModel(); + return View(model); + } + + [HttpPost] + public virtual ActionResult List(DataSourceRequest command, AffiliateListModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) + return AccessDeniedKendoGridJson(); + + var affiliates = _affiliateService.GetAllAffiliates(model.SearchFriendlyUrlName, + model.SearchFirstName, model.SearchLastName, + model.LoadOnlyWithOrders, model.OrdersCreatedFromUtc, model.OrdersCreatedToUtc, + command.Page - 1, command.PageSize, true); + + var gridModel = new DataSourceResult + { + Data = affiliates.Select(x => + { + var m = new AffiliateModel(); + PrepareAffiliateModel(m, x, false, false); + return m; + }), + Total = affiliates.TotalCount, + }; + return Json(gridModel); + } + + //create + public virtual ActionResult Create() + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) + return AccessDeniedView(); + + var model = new AffiliateModel(); + PrepareAffiliateModel(model, null, false); + return View(model); + } + + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] + [FormValueRequired("save", "save-continue")] + public virtual ActionResult Create(AffiliateModel model, bool continueEditing) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) + return AccessDeniedView(); + + if (ModelState.IsValid) + { + var affiliate = new Affiliate(); + + affiliate.Active = model.Active; + affiliate.AdminComment = model.AdminComment; + //validate friendly URL name + var friendlyUrlName = affiliate.ValidateFriendlyUrlName(model.FriendlyUrlName); + affiliate.FriendlyUrlName = friendlyUrlName; + affiliate.Address = model.Address.ToEntity(); + affiliate.Address.CreatedOnUtc = DateTime.UtcNow; + //some validation + if (affiliate.Address.CountryId == 0) + affiliate.Address.CountryId = null; + if (affiliate.Address.StateProvinceId == 0) + affiliate.Address.StateProvinceId = null; + _affiliateService.InsertAffiliate(affiliate); + + //activity log + _customerActivityService.InsertActivity("AddNewAffiliate", _localizationService.GetResource("ActivityLog.AddNewAffiliate"), affiliate.Id); + + SuccessNotification(_localizationService.GetResource("Admin.Affiliates.Added")); + return continueEditing ? RedirectToAction("Edit", new { id = affiliate.Id }) : RedirectToAction("List"); + } + + //If we got this far, something failed, redisplay form + PrepareAffiliateModel(model, null, true); + return View(model); + + } + + + //edit + public virtual ActionResult Edit(int id) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) + return AccessDeniedView(); + + var affiliate = _affiliateService.GetAffiliateById(id); + if (affiliate == null || affiliate.Deleted) + //No affiliate found with the specified id + return RedirectToAction("List"); + + var model = new AffiliateModel(); + PrepareAffiliateModel(model, affiliate, false); + return View(model); + } + + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] + public virtual ActionResult Edit(AffiliateModel model, bool continueEditing) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) + return AccessDeniedView(); + + var affiliate = _affiliateService.GetAffiliateById(model.Id); + if (affiliate == null || affiliate.Deleted) + //No affiliate found with the specified id + return RedirectToAction("List"); + + if (ModelState.IsValid) + { + affiliate.Active = model.Active; + affiliate.AdminComment = model.AdminComment; + //validate friendly URL name + var friendlyUrlName = affiliate.ValidateFriendlyUrlName(model.FriendlyUrlName); + affiliate.FriendlyUrlName = friendlyUrlName; + affiliate.Address = model.Address.ToEntity(affiliate.Address); + //some validation + if (affiliate.Address.CountryId == 0) + affiliate.Address.CountryId = null; + if (affiliate.Address.StateProvinceId == 0) + affiliate.Address.StateProvinceId = null; + _affiliateService.UpdateAffiliate(affiliate); + + //activity log + _customerActivityService.InsertActivity("EditAffiliate", _localizationService.GetResource("ActivityLog.EditAffiliate"), affiliate.Id); + + SuccessNotification(_localizationService.GetResource("Admin.Affiliates.Updated")); + if (continueEditing) + { + //selected tab + SaveSelectedTabName(); + + return RedirectToAction("Edit", new {id = affiliate.Id}); + } + return RedirectToAction("List"); + } + + //If we got this far, something failed, redisplay form + PrepareAffiliateModel(model, affiliate, true); + return View(model); + } + + //delete + [HttpPost] + public virtual ActionResult Delete(int id) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) + return AccessDeniedView(); + + var affiliate = _affiliateService.GetAffiliateById(id); + if (affiliate == null) + //No affiliate found with the specified id + return RedirectToAction("List"); + + _affiliateService.DeleteAffiliate(affiliate); + + //activity log + _customerActivityService.InsertActivity("DeleteAffiliate", _localizationService.GetResource("ActivityLog.DeleteAffiliate"), affiliate.Id); + + SuccessNotification(_localizationService.GetResource("Admin.Affiliates.Deleted")); + return RedirectToAction("List"); + } + + [ChildActionOnly] + public virtual ActionResult AffiliatedOrderList(int affiliateId) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) + return Content(""); + + if (affiliateId == 0) + throw new Exception("Affliate ID cannot be 0"); + + var model = new AffiliatedOrderListModel(); + model.AffliateId = affiliateId; + + //order statuses + model.AvailableOrderStatuses = OrderStatus.Pending.ToSelectList(false).ToList(); + model.AvailableOrderStatuses.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + + //payment statuses + model.AvailablePaymentStatuses = PaymentStatus.Pending.ToSelectList(false).ToList(); + model.AvailablePaymentStatuses.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + + //shipping statuses + model.AvailableShippingStatuses = ShippingStatus.NotYetShipped.ToSelectList(false).ToList(); + model.AvailableShippingStatuses.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + + return PartialView(model); + } + [HttpPost] + public virtual ActionResult AffiliatedOrderListGrid(DataSourceRequest command, AffiliatedOrderListModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) + return AccessDeniedKendoGridJson(); + + var affiliate = _affiliateService.GetAffiliateById(model.AffliateId); + if (affiliate == null) + throw new ArgumentException("No affiliate found with the specified id"); + + DateTime? startDateValue = (model.StartDate == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); + + DateTime? endDateValue = (model.EndDate == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); + + var orderStatusIds = model.OrderStatusId > 0 ? new List() { model.OrderStatusId } : null; + var paymentStatusIds = model.PaymentStatusId > 0 ? new List() { model.PaymentStatusId } : null; + var shippingStatusIds = model.ShippingStatusId > 0 ? new List() { model.ShippingStatusId } : null; + + var orders = _orderService.SearchOrders( + createdFromUtc: startDateValue, + createdToUtc: endDateValue, + osIds: orderStatusIds, + psIds: paymentStatusIds, + ssIds: shippingStatusIds, + affiliateId: affiliate.Id, + pageIndex: command.Page - 1, + pageSize: command.PageSize); + var gridModel = new DataSourceResult + { + Data = orders.Select(order => + { + var orderModel = new AffiliateModel.AffiliatedOrderModel(); + orderModel.Id = order.Id; + orderModel.OrderStatus = order.OrderStatus.GetLocalizedEnum(_localizationService, _workContext); + orderModel.OrderStatusId = order.OrderStatusId; + orderModel.PaymentStatus = order.PaymentStatus.GetLocalizedEnum(_localizationService, _workContext); + orderModel.ShippingStatus = order.ShippingStatus.GetLocalizedEnum(_localizationService, _workContext); + orderModel.OrderTotal = _priceFormatter.FormatPrice(order.OrderTotal, true, false); + orderModel.CreatedOn = _dateTimeHelper.ConvertToUserTime(order.CreatedOnUtc, DateTimeKind.Utc); + orderModel.CustomOrderNumber = order.CustomOrderNumber; + + return orderModel; + }), + Total = orders.TotalCount + }; + + return Json(gridModel); + } + + + [HttpPost] + public virtual ActionResult AffiliatedCustomerList(int affiliateId, DataSourceRequest command) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) + return AccessDeniedKendoGridJson(); + + var affiliate = _affiliateService.GetAffiliateById(affiliateId); + if (affiliate == null) + throw new ArgumentException("No affiliate found with the specified id"); + + var customers = _customerService.GetAllCustomers( + affiliateId: affiliate.Id, + pageIndex: command.Page - 1, + pageSize: command.PageSize); + var gridModel = new DataSourceResult + { + Data = customers.Select(customer => + { + var customerModel = new AffiliateModel.AffiliatedCustomerModel(); + customerModel.Id = customer.Id; + customerModel.Name = customer.Email; + return customerModel; + }), + Total = customers.TotalCount + }; + + return Json(gridModel); + } + + #endregion + } +} diff --git a/src/Presentation/Nop.Web/Administration/Controllers/CustomerController.cs b/src/Presentation/Nop.Web/Administration/Controllers/CustomerController.cs index ff96579f9f9..c5452d4b84d 100644 --- a/src/Presentation/Nop.Web/Administration/Controllers/CustomerController.cs +++ b/src/Presentation/Nop.Web/Administration/Controllers/CustomerController.cs @@ -1,2396 +1,2434 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Web.Mvc; -using Nop.Admin.Extensions; -using Nop.Admin.Helpers; -using Nop.Admin.Models.Common; -using Nop.Admin.Models.Customers; -using Nop.Admin.Models.ShoppingCart; -using Nop.Core; -using Nop.Core.Caching; -using Nop.Core.Domain.Catalog; -using Nop.Core.Domain.Common; -using Nop.Core.Domain.Customers; -using Nop.Core.Domain.Directory; -using Nop.Core.Domain.Forums; -using Nop.Core.Domain.Messages; -using Nop.Core.Domain.Orders; -using Nop.Core.Domain.Payments; -using Nop.Core.Domain.Shipping; -using Nop.Core.Domain.Tax; -using Nop.Services; -using Nop.Services.Affiliates; -using Nop.Services.Authentication.External; -using Nop.Services.Catalog; -using Nop.Services.Common; -using Nop.Services.Customers; -using Nop.Services.Directory; -using Nop.Services.ExportImport; -using Nop.Services.Forums; -using Nop.Services.Helpers; -using Nop.Services.Localization; -using Nop.Services.Logging; -using Nop.Services.Messages; -using Nop.Services.Orders; -using Nop.Services.Security; -using Nop.Services.Stores; -using Nop.Services.Tax; -using Nop.Services.Vendors; -using Nop.Web.Framework; -using Nop.Web.Framework.Controllers; -using Nop.Web.Framework.Kendoui; -using Nop.Web.Framework.Mvc; - -namespace Nop.Admin.Controllers -{ - public partial class CustomerController : BaseAdminController - { - #region Fields - - private readonly ICustomerService _customerService; - private readonly INewsLetterSubscriptionService _newsLetterSubscriptionService; - private readonly IGenericAttributeService _genericAttributeService; - private readonly ICustomerRegistrationService _customerRegistrationService; - private readonly ICustomerReportService _customerReportService; - private readonly IDateTimeHelper _dateTimeHelper; - private readonly ILocalizationService _localizationService; - private readonly DateTimeSettings _dateTimeSettings; - private readonly TaxSettings _taxSettings; - private readonly RewardPointsSettings _rewardPointsSettings; - private readonly ICountryService _countryService; - private readonly IStateProvinceService _stateProvinceService; - private readonly IAddressService _addressService; - private readonly CustomerSettings _customerSettings; - private readonly ITaxService _taxService; - private readonly IWorkContext _workContext; - private readonly IVendorService _vendorService; - private readonly IStoreContext _storeContext; - private readonly IPriceFormatter _priceFormatter; - private readonly IOrderService _orderService; - private readonly IExportManager _exportManager; - private readonly ICustomerActivityService _customerActivityService; - private readonly IBackInStockSubscriptionService _backInStockSubscriptionService; - private readonly IPriceCalculationService _priceCalculationService; - private readonly IProductAttributeFormatter _productAttributeFormatter; - private readonly IPermissionService _permissionService; - private readonly IQueuedEmailService _queuedEmailService; - private readonly EmailAccountSettings _emailAccountSettings; - private readonly IEmailAccountService _emailAccountService; - private readonly ForumSettings _forumSettings; - private readonly IForumService _forumService; - private readonly IOpenAuthenticationService _openAuthenticationService; - private readonly AddressSettings _addressSettings; - private readonly IStoreService _storeService; - private readonly ICustomerAttributeParser _customerAttributeParser; - private readonly ICustomerAttributeService _customerAttributeService; - private readonly IAddressAttributeParser _addressAttributeParser; - private readonly IAddressAttributeService _addressAttributeService; - private readonly IAddressAttributeFormatter _addressAttributeFormatter; - private readonly IAffiliateService _affiliateService; - private readonly IWorkflowMessageService _workflowMessageService; - private readonly IRewardPointService _rewardPointService; - private readonly ICacheManager _cacheManager; - - #endregion - - #region Constructors - - public CustomerController(ICustomerService customerService, - INewsLetterSubscriptionService newsLetterSubscriptionService, - IGenericAttributeService genericAttributeService, - ICustomerRegistrationService customerRegistrationService, - ICustomerReportService customerReportService, - IDateTimeHelper dateTimeHelper, - ILocalizationService localizationService, - DateTimeSettings dateTimeSettings, - TaxSettings taxSettings, - RewardPointsSettings rewardPointsSettings, - ICountryService countryService, - IStateProvinceService stateProvinceService, - IAddressService addressService, - CustomerSettings customerSettings, - ITaxService taxService, - IWorkContext workContext, - IVendorService vendorService, - IStoreContext storeContext, - IPriceFormatter priceFormatter, - IOrderService orderService, - IExportManager exportManager, - ICustomerActivityService customerActivityService, - IBackInStockSubscriptionService backInStockSubscriptionService, - IPriceCalculationService priceCalculationService, - IProductAttributeFormatter productAttributeFormatter, - IPermissionService permissionService, - IQueuedEmailService queuedEmailService, - EmailAccountSettings emailAccountSettings, - IEmailAccountService emailAccountService, - ForumSettings forumSettings, - IForumService forumService, - IOpenAuthenticationService openAuthenticationService, - AddressSettings addressSettings, - IStoreService storeService, - ICustomerAttributeParser customerAttributeParser, - ICustomerAttributeService customerAttributeService, - IAddressAttributeParser addressAttributeParser, - IAddressAttributeService addressAttributeService, - IAddressAttributeFormatter addressAttributeFormatter, - IAffiliateService affiliateService, - IWorkflowMessageService workflowMessageService, - IRewardPointService rewardPointService, - ICacheManager cacheManager) - { - this._customerService = customerService; - this._newsLetterSubscriptionService = newsLetterSubscriptionService; - this._genericAttributeService = genericAttributeService; - this._customerRegistrationService = customerRegistrationService; - this._customerReportService = customerReportService; - this._dateTimeHelper = dateTimeHelper; - this._localizationService = localizationService; - this._dateTimeSettings = dateTimeSettings; - this._taxSettings = taxSettings; - this._rewardPointsSettings = rewardPointsSettings; - this._countryService = countryService; - this._stateProvinceService = stateProvinceService; - this._addressService = addressService; - this._customerSettings = customerSettings; - this._taxService = taxService; - this._workContext = workContext; - this._vendorService = vendorService; - this._storeContext = storeContext; - this._priceFormatter = priceFormatter; - this._orderService = orderService; - this._exportManager = exportManager; - this._customerActivityService = customerActivityService; - this._backInStockSubscriptionService = backInStockSubscriptionService; - this._priceCalculationService = priceCalculationService; - this._productAttributeFormatter = productAttributeFormatter; - this._permissionService = permissionService; - this._queuedEmailService = queuedEmailService; - this._emailAccountSettings = emailAccountSettings; - this._emailAccountService = emailAccountService; - this._forumSettings = forumSettings; - this._forumService = forumService; - this._openAuthenticationService = openAuthenticationService; - this._addressSettings = addressSettings; - this._storeService = storeService; - this._customerAttributeParser = customerAttributeParser; - this._customerAttributeService = customerAttributeService; - this._addressAttributeParser = addressAttributeParser; - this._addressAttributeService = addressAttributeService; - this._addressAttributeFormatter = addressAttributeFormatter; - this._affiliateService = affiliateService; - this._workflowMessageService = workflowMessageService; - this._rewardPointService = rewardPointService; - this._cacheManager = cacheManager; - } - - #endregion - - #region Utilities - - [NonAction] - protected virtual string GetCustomerRolesNames(IList customerRoles, string separator = ",") - { - var sb = new StringBuilder(); - for (int i = 0; i < customerRoles.Count; i++) - { - sb.Append(customerRoles[i].Name); - if (i != customerRoles.Count - 1) - { - sb.Append(separator); - sb.Append(" "); - } - } - return sb.ToString(); - } - - [NonAction] - protected virtual IList GetReportRegisteredCustomersModel() - { - var report = new List(); - report.Add(new RegisteredCustomerReportLineModel - { - Period = _localizationService.GetResource("Admin.Customers.Reports.RegisteredCustomers.Fields.Period.7days"), - Customers = _customerReportService.GetRegisteredCustomersReport(7) - }); - - report.Add(new RegisteredCustomerReportLineModel - { - Period = _localizationService.GetResource("Admin.Customers.Reports.RegisteredCustomers.Fields.Period.14days"), - Customers = _customerReportService.GetRegisteredCustomersReport(14) - }); - report.Add(new RegisteredCustomerReportLineModel - { - Period = _localizationService.GetResource("Admin.Customers.Reports.RegisteredCustomers.Fields.Period.month"), - Customers = _customerReportService.GetRegisteredCustomersReport(30) - }); - report.Add(new RegisteredCustomerReportLineModel - { - Period = _localizationService.GetResource("Admin.Customers.Reports.RegisteredCustomers.Fields.Period.year"), - Customers = _customerReportService.GetRegisteredCustomersReport(365) - }); - - return report; - } - - [NonAction] - protected virtual IList GetAssociatedExternalAuthRecords(Customer customer) - { - if (customer == null) - throw new ArgumentNullException("customer"); - - var result = new List(); - foreach (var record in _openAuthenticationService.GetExternalIdentifiersFor(customer)) - { - var method = _openAuthenticationService.LoadExternalAuthenticationMethodBySystemName(record.ProviderSystemName); - if (method == null) - continue; - - result.Add(new CustomerModel.AssociatedExternalAuthModel - { - Id = record.Id, - Email = record.Email, - ExternalIdentifier = record.ExternalIdentifier, - AuthMethodName = method.PluginDescriptor.FriendlyName - }); - } - - return result; - } - - [NonAction] - protected virtual CustomerModel PrepareCustomerModelForList(Customer customer) - { - return new CustomerModel - { - Id = customer.Id, - Email = customer.IsRegistered() ? customer.Email : _localizationService.GetResource("Admin.Customers.Guest"), - Username = customer.Username, - FullName = customer.GetFullName(), - Company = customer.GetAttribute(SystemCustomerAttributeNames.Company), - Phone = customer.GetAttribute(SystemCustomerAttributeNames.Phone), - ZipPostalCode = customer.GetAttribute(SystemCustomerAttributeNames.ZipPostalCode), - CustomerRoleNames = GetCustomerRolesNames(customer.CustomerRoles.ToList()), - Active = customer.Active, - CreatedOn = _dateTimeHelper.ConvertToUserTime(customer.CreatedOnUtc, DateTimeKind.Utc), - LastActivityDate = _dateTimeHelper.ConvertToUserTime(customer.LastActivityDateUtc, DateTimeKind.Utc), - }; - } - - [NonAction] - protected virtual string ValidateCustomerRoles(IList customerRoles) - { - if (customerRoles == null) - throw new ArgumentNullException("customerRoles"); - - //ensure a customer is not added to both 'Guests' and 'Registered' customer roles - //ensure that a customer is in at least one required role ('Guests' and 'Registered') - bool isInGuestsRole = customerRoles.FirstOrDefault(cr => cr.SystemName == SystemCustomerRoleNames.Guests) != null; - bool isInRegisteredRole = customerRoles.FirstOrDefault(cr => cr.SystemName == SystemCustomerRoleNames.Registered) != null; - if (isInGuestsRole && isInRegisteredRole) - return _localizationService.GetResource("Admin.Customers.Customers.GuestsAndRegisteredRolesError"); - if (!isInGuestsRole && !isInRegisteredRole) - return _localizationService.GetResource("Admin.Customers.Customers.AddCustomerToGuestsOrRegisteredRoleError"); - - //no errors - return ""; - } - - [NonAction] - protected virtual void PrepareVendorsModel(CustomerModel model) - { - if (model == null) - throw new ArgumentNullException("model"); - - model.AvailableVendors.Add(new SelectListItem - { - Text = _localizationService.GetResource("Admin.Customers.Customers.Fields.Vendor.None"), - Value = "0" - }); - var vendors = SelectListHelper.GetVendorList(_vendorService, _cacheManager, true); - foreach (var v in vendors) - model.AvailableVendors.Add(v); - } - - [NonAction] - protected virtual void PrepareCustomerAttributeModel(CustomerModel model, Customer customer) - { - var customerAttributes = _customerAttributeService.GetAllCustomerAttributes(); - foreach (var attribute in customerAttributes) - { - var attributeModel = new CustomerModel.CustomerAttributeModel - { - Id = attribute.Id, - Name = attribute.Name, - IsRequired = attribute.IsRequired, - AttributeControlType = attribute.AttributeControlType, - }; - - if (attribute.ShouldHaveValues()) - { - //values - var attributeValues = _customerAttributeService.GetCustomerAttributeValues(attribute.Id); - foreach (var attributeValue in attributeValues) - { - var attributeValueModel = new CustomerModel.CustomerAttributeValueModel - { - Id = attributeValue.Id, - Name = attributeValue.Name, - IsPreSelected = attributeValue.IsPreSelected - }; - attributeModel.Values.Add(attributeValueModel); - } - } - - - //set already selected attributes - if (customer != null) - { - var selectedCustomerAttributes = customer.GetAttribute(SystemCustomerAttributeNames.CustomCustomerAttributes, _genericAttributeService); - switch (attribute.AttributeControlType) - { - case AttributeControlType.DropdownList: - case AttributeControlType.RadioList: - case AttributeControlType.Checkboxes: - { - if (!String.IsNullOrEmpty(selectedCustomerAttributes)) - { - //clear default selection - foreach (var item in attributeModel.Values) - item.IsPreSelected = false; - - //select new values - var selectedValues = _customerAttributeParser.ParseCustomerAttributeValues(selectedCustomerAttributes); - foreach (var attributeValue in selectedValues) - foreach (var item in attributeModel.Values) - if (attributeValue.Id == item.Id) - item.IsPreSelected = true; - } - } - break; - case AttributeControlType.ReadonlyCheckboxes: - { - //do nothing - //values are already pre-set - } - break; - case AttributeControlType.TextBox: - case AttributeControlType.MultilineTextbox: - { - if (!String.IsNullOrEmpty(selectedCustomerAttributes)) - { - var enteredText = _customerAttributeParser.ParseValues(selectedCustomerAttributes, attribute.Id); - if (enteredText.Any()) - attributeModel.DefaultValue = enteredText[0]; - } - } - break; - case AttributeControlType.Datepicker: - case AttributeControlType.ColorSquares: - case AttributeControlType.ImageSquares: - case AttributeControlType.FileUpload: - default: - //not supported attribute control types - break; - } - } - - model.CustomerAttributes.Add(attributeModel); - } - } - - [NonAction] - protected virtual string ParseCustomCustomerAttributes( FormCollection form) - { - if (form == null) - throw new ArgumentNullException("form"); - - string attributesXml = ""; - var customerAttributes = _customerAttributeService.GetAllCustomerAttributes(); - foreach (var attribute in customerAttributes) - { - string controlId = string.Format("customer_attribute_{0}", attribute.Id); - switch (attribute.AttributeControlType) - { - case AttributeControlType.DropdownList: - case AttributeControlType.RadioList: - { - var ctrlAttributes = form[controlId]; - if (!String.IsNullOrEmpty(ctrlAttributes)) - { - int selectedAttributeId = int.Parse(ctrlAttributes); - if (selectedAttributeId > 0) - attributesXml = _customerAttributeParser.AddCustomerAttribute(attributesXml, - attribute, selectedAttributeId.ToString()); - } - } - break; - case AttributeControlType.Checkboxes: - { - var cblAttributes = form[controlId]; - if (!String.IsNullOrEmpty(cblAttributes)) - { - foreach (var item in cblAttributes.Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries)) - { - int selectedAttributeId = int.Parse(item); - if (selectedAttributeId > 0) - attributesXml = _customerAttributeParser.AddCustomerAttribute(attributesXml, - attribute, selectedAttributeId.ToString()); - } - } - } - break; - case AttributeControlType.ReadonlyCheckboxes: - { - //load read-only (already server-side selected) values - var attributeValues = _customerAttributeService.GetCustomerAttributeValues(attribute.Id); - foreach (var selectedAttributeId in attributeValues - .Where(v => v.IsPreSelected) - .Select(v => v.Id) - .ToList()) - { - attributesXml = _customerAttributeParser.AddCustomerAttribute(attributesXml, - attribute, selectedAttributeId.ToString()); - } - } - break; - case AttributeControlType.TextBox: - case AttributeControlType.MultilineTextbox: - { - var ctrlAttributes = form[controlId]; - if (!String.IsNullOrEmpty(ctrlAttributes)) - { - string enteredText = ctrlAttributes.Trim(); - attributesXml = _customerAttributeParser.AddCustomerAttribute(attributesXml, - attribute, enteredText); - } - } - break; - case AttributeControlType.Datepicker: - case AttributeControlType.ColorSquares: - case AttributeControlType.ImageSquares: - case AttributeControlType.FileUpload: - //not supported customer attributes - default: - break; - } - } - - return attributesXml; - } - - [NonAction] - protected virtual void PrepareCustomerModel(CustomerModel model, Customer customer, bool excludeProperties) - { - var allStores = _storeService.GetAllStores(); - if (customer != null) - { - model.Id = customer.Id; - if (!excludeProperties) - { - model.Email = customer.Email; - model.Username = customer.Username; - model.VendorId = customer.VendorId; - model.AdminComment = customer.AdminComment; - model.IsTaxExempt = customer.IsTaxExempt; - model.Active = customer.Active; - - if (customer.RegisteredInStoreId == 0 || allStores.All(s => s.Id != customer.RegisteredInStoreId)) - model.RegisteredInStore = string.Empty; - else - model.RegisteredInStore = allStores.First(s => s.Id == customer.RegisteredInStoreId).Name; - - var affiliate = _affiliateService.GetAffiliateById(customer.AffiliateId); - if (affiliate != null) - { - model.AffiliateId = affiliate.Id; - model.AffiliateName = affiliate.GetFullName(); - } - - model.TimeZoneId = customer.GetAttribute(SystemCustomerAttributeNames.TimeZoneId); - model.VatNumber = customer.GetAttribute(SystemCustomerAttributeNames.VatNumber); - model.VatNumberStatusNote = ((VatNumberStatus)customer.GetAttribute(SystemCustomerAttributeNames.VatNumberStatusId)) - .GetLocalizedEnum(_localizationService, _workContext); - model.CreatedOn = _dateTimeHelper.ConvertToUserTime(customer.CreatedOnUtc, DateTimeKind.Utc); - model.LastActivityDate = _dateTimeHelper.ConvertToUserTime(customer.LastActivityDateUtc, DateTimeKind.Utc); - model.LastIpAddress = customer.LastIpAddress; - model.LastVisitedPage = customer.GetAttribute(SystemCustomerAttributeNames.LastVisitedPage); - - model.SelectedCustomerRoleIds = customer.CustomerRoles.Select(cr => cr.Id).ToList(); - - //newsletter subscriptions - if (!String.IsNullOrEmpty(customer.Email)) - { - var newsletterSubscriptionStoreIds = new List(); - foreach (var store in allStores) - { - var newsletterSubscription = _newsLetterSubscriptionService - .GetNewsLetterSubscriptionByEmailAndStoreId(customer.Email, store.Id); - if (newsletterSubscription != null) - newsletterSubscriptionStoreIds.Add(store.Id); - model.SelectedNewsletterSubscriptionStoreIds = newsletterSubscriptionStoreIds.ToArray(); - } - } - - //form fields - model.FirstName = customer.GetAttribute(SystemCustomerAttributeNames.FirstName); - model.LastName = customer.GetAttribute(SystemCustomerAttributeNames.LastName); - model.Gender = customer.GetAttribute(SystemCustomerAttributeNames.Gender); - model.DateOfBirth = customer.GetAttribute(SystemCustomerAttributeNames.DateOfBirth); - model.Company = customer.GetAttribute(SystemCustomerAttributeNames.Company); - model.StreetAddress = customer.GetAttribute(SystemCustomerAttributeNames.StreetAddress); - model.StreetAddress2 = customer.GetAttribute(SystemCustomerAttributeNames.StreetAddress2); - model.ZipPostalCode = customer.GetAttribute(SystemCustomerAttributeNames.ZipPostalCode); - model.City = customer.GetAttribute(SystemCustomerAttributeNames.City); - model.CountryId = customer.GetAttribute(SystemCustomerAttributeNames.CountryId); - model.StateProvinceId = customer.GetAttribute(SystemCustomerAttributeNames.StateProvinceId); - model.Phone = customer.GetAttribute(SystemCustomerAttributeNames.Phone); - model.Fax = customer.GetAttribute(SystemCustomerAttributeNames.Fax); - } - } - - model.UsernamesEnabled = _customerSettings.UsernamesEnabled; - model.AllowCustomersToSetTimeZone = _dateTimeSettings.AllowCustomersToSetTimeZone; - foreach (var tzi in _dateTimeHelper.GetSystemTimeZones()) - model.AvailableTimeZones.Add(new SelectListItem { Text = tzi.DisplayName, Value = tzi.Id, Selected = (tzi.Id == model.TimeZoneId) }); - if (customer != null) - { - model.DisplayVatNumber = _taxSettings.EuVatEnabled; - } - else - { - model.DisplayVatNumber = false; - } - - //vendors - PrepareVendorsModel(model); - //customer attributes - PrepareCustomerAttributeModel(model, customer); - - model.GenderEnabled = _customerSettings.GenderEnabled; - model.DateOfBirthEnabled = _customerSettings.DateOfBirthEnabled; - model.CompanyEnabled = _customerSettings.CompanyEnabled; - model.StreetAddressEnabled = _customerSettings.StreetAddressEnabled; - model.StreetAddress2Enabled = _customerSettings.StreetAddress2Enabled; - model.ZipPostalCodeEnabled = _customerSettings.ZipPostalCodeEnabled; - model.CityEnabled = _customerSettings.CityEnabled; - model.CountryEnabled = _customerSettings.CountryEnabled; - model.StateProvinceEnabled = _customerSettings.StateProvinceEnabled; - model.PhoneEnabled = _customerSettings.PhoneEnabled; - model.FaxEnabled = _customerSettings.FaxEnabled; - - //countries and states - if (_customerSettings.CountryEnabled) - { - model.AvailableCountries.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.SelectCountry"), Value = "0" }); - foreach (var c in _countryService.GetAllCountries(showHidden: true)) - { - model.AvailableCountries.Add(new SelectListItem - { - Text = c.Name, - Value = c.Id.ToString(), - Selected = c.Id == model.CountryId - }); - } - - if (_customerSettings.StateProvinceEnabled) - { - //states - var states = _stateProvinceService.GetStateProvincesByCountryId(model.CountryId).ToList(); - if (states.Any()) - { - model.AvailableStates.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.SelectState"), Value = "0" }); - - foreach (var s in states) - { - model.AvailableStates.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString(), Selected = (s.Id == model.StateProvinceId) }); - } - } - else - { - bool anyCountrySelected = model.AvailableCountries.Any(x => x.Selected); - - model.AvailableStates.Add(new SelectListItem - { - Text = _localizationService.GetResource(anyCountrySelected ? "Admin.Address.OtherNonUS" : "Admin.Address.SelectState"), - Value = "0" - }); - } - } - } - - //newsletter subscriptions - model.AvailableNewsletterSubscriptionStores = allStores - .Select(s => new CustomerModel.StoreModel() {Id = s.Id, Name = s.Name }) - .ToList(); - - //customer roles - var allRoles = _customerService.GetAllCustomerRoles(true); - var adminRole = allRoles.FirstOrDefault(c => c.SystemName == SystemCustomerRoleNames.Registered); - //precheck Registered Role as a default role while creating a new customer through admin - if (customer == null && adminRole != null) - { - model.SelectedCustomerRoleIds.Add(adminRole.Id); - } - foreach (var role in allRoles) - { - model.AvailableCustomerRoles.Add(new SelectListItem - { - Text = role.Name, - Value = role.Id.ToString(), - Selected = model.SelectedCustomerRoleIds.Contains(role.Id) - }); - } - - //reward points history - if (customer != null) - { - model.DisplayRewardPointsHistory = _rewardPointsSettings.Enabled; - model.AddRewardPointsValue = 0; - model.AddRewardPointsMessage = "Some comment here..."; - - //stores - foreach (var store in allStores) - { - model.RewardPointsAvailableStores.Add(new SelectListItem - { - Text = store.Name, - Value = store.Id.ToString(), - Selected = (store.Id == _storeContext.CurrentStore.Id) - }); - } - } - else - { - model.DisplayRewardPointsHistory = false; - } - //external authentication records - if (customer != null) - { - model.AssociatedExternalAuthRecords = GetAssociatedExternalAuthRecords(customer); - } - //sending of the welcome message: - //1. "admin approval" registration method - //2. already created customer - //3. registered - model.AllowSendingOfWelcomeMessage = _customerSettings.UserRegistrationType == UserRegistrationType.AdminApproval && - customer != null && - customer.IsRegistered(); - //sending of the activation message - //1. "email validation" registration method - //2. already created customer - //3. registered - //4. not active - model.AllowReSendingOfActivationMessage = _customerSettings.UserRegistrationType == UserRegistrationType.EmailValidation && - customer != null && - customer.IsRegistered() && - !customer.Active; - } - - [NonAction] - protected virtual void PrepareAddressModel(CustomerAddressModel model, Address address, Customer customer, bool excludeProperties) - { - if (customer == null) - throw new ArgumentNullException("customer"); - - model.CustomerId = customer.Id; - if (address != null) - { - if (!excludeProperties) - { - model.Address = address.ToModel(); - } - } - - if (model.Address == null) - model.Address = new AddressModel(); - - model.Address.FirstNameEnabled = true; - model.Address.FirstNameRequired = true; - model.Address.LastNameEnabled = true; - model.Address.LastNameRequired = true; - model.Address.EmailEnabled = true; - model.Address.EmailRequired = true; - model.Address.CompanyEnabled = _addressSettings.CompanyEnabled; - model.Address.CompanyRequired = _addressSettings.CompanyRequired; - model.Address.CountryEnabled = _addressSettings.CountryEnabled; - model.Address.CountryRequired = _addressSettings.CountryEnabled; //country is required when enabled - model.Address.StateProvinceEnabled = _addressSettings.StateProvinceEnabled; - model.Address.CityEnabled = _addressSettings.CityEnabled; - model.Address.CityRequired = _addressSettings.CityRequired; - model.Address.StreetAddressEnabled = _addressSettings.StreetAddressEnabled; - model.Address.StreetAddressRequired = _addressSettings.StreetAddressRequired; - model.Address.StreetAddress2Enabled = _addressSettings.StreetAddress2Enabled; - model.Address.StreetAddress2Required = _addressSettings.StreetAddress2Required; - model.Address.ZipPostalCodeEnabled = _addressSettings.ZipPostalCodeEnabled; - model.Address.ZipPostalCodeRequired = _addressSettings.ZipPostalCodeRequired; - model.Address.PhoneEnabled = _addressSettings.PhoneEnabled; - model.Address.PhoneRequired = _addressSettings.PhoneRequired; - model.Address.FaxEnabled = _addressSettings.FaxEnabled; - model.Address.FaxRequired = _addressSettings.FaxRequired; - //countries - model.Address.AvailableCountries.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.SelectCountry"), Value = "0" }); - foreach (var c in _countryService.GetAllCountries(showHidden: true)) - model.Address.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id.ToString(), Selected = (c.Id == model.Address.CountryId) }); - //states - var states = model.Address.CountryId.HasValue ? _stateProvinceService.GetStateProvincesByCountryId(model.Address.CountryId.Value, showHidden: true).ToList() : new List(); - if (states.Any()) - { - foreach (var s in states) - model.Address.AvailableStates.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString(), Selected = (s.Id == model.Address.StateProvinceId) }); - } - else - model.Address.AvailableStates.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.OtherNonUS"), Value = "0" }); - //customer attribute services - model.Address.PrepareCustomAddressAttributes(address, _addressAttributeService, _addressAttributeParser); - } - - [NonAction] - private bool SecondAdminAccountExists(Customer customer) - { - var customers = _customerService.GetAllCustomers(customerRoleIds: new[] {_customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.Administrators).Id}); - - return customers.Any(c => c.Active && c.Id != customer.Id); - } - #endregion - - #region Customers - - public virtual ActionResult Index() - { - return RedirectToAction("List"); - } - - public virtual ActionResult List() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); - - //load registered customers by default - var defaultRoleIds = new List {_customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.Registered).Id}; - var model = new CustomerListModel - { - UsernamesEnabled = _customerSettings.UsernamesEnabled, - DateOfBirthEnabled = _customerSettings.DateOfBirthEnabled, - CompanyEnabled = _customerSettings.CompanyEnabled, - PhoneEnabled = _customerSettings.PhoneEnabled, - ZipPostalCodeEnabled = _customerSettings.ZipPostalCodeEnabled, - SearchCustomerRoleIds = defaultRoleIds, - }; - var allRoles = _customerService.GetAllCustomerRoles(true); - foreach (var role in allRoles) - { - model.AvailableCustomerRoles.Add(new SelectListItem - { - Text = role.Name, - Value = role.Id.ToString(), - Selected = defaultRoleIds.Any(x => x == role.Id) - }); - } - - return View(model); - } - - [HttpPost] - public virtual ActionResult CustomerList(DataSourceRequest command, CustomerListModel model, - [ModelBinder(typeof(CommaSeparatedModelBinder))] int[] searchCustomerRoleIds) - { - //we use own own binder for searchCustomerRoleIds property - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedKendoGridJson(); - - var searchDayOfBirth = 0; - int searchMonthOfBirth = 0; - if (!String.IsNullOrWhiteSpace(model.SearchDayOfBirth)) - searchDayOfBirth = Convert.ToInt32(model.SearchDayOfBirth); - if (!String.IsNullOrWhiteSpace(model.SearchMonthOfBirth)) - searchMonthOfBirth = Convert.ToInt32(model.SearchMonthOfBirth); - - var customers = _customerService.GetAllCustomers( - customerRoleIds: searchCustomerRoleIds, - email: model.SearchEmail, - username: model.SearchUsername, - firstName: model.SearchFirstName, - lastName: model.SearchLastName, - dayOfBirth: searchDayOfBirth, - monthOfBirth: searchMonthOfBirth, - company: model.SearchCompany, - phone: model.SearchPhone, - zipPostalCode: model.SearchZipPostalCode, - ipAddress: model.SearchIpAddress, - loadOnlyWithShoppingCart: false, - pageIndex: command.Page - 1, - pageSize: command.PageSize); - var gridModel = new DataSourceResult - { - Data = customers.Select(PrepareCustomerModelForList), - Total = customers.TotalCount - }; - - return Json(gridModel); - } - - public virtual ActionResult Create() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); - - var model = new CustomerModel(); - PrepareCustomerModel(model, null, false); - //default value - model.Active = true; - return View(model); - } - - [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] - [FormValueRequired("save", "save-continue")] - [ValidateInput(false)] - public virtual ActionResult Create(CustomerModel model, bool continueEditing, FormCollection form) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); - - if (!String.IsNullOrWhiteSpace(model.Email)) - { - var cust2 = _customerService.GetCustomerByEmail(model.Email); - if (cust2 != null) - ModelState.AddModelError("", "Email is already registered"); - } - if (!String.IsNullOrWhiteSpace(model.Username) & _customerSettings.UsernamesEnabled) - { - var cust2 = _customerService.GetCustomerByUsername(model.Username); - if (cust2 != null) - ModelState.AddModelError("", "Username is already registered"); - } - - //validate customer roles - var allCustomerRoles = _customerService.GetAllCustomerRoles(true); - var newCustomerRoles = new List(); - foreach (var customerRole in allCustomerRoles) - if (model.SelectedCustomerRoleIds.Contains(customerRole.Id)) - newCustomerRoles.Add(customerRole); - var customerRolesError = ValidateCustomerRoles(newCustomerRoles); - if (!String.IsNullOrEmpty(customerRolesError)) - { - ModelState.AddModelError("", customerRolesError); - ErrorNotification(customerRolesError, false); - } - - // Ensure that valid email address is entered if Registered role is checked to avoid registered customers with empty email address - if (newCustomerRoles.Any() && newCustomerRoles.FirstOrDefault(c => c.SystemName == SystemCustomerRoleNames.Registered) != null && !CommonHelper.IsValidEmail(model.Email)) - { - ModelState.AddModelError("", _localizationService.GetResource("Admin.Customers.Customers.ValidEmailRequiredRegisteredRole")); - ErrorNotification(_localizationService.GetResource("Admin.Customers.Customers.ValidEmailRequiredRegisteredRole"), false); - } - - //custom customer attributes - var customerAttributesXml = ParseCustomCustomerAttributes(form); - if (newCustomerRoles.Any() && newCustomerRoles.FirstOrDefault(c => c.SystemName == SystemCustomerRoleNames.Registered) != null) - { - var customerAttributeWarnings = _customerAttributeParser.GetAttributeWarnings(customerAttributesXml); - foreach (var error in customerAttributeWarnings) - { - ModelState.AddModelError("", error); - } - } - - if (ModelState.IsValid) - { - var customer = new Customer - { - CustomerGuid = Guid.NewGuid(), - Email = model.Email, - Username = model.Username, - VendorId = model.VendorId, - AdminComment = model.AdminComment, - IsTaxExempt = model.IsTaxExempt, - Active = model.Active, - CreatedOnUtc = DateTime.UtcNow, - LastActivityDateUtc = DateTime.UtcNow, - RegisteredInStoreId = _storeContext.CurrentStore.Id - }; - _customerService.InsertCustomer(customer); - - //form fields - if (_dateTimeSettings.AllowCustomersToSetTimeZone) - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.TimeZoneId, model.TimeZoneId); - if (_customerSettings.GenderEnabled) - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Gender, model.Gender); - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.FirstName, model.FirstName); - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.LastName, model.LastName); - if (_customerSettings.DateOfBirthEnabled) - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.DateOfBirth, model.DateOfBirth); - if (_customerSettings.CompanyEnabled) - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Company, model.Company); - if (_customerSettings.StreetAddressEnabled) - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.StreetAddress, model.StreetAddress); - if (_customerSettings.StreetAddress2Enabled) - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.StreetAddress2, model.StreetAddress2); - if (_customerSettings.ZipPostalCodeEnabled) - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.ZipPostalCode, model.ZipPostalCode); - if (_customerSettings.CityEnabled) - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.City, model.City); - if (_customerSettings.CountryEnabled) - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.CountryId, model.CountryId); - if (_customerSettings.CountryEnabled && _customerSettings.StateProvinceEnabled) - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.StateProvinceId, model.StateProvinceId); - if (_customerSettings.PhoneEnabled) - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Phone, model.Phone); - if (_customerSettings.FaxEnabled) - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Fax, model.Fax); - - //custom customer attributes - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.CustomCustomerAttributes, customerAttributesXml); - - - //newsletter subscriptions - if (!String.IsNullOrEmpty(customer.Email)) - { - var allStores = _storeService.GetAllStores(); - foreach (var store in allStores) - { - var newsletterSubscription = _newsLetterSubscriptionService - .GetNewsLetterSubscriptionByEmailAndStoreId(customer.Email, store.Id); - if (model.SelectedNewsletterSubscriptionStoreIds != null && - model.SelectedNewsletterSubscriptionStoreIds.Contains(store.Id)) - { - //subscribed - if (newsletterSubscription == null) - { - _newsLetterSubscriptionService.InsertNewsLetterSubscription(new NewsLetterSubscription - { - NewsLetterSubscriptionGuid = Guid.NewGuid(), - Email = customer.Email, - Active = true, - StoreId = store.Id, - CreatedOnUtc = DateTime.UtcNow - }); - } - } - else - { - //not subscribed - if (newsletterSubscription != null) - { - _newsLetterSubscriptionService.DeleteNewsLetterSubscription(newsletterSubscription); - } - } - } - } - - //password - if (!String.IsNullOrWhiteSpace(model.Password)) - { - var changePassRequest = new ChangePasswordRequest(model.Email, false, _customerSettings.DefaultPasswordFormat, model.Password); - var changePassResult = _customerRegistrationService.ChangePassword(changePassRequest); - if (!changePassResult.Success) - { - foreach (var changePassError in changePassResult.Errors) - ErrorNotification(changePassError); - } - } - - //customer roles - foreach (var customerRole in newCustomerRoles) - { - //ensure that the current customer cannot add to "Administrators" system role if he's not an admin himself - if (customerRole.SystemName == SystemCustomerRoleNames.Administrators && - !_workContext.CurrentCustomer.IsAdmin()) - continue; - - customer.CustomerRoles.Add(customerRole); - } - _customerService.UpdateCustomer(customer); - - - //ensure that a customer with a vendor associated is not in "Administrators" role - //otherwise, he won't have access to other functionality in admin area - if (customer.IsAdmin() && customer.VendorId > 0) - { - customer.VendorId = 0; - _customerService.UpdateCustomer(customer); - ErrorNotification(_localizationService.GetResource("Admin.Customers.Customers.AdminCouldNotbeVendor")); - } - - //ensure that a customer in the Vendors role has a vendor account associated. - //otherwise, he will have access to ALL products - if (customer.IsVendor() && customer.VendorId == 0) - { - var vendorRole = customer - .CustomerRoles - .FirstOrDefault(x => x.SystemName == SystemCustomerRoleNames.Vendors); - customer.CustomerRoles.Remove(vendorRole); - _customerService.UpdateCustomer(customer); - ErrorNotification(_localizationService.GetResource("Admin.Customers.Customers.CannotBeInVendoRoleWithoutVendorAssociated")); - } - - //activity log - _customerActivityService.InsertActivity("AddNewCustomer", _localizationService.GetResource("ActivityLog.AddNewCustomer"), customer.Id); - - SuccessNotification(_localizationService.GetResource("Admin.Customers.Customers.Added")); - - if (continueEditing) - { - //selected tab - SaveSelectedTabName(); - - return RedirectToAction("Edit", new {id = customer.Id}); - } - return RedirectToAction("List"); - } - - //If we got this far, something failed, redisplay form - PrepareCustomerModel(model, null, true); - return View(model); - } - - public virtual ActionResult Edit(int id) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); - - var customer = _customerService.GetCustomerById(id); - if (customer == null || customer.Deleted) - //No customer found with the specified id - return RedirectToAction("List"); - - var model = new CustomerModel(); - PrepareCustomerModel(model, customer, false); - return View(model); - } - - [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] - [FormValueRequired("save", "save-continue")] - [ValidateInput(false)] - public virtual ActionResult Edit(CustomerModel model, bool continueEditing, FormCollection form) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); - - var customer = _customerService.GetCustomerById(model.Id); - if (customer == null || customer.Deleted) - //No customer found with the specified id - return RedirectToAction("List"); - - //validate customer roles - var allCustomerRoles = _customerService.GetAllCustomerRoles(true); - var newCustomerRoles = new List(); - foreach (var customerRole in allCustomerRoles) - if (model.SelectedCustomerRoleIds.Contains(customerRole.Id)) - newCustomerRoles.Add(customerRole); - var customerRolesError = ValidateCustomerRoles(newCustomerRoles); - if (!String.IsNullOrEmpty(customerRolesError)) - { - ModelState.AddModelError("", customerRolesError); - ErrorNotification(customerRolesError, false); - } - - // Ensure that valid email address is entered if Registered role is checked to avoid registered customers with empty email address - if (newCustomerRoles.Any() && newCustomerRoles.FirstOrDefault(c => c.SystemName == SystemCustomerRoleNames.Registered) != null && !CommonHelper.IsValidEmail(model.Email)) - { - ModelState.AddModelError("", _localizationService.GetResource("Admin.Customers.Customers.ValidEmailRequiredRegisteredRole")); - ErrorNotification(_localizationService.GetResource("Admin.Customers.Customers.ValidEmailRequiredRegisteredRole"), false); - } - - //custom customer attributes - var customerAttributesXml = ParseCustomCustomerAttributes(form); - if (newCustomerRoles.Any() && newCustomerRoles.FirstOrDefault(c => c.SystemName == SystemCustomerRoleNames.Registered) != null) - { - var customerAttributeWarnings = _customerAttributeParser.GetAttributeWarnings(customerAttributesXml); - foreach (var error in customerAttributeWarnings) - { - ModelState.AddModelError("", error); - } - } - - if (ModelState.IsValid) - { - try - { - customer.AdminComment = model.AdminComment; - customer.IsTaxExempt = model.IsTaxExempt; - - //prevent deactivation of the last active administrator - if (!customer.IsAdmin() || model.Active || SecondAdminAccountExists(customer)) - customer.Active = model.Active; - else - ErrorNotification(_localizationService.GetResource("Admin.Customers.Customers.AdminAccountShouldExists.Deactivate")); - - //email - if (!String.IsNullOrWhiteSpace(model.Email)) - { - _customerRegistrationService.SetEmail(customer, model.Email, false); - } - else - { - customer.Email = model.Email; - } - - //username - if (_customerSettings.UsernamesEnabled) - { - if (!String.IsNullOrWhiteSpace(model.Username)) - { - _customerRegistrationService.SetUsername(customer, model.Username); - } - else - { - customer.Username = model.Username; - } - } - - //VAT number - if (_taxSettings.EuVatEnabled) - { - var prevVatNumber = customer.GetAttribute(SystemCustomerAttributeNames.VatNumber); - - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.VatNumber, model.VatNumber); - //set VAT number status - if (!String.IsNullOrEmpty(model.VatNumber)) - { - if (!model.VatNumber.Equals(prevVatNumber, StringComparison.InvariantCultureIgnoreCase)) - { - _genericAttributeService.SaveAttribute(customer, - SystemCustomerAttributeNames.VatNumberStatusId, - (int)_taxService.GetVatNumberStatus(model.VatNumber)); - } - } - else - { - _genericAttributeService.SaveAttribute(customer, - SystemCustomerAttributeNames.VatNumberStatusId, - (int)VatNumberStatus.Empty); - } - } - - //vendor - customer.VendorId = model.VendorId; - - //form fields - if (_dateTimeSettings.AllowCustomersToSetTimeZone) - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.TimeZoneId, model.TimeZoneId); - if (_customerSettings.GenderEnabled) - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Gender, model.Gender); - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.FirstName, model.FirstName); - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.LastName, model.LastName); - if (_customerSettings.DateOfBirthEnabled) - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.DateOfBirth, model.DateOfBirth); - if (_customerSettings.CompanyEnabled) - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Company, model.Company); - if (_customerSettings.StreetAddressEnabled) - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.StreetAddress, model.StreetAddress); - if (_customerSettings.StreetAddress2Enabled) - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.StreetAddress2, model.StreetAddress2); - if (_customerSettings.ZipPostalCodeEnabled) - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.ZipPostalCode, model.ZipPostalCode); - if (_customerSettings.CityEnabled) - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.City, model.City); - if (_customerSettings.CountryEnabled) - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.CountryId, model.CountryId); - if (_customerSettings.CountryEnabled && _customerSettings.StateProvinceEnabled) - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.StateProvinceId, model.StateProvinceId); - if (_customerSettings.PhoneEnabled) - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Phone, model.Phone); - if (_customerSettings.FaxEnabled) - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Fax, model.Fax); - - //custom customer attributes - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.CustomCustomerAttributes, customerAttributesXml); - - //newsletter subscriptions - if (!String.IsNullOrEmpty(customer.Email)) - { - var allStores = _storeService.GetAllStores(); - foreach (var store in allStores) - { - var newsletterSubscription = _newsLetterSubscriptionService - .GetNewsLetterSubscriptionByEmailAndStoreId(customer.Email, store.Id); - if (model.SelectedNewsletterSubscriptionStoreIds != null && - model.SelectedNewsletterSubscriptionStoreIds.Contains(store.Id)) - { - //subscribed - if (newsletterSubscription == null) - { - _newsLetterSubscriptionService.InsertNewsLetterSubscription(new NewsLetterSubscription - { - NewsLetterSubscriptionGuid = Guid.NewGuid(), - Email = customer.Email, - Active = true, - StoreId = store.Id, - CreatedOnUtc = DateTime.UtcNow - }); - } - } - else - { - //not subscribed - if (newsletterSubscription != null) - { - _newsLetterSubscriptionService.DeleteNewsLetterSubscription(newsletterSubscription); - } - } - } - } - - - //customer roles - foreach (var customerRole in allCustomerRoles) - { - //ensure that the current customer cannot add/remove to/from "Administrators" system role - //if he's not an admin himself - if (customerRole.SystemName == SystemCustomerRoleNames.Administrators && - !_workContext.CurrentCustomer.IsAdmin()) - continue; - - if (model.SelectedCustomerRoleIds.Contains(customerRole.Id)) - { - //new role - if (customer.CustomerRoles.Count(cr => cr.Id == customerRole.Id) == 0) - customer.CustomerRoles.Add(customerRole); - } - else - { - //prevent attempts to delete the administrator role from the user, if the user is the last active administrator - if (customerRole.SystemName == SystemCustomerRoleNames.Administrators && !SecondAdminAccountExists(customer)) - { - ErrorNotification(_localizationService.GetResource("Admin.Customers.Customers.AdminAccountShouldExists.DeleteRole")); - continue; - } - - //remove role - if (customer.CustomerRoles.Count(cr => cr.Id == customerRole.Id) > 0) - customer.CustomerRoles.Remove(customerRole); - } - } - _customerService.UpdateCustomer(customer); - - - //ensure that a customer with a vendor associated is not in "Administrators" role - //otherwise, he won't have access to the other functionality in admin area - if (customer.IsAdmin() && customer.VendorId > 0) - { - customer.VendorId = 0; - _customerService.UpdateCustomer(customer); - ErrorNotification(_localizationService.GetResource("Admin.Customers.Customers.AdminCouldNotbeVendor")); - } - - //ensure that a customer in the Vendors role has a vendor account associated. - //otherwise, he will have access to ALL products - if (customer.IsVendor() && customer.VendorId == 0) - { - var vendorRole = customer - .CustomerRoles - .FirstOrDefault(x => x.SystemName == SystemCustomerRoleNames.Vendors); - customer.CustomerRoles.Remove(vendorRole); - _customerService.UpdateCustomer(customer); - ErrorNotification(_localizationService.GetResource("Admin.Customers.Customers.CannotBeInVendoRoleWithoutVendorAssociated")); - } - - - //activity log - _customerActivityService.InsertActivity("EditCustomer", _localizationService.GetResource("ActivityLog.EditCustomer"), customer.Id); - - SuccessNotification(_localizationService.GetResource("Admin.Customers.Customers.Updated")); - if (continueEditing) - { - //selected tab - SaveSelectedTabName(); - - return RedirectToAction("Edit", new {id = customer.Id}); - } - return RedirectToAction("List"); - } - catch (Exception exc) - { - ErrorNotification(exc.Message, false); - } - } - - - //If we got this far, something failed, redisplay form - PrepareCustomerModel(model, customer, true); - return View(model); - } - - [HttpPost, ActionName("Edit")] - [FormValueRequired("changepassword")] - public virtual ActionResult ChangePassword(CustomerModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); - - var customer = _customerService.GetCustomerById(model.Id); - if (customer == null) - //No customer found with the specified id - return RedirectToAction("List"); - - //ensure that the current customer cannot change passwords of "Administrators" if he's not an admin himself - if (customer.IsAdmin() && !_workContext.CurrentCustomer.IsAdmin()) - { - ErrorNotification(_localizationService.GetResource("Admin.Customers.Customers.OnlyAdminCanChangePassword")); - return RedirectToAction("Edit", new { id = customer.Id }); - } - - if (ModelState.IsValid) - { - var changePassRequest = new ChangePasswordRequest(model.Email, - false, _customerSettings.DefaultPasswordFormat, model.Password); - var changePassResult = _customerRegistrationService.ChangePassword(changePassRequest); - if (changePassResult.Success) - SuccessNotification(_localizationService.GetResource("Admin.Customers.Customers.PasswordChanged")); - else - foreach (var error in changePassResult.Errors) - ErrorNotification(error); - } - - return RedirectToAction("Edit", new {id = customer.Id}); - } - - [HttpPost, ActionName("Edit")] - [FormValueRequired("markVatNumberAsValid")] - public virtual ActionResult MarkVatNumberAsValid(CustomerModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); - - var customer = _customerService.GetCustomerById(model.Id); - if (customer == null) - //No customer found with the specified id - return RedirectToAction("List"); - - _genericAttributeService.SaveAttribute(customer, - SystemCustomerAttributeNames.VatNumberStatusId, - (int)VatNumberStatus.Valid); - - return RedirectToAction("Edit", new {id = customer.Id}); - } - - [HttpPost, ActionName("Edit")] - [FormValueRequired("markVatNumberAsInvalid")] - public virtual ActionResult MarkVatNumberAsInvalid(CustomerModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); - - var customer = _customerService.GetCustomerById(model.Id); - if (customer == null) - //No customer found with the specified id - return RedirectToAction("List"); - - _genericAttributeService.SaveAttribute(customer, - SystemCustomerAttributeNames.VatNumberStatusId, - (int)VatNumberStatus.Invalid); - - return RedirectToAction("Edit", new {id = customer.Id}); - } - - [HttpPost, ActionName("Edit")] - [FormValueRequired("remove-affiliate")] - public virtual ActionResult RemoveAffiliate(CustomerModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); - - var customer = _customerService.GetCustomerById(model.Id); - if (customer == null) - //No customer found with the specified id - return RedirectToAction("List"); - - customer.AffiliateId = 0; - _customerService.UpdateCustomer(customer); - - return RedirectToAction("Edit", new { id = customer.Id }); - } - - [HttpPost] - public virtual ActionResult Delete(int id) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); - - var customer = _customerService.GetCustomerById(id); - if (customer == null) - //No customer found with the specified id - return RedirectToAction("List"); - - try - { - //prevent attempts to delete the user, if it is the last active administrator - if (customer.IsAdmin() && !SecondAdminAccountExists(customer)) - { - ErrorNotification(_localizationService.GetResource("Admin.Customers.Customers.AdminAccountShouldExists.DeleteAdministrator")); - return RedirectToAction("Edit", new { id = customer.Id }); - } - - //ensure that the current customer cannot delete "Administrators" if he's not an admin himself - if (customer.IsAdmin() && !_workContext.CurrentCustomer.IsAdmin()) - { - ErrorNotification(_localizationService.GetResource("Admin.Customers.Customers.OnlyAdminCanDeleteAdmin")); - return RedirectToAction("Edit", new { id = customer.Id }); - } - - //delete - _customerService.DeleteCustomer(customer); - - //remove newsletter subscription (if exists) - foreach (var store in _storeService.GetAllStores()) - { - var subscription = _newsLetterSubscriptionService.GetNewsLetterSubscriptionByEmailAndStoreId(customer.Email, store.Id); - if (subscription != null) - _newsLetterSubscriptionService.DeleteNewsLetterSubscription(subscription); - } - - //activity log - _customerActivityService.InsertActivity("DeleteCustomer", _localizationService.GetResource("ActivityLog.DeleteCustomer"), customer.Id); - - SuccessNotification(_localizationService.GetResource("Admin.Customers.Customers.Deleted")); - return RedirectToAction("List"); - } - catch (Exception exc) - { - ErrorNotification(exc.Message); - return RedirectToAction("Edit", new { id = customer.Id }); - } - } - - [HttpPost, ActionName("Edit")] - [FormValueRequired("impersonate")] - public virtual ActionResult Impersonate(int id) - { - if (!_permissionService.Authorize(StandardPermissionProvider.AllowCustomerImpersonation)) - return AccessDeniedView(); - - var customer = _customerService.GetCustomerById(id); - if (customer == null) - //No customer found with the specified id - return RedirectToAction("List"); - - //ensure that a non-admin user cannot impersonate as an administrator - //otherwise, that user can simply impersonate as an administrator and gain additional administrative privileges - if (!_workContext.CurrentCustomer.IsAdmin() && customer.IsAdmin()) - { - ErrorNotification(_localizationService.GetResource("Admin.Customers.Customers.NonAdminNotImpersonateAsAdminError")); - return RedirectToAction("Edit", customer.Id); - } - - //activity log - _customerActivityService.InsertActivity("Impersonation.Started", _localizationService.GetResource("ActivityLog.Impersonation.Started.StoreOwner"), customer.Email, customer.Id); - _customerActivityService.InsertActivity(customer, "Impersonation.Started", _localizationService.GetResource("ActivityLog.Impersonation.Started.Customer"), _workContext.CurrentCustomer.Email, _workContext.CurrentCustomer.Id); - - //ensure login is not required - customer.RequireReLogin = false; - _customerService.UpdateCustomer(customer); - _genericAttributeService.SaveAttribute(_workContext.CurrentCustomer, SystemCustomerAttributeNames.ImpersonatedCustomerId, customer.Id); - - return RedirectToAction("Index", "Home", new { area = "" }); - } - - [HttpPost, ActionName("Edit")] - [FormValueRequired("send-welcome-message")] - public virtual ActionResult SendWelcomeMessage(CustomerModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); - - var customer = _customerService.GetCustomerById(model.Id); - if (customer == null) - //No customer found with the specified id - return RedirectToAction("List"); - - _workflowMessageService.SendCustomerWelcomeMessage(customer, _workContext.WorkingLanguage.Id); - - SuccessNotification(_localizationService.GetResource("Admin.Customers.Customers.SendWelcomeMessage.Success")); - - return RedirectToAction("Edit", new { id = customer.Id }); - } - - [HttpPost, ActionName("Edit")] - [FormValueRequired("resend-activation-message")] - public virtual ActionResult ReSendActivationMessage(CustomerModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); - - var customer = _customerService.GetCustomerById(model.Id); - if (customer == null) - //No customer found with the specified id - return RedirectToAction("List"); - - //email validation message - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.AccountActivationToken, Guid.NewGuid().ToString()); - _workflowMessageService.SendCustomerEmailValidationMessage(customer, _workContext.WorkingLanguage.Id); - - SuccessNotification(_localizationService.GetResource("Admin.Customers.Customers.ReSendActivationMessage.Success")); - - return RedirectToAction("Edit", new { id = customer.Id }); - } - - public virtual ActionResult SendEmail(CustomerModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); - - var customer = _customerService.GetCustomerById(model.Id); - if (customer == null) - //No customer found with the specified id - return RedirectToAction("List"); - - try - { - if (String.IsNullOrWhiteSpace(customer.Email)) - throw new NopException("Customer email is empty"); - if (!CommonHelper.IsValidEmail(customer.Email)) - throw new NopException("Customer email is not valid"); - if (String.IsNullOrWhiteSpace(model.SendEmail.Subject)) - throw new NopException("Email subject is empty"); - if (String.IsNullOrWhiteSpace(model.SendEmail.Body)) - throw new NopException("Email body is empty"); - - var emailAccount = _emailAccountService.GetEmailAccountById(_emailAccountSettings.DefaultEmailAccountId); - if (emailAccount == null) - emailAccount = _emailAccountService.GetAllEmailAccounts().FirstOrDefault(); - if (emailAccount == null) - throw new NopException("Email account can't be loaded"); - var email = new QueuedEmail - { - Priority = QueuedEmailPriority.High, - EmailAccountId = emailAccount.Id, - FromName = emailAccount.DisplayName, - From = emailAccount.Email, - ToName = customer.GetFullName(), - To = customer.Email, - Subject = model.SendEmail.Subject, - Body = model.SendEmail.Body, - CreatedOnUtc = DateTime.UtcNow, - DontSendBeforeDateUtc = (model.SendEmail.SendImmediately || !model.SendEmail.DontSendBeforeDate.HasValue) ? - null : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.SendEmail.DontSendBeforeDate.Value) - }; - _queuedEmailService.InsertQueuedEmail(email); - SuccessNotification(_localizationService.GetResource("Admin.Customers.Customers.SendEmail.Queued")); - } - catch (Exception exc) - { - ErrorNotification(exc.Message); - } - - return RedirectToAction("Edit", new { id = customer.Id }); - } - - public virtual ActionResult SendPm(CustomerModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); - - var customer = _customerService.GetCustomerById(model.Id); - if (customer == null) - //No customer found with the specified id - return RedirectToAction("List"); - - try - { - if (!_forumSettings.AllowPrivateMessages) - throw new NopException("Private messages are disabled"); - if (customer.IsGuest()) - throw new NopException("Customer should be registered"); - if (String.IsNullOrWhiteSpace(model.SendPm.Subject)) - throw new NopException("PM subject is empty"); - if (String.IsNullOrWhiteSpace(model.SendPm.Message)) - throw new NopException("PM message is empty"); - - - var privateMessage = new PrivateMessage - { - StoreId = _storeContext.CurrentStore.Id, - ToCustomerId = customer.Id, - FromCustomerId = _workContext.CurrentCustomer.Id, - Subject = model.SendPm.Subject, - Text = model.SendPm.Message, - IsDeletedByAuthor = false, - IsDeletedByRecipient = false, - IsRead = false, - CreatedOnUtc = DateTime.UtcNow - }; - - _forumService.InsertPrivateMessage(privateMessage); - SuccessNotification(_localizationService.GetResource("Admin.Customers.Customers.SendPM.Sent")); - } - catch (Exception exc) - { - ErrorNotification(exc.Message); - } - - return RedirectToAction("Edit", new { id = customer.Id }); - } - - #endregion - - #region Reward points history - - [HttpPost] - public virtual ActionResult RewardPointsHistorySelect(DataSourceRequest command, int customerId) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedKendoGridJson(); - - var customer = _customerService.GetCustomerById(customerId); - if (customer == null) - throw new ArgumentException("No customer found with the specified id"); - - var rewardPoints = _rewardPointService.GetRewardPointsHistory(customer.Id, true, true, command.Page - 1, command.PageSize); - var gridModel = new DataSourceResult - { - Data = rewardPoints.Select(rph => - { - var store = _storeService.GetStoreById(rph.StoreId); - var activatingDate = _dateTimeHelper.ConvertToUserTime(rph.CreatedOnUtc, DateTimeKind.Utc); - - return new CustomerModel.RewardPointsHistoryModel - { - StoreName = store != null ? store.Name : "Unknown", - Points = rph.Points, - PointsBalance = rph.PointsBalance.HasValue ? rph.PointsBalance.ToString() - : string.Format(_localizationService.GetResource("Admin.Customers.Customers.RewardPoints.ActivatedLater"), activatingDate), - Message = rph.Message, - CreatedOn = activatingDate - }; - }), - Total = rewardPoints.TotalCount - }; - - return Json(gridModel); - } - - [ValidateInput(false)] - public virtual ActionResult RewardPointsHistoryAdd(int customerId, int storeId, int addRewardPointsValue, string addRewardPointsMessage) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); - - var customer = _customerService.GetCustomerById(customerId); - if (customer == null) - return Json(new { Result = false }, JsonRequestBehavior.AllowGet); - - _rewardPointService.AddRewardPointsHistoryEntry(customer, - addRewardPointsValue, storeId, addRewardPointsMessage); - - return Json(new { Result = true }, JsonRequestBehavior.AllowGet); - } - - #endregion - - #region Addresses - - [HttpPost] - public virtual ActionResult AddressesSelect(int customerId, DataSourceRequest command) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedKendoGridJson(); - - var customer = _customerService.GetCustomerById(customerId); - if (customer == null) - throw new ArgumentException("No customer found with the specified id", "customerId"); - - var addresses = customer.Addresses.OrderByDescending(a => a.CreatedOnUtc).ThenByDescending(a => a.Id).ToList(); - var gridModel = new DataSourceResult - { - Data = addresses.Select(x => - { - var model = x.ToModel(); - var addressHtmlSb = new StringBuilder("
    "); - if (_addressSettings.CompanyEnabled && !String.IsNullOrEmpty(model.Company)) - addressHtmlSb.AppendFormat("{0}
    ", Server.HtmlEncode(model.Company)); - if (_addressSettings.StreetAddressEnabled && !String.IsNullOrEmpty(model.Address1)) - addressHtmlSb.AppendFormat("{0}
    ", Server.HtmlEncode(model.Address1)); - if (_addressSettings.StreetAddress2Enabled && !String.IsNullOrEmpty(model.Address2)) - addressHtmlSb.AppendFormat("{0}
    ", Server.HtmlEncode(model.Address2)); - if (_addressSettings.CityEnabled && !String.IsNullOrEmpty(model.City)) - addressHtmlSb.AppendFormat("{0},", Server.HtmlEncode(model.City)); - if (_addressSettings.StateProvinceEnabled && !String.IsNullOrEmpty(model.StateProvinceName)) - addressHtmlSb.AppendFormat("{0},", Server.HtmlEncode(model.StateProvinceName)); - if (_addressSettings.ZipPostalCodeEnabled && !String.IsNullOrEmpty(model.ZipPostalCode)) - addressHtmlSb.AppendFormat("{0}
    ", Server.HtmlEncode(model.ZipPostalCode)); - if (_addressSettings.CountryEnabled && !String.IsNullOrEmpty(model.CountryName)) - addressHtmlSb.AppendFormat("{0}", Server.HtmlEncode(model.CountryName)); - var customAttributesFormatted = _addressAttributeFormatter.FormatAttributes(x.CustomAttributes); - if (!String.IsNullOrEmpty(customAttributesFormatted)) - { - //already encoded - addressHtmlSb.AppendFormat("
    {0}", customAttributesFormatted); - } - addressHtmlSb.Append("
    "); - model.AddressHtml = addressHtmlSb.ToString(); - return model; - }), - Total = addresses.Count - }; - - return Json(gridModel); - } - - [HttpPost] - public virtual ActionResult AddressDelete(int id, int customerId) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); - - var customer = _customerService.GetCustomerById(customerId); - if (customer == null) - throw new ArgumentException("No customer found with the specified id", "customerId"); - - var address = customer.Addresses.FirstOrDefault(a => a.Id == id); - if (address == null) - //No customer found with the specified id - return Content("No customer found with the specified id"); - customer.RemoveAddress(address); - _customerService.UpdateCustomer(customer); - //now delete the address record - _addressService.DeleteAddress(address); - - return new NullJsonResult(); - } - - public virtual ActionResult AddressCreate(int customerId) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); - - var customer = _customerService.GetCustomerById(customerId); - if (customer == null) - //No customer found with the specified id - return RedirectToAction("List"); - - var model = new CustomerAddressModel(); - PrepareAddressModel(model, null, customer, false); - - return View(model); - } - - [HttpPost] - [ValidateInput(false)] - public virtual ActionResult AddressCreate(CustomerAddressModel model, FormCollection form) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); - - var customer = _customerService.GetCustomerById(model.CustomerId); - if (customer == null) - //No customer found with the specified id - return RedirectToAction("List"); - - //custom address attributes - var customAttributes = form.ParseCustomAddressAttributes(_addressAttributeParser, _addressAttributeService); - var customAttributeWarnings = _addressAttributeParser.GetAttributeWarnings(customAttributes); - foreach (var error in customAttributeWarnings) - { - ModelState.AddModelError("", error); - } - - if (ModelState.IsValid) - { - var address = model.Address.ToEntity(); - address.CustomAttributes = customAttributes; - address.CreatedOnUtc = DateTime.UtcNow; - //some validation - if (address.CountryId == 0) - address.CountryId = null; - if (address.StateProvinceId == 0) - address.StateProvinceId = null; - customer.Addresses.Add(address); - _customerService.UpdateCustomer(customer); - - SuccessNotification(_localizationService.GetResource("Admin.Customers.Customers.Addresses.Added")); - return RedirectToAction("AddressEdit", new { addressId = address.Id, customerId = model.CustomerId }); - } - - //If we got this far, something failed, redisplay form - PrepareAddressModel(model, null, customer, true); - return View(model); - } - - public virtual ActionResult AddressEdit(int addressId, int customerId) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); - - var customer = _customerService.GetCustomerById(customerId); - if (customer == null) - //No customer found with the specified id - return RedirectToAction("List"); - - var address = _addressService.GetAddressById(addressId); - if (address == null) - //No address found with the specified id - return RedirectToAction("Edit", new { id = customer.Id }); - - var model = new CustomerAddressModel(); - PrepareAddressModel(model, address, customer, false); - return View(model); - } - - [HttpPost] - [ValidateInput(false)] - public virtual ActionResult AddressEdit(CustomerAddressModel model, FormCollection form) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); - - var customer = _customerService.GetCustomerById(model.CustomerId); - if (customer == null) - //No customer found with the specified id - return RedirectToAction("List"); - - var address = _addressService.GetAddressById(model.Address.Id); - if (address == null) - //No address found with the specified id - return RedirectToAction("Edit", new { id = customer.Id }); - - //custom address attributes - var customAttributes = form.ParseCustomAddressAttributes(_addressAttributeParser, _addressAttributeService); - var customAttributeWarnings = _addressAttributeParser.GetAttributeWarnings(customAttributes); - foreach (var error in customAttributeWarnings) - { - ModelState.AddModelError("", error); - } - - if (ModelState.IsValid) - { - address = model.Address.ToEntity(address); - address.CustomAttributes = customAttributes; - _addressService.UpdateAddress(address); - - SuccessNotification(_localizationService.GetResource("Admin.Customers.Customers.Addresses.Updated")); - return RedirectToAction("AddressEdit", new { addressId = model.Address.Id, customerId = model.CustomerId }); - } - - //If we got this far, something failed, redisplay form - PrepareAddressModel(model, address, customer, true); - - return View(model); - } - - #endregion - - #region Orders - - [HttpPost] - public virtual ActionResult OrderList(int customerId, DataSourceRequest command) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedKendoGridJson(); - - var orders = _orderService.SearchOrders(customerId: customerId); - - var gridModel = new DataSourceResult - { - Data = orders.PagedForCommand(command) - .Select(order => - { - var store = _storeService.GetStoreById(order.StoreId); - var orderModel = new CustomerModel.OrderModel - { - Id = order.Id, - OrderStatus = order.OrderStatus.GetLocalizedEnum(_localizationService, _workContext), - OrderStatusId = order.OrderStatusId, - PaymentStatus = order.PaymentStatus.GetLocalizedEnum(_localizationService, _workContext), - ShippingStatus = order.ShippingStatus.GetLocalizedEnum(_localizationService, _workContext), - OrderTotal = _priceFormatter.FormatPrice(order.OrderTotal, true, false), - StoreName = store != null ? store.Name : "Unknown", - CreatedOn = _dateTimeHelper.ConvertToUserTime(order.CreatedOnUtc, DateTimeKind.Utc), - CustomOrderNumber = order.CustomOrderNumber - }; - return orderModel; - }), - Total = orders.Count - }; - - - return Json(gridModel); - } - - #endregion - - #region Reports - - public virtual ActionResult Reports() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); - - var model = new CustomerReportsModel(); - //customers by number of orders - model.BestCustomersByNumberOfOrders = new BestCustomersReportModel(); - model.BestCustomersByNumberOfOrders.AvailableOrderStatuses = OrderStatus.Pending.ToSelectList(false).ToList(); - model.BestCustomersByNumberOfOrders.AvailableOrderStatuses.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - model.BestCustomersByNumberOfOrders.AvailablePaymentStatuses = PaymentStatus.Pending.ToSelectList(false).ToList(); - model.BestCustomersByNumberOfOrders.AvailablePaymentStatuses.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - model.BestCustomersByNumberOfOrders.AvailableShippingStatuses = ShippingStatus.NotYetShipped.ToSelectList(false).ToList(); - model.BestCustomersByNumberOfOrders.AvailableShippingStatuses.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - - //customers by order total - model.BestCustomersByOrderTotal = new BestCustomersReportModel(); - model.BestCustomersByOrderTotal.AvailableOrderStatuses = OrderStatus.Pending.ToSelectList(false).ToList(); - model.BestCustomersByOrderTotal.AvailableOrderStatuses.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - model.BestCustomersByOrderTotal.AvailablePaymentStatuses = PaymentStatus.Pending.ToSelectList(false).ToList(); - model.BestCustomersByOrderTotal.AvailablePaymentStatuses.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - model.BestCustomersByOrderTotal.AvailableShippingStatuses = ShippingStatus.NotYetShipped.ToSelectList(false).ToList(); - model.BestCustomersByOrderTotal.AvailableShippingStatuses.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - - return View(model); - } - - [HttpPost] - public virtual ActionResult ReportBestCustomersByOrderTotalList(DataSourceRequest command, BestCustomersReportModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedKendoGridJson(); - - DateTime? startDateValue = (model.StartDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); - - DateTime? endDateValue = (model.EndDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); - - OrderStatus? orderStatus = model.OrderStatusId > 0 ? (OrderStatus?)(model.OrderStatusId) : null; - PaymentStatus? paymentStatus = model.PaymentStatusId > 0 ? (PaymentStatus?)(model.PaymentStatusId) : null; - ShippingStatus? shippingStatus = model.ShippingStatusId > 0 ? (ShippingStatus?)(model.ShippingStatusId) : null; - - - var items = _customerReportService.GetBestCustomersReport(startDateValue, endDateValue, - orderStatus, paymentStatus, shippingStatus, 1, command.Page - 1, command.PageSize); - var gridModel = new DataSourceResult - { - Data = items.Select(x => - { - var m = new BestCustomerReportLineModel - { - CustomerId = x.CustomerId, - OrderTotal = _priceFormatter.FormatPrice(x.OrderTotal, true, false), - OrderCount = x.OrderCount, - }; - var customer = _customerService.GetCustomerById(x.CustomerId); - if (customer != null) - { - m.CustomerName = customer.IsRegistered() ? customer.Email : _localizationService.GetResource("Admin.Customers.Guest"); - } - return m; - }), - Total = items.TotalCount - }; - - return Json(gridModel); - } - [HttpPost] - public virtual ActionResult ReportBestCustomersByNumberOfOrdersList(DataSourceRequest command, BestCustomersReportModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedKendoGridJson(); - - DateTime? startDateValue = (model.StartDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); - - DateTime? endDateValue = (model.EndDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); - - OrderStatus? orderStatus = model.OrderStatusId > 0 ? (OrderStatus?)(model.OrderStatusId) : null; - PaymentStatus? paymentStatus = model.PaymentStatusId > 0 ? (PaymentStatus?)(model.PaymentStatusId) : null; - ShippingStatus? shippingStatus = model.ShippingStatusId > 0 ? (ShippingStatus?)(model.ShippingStatusId) : null; - - - var items = _customerReportService.GetBestCustomersReport(startDateValue, endDateValue, - orderStatus, paymentStatus, shippingStatus, 2, command.Page - 1, command.PageSize); - var gridModel = new DataSourceResult - { - Data = items.Select(x => - { - var m = new BestCustomerReportLineModel - { - CustomerId = x.CustomerId, - OrderTotal = _priceFormatter.FormatPrice(x.OrderTotal, true, false), - OrderCount = x.OrderCount, - }; - var customer = _customerService.GetCustomerById(x.CustomerId); - if (customer != null) - { - m.CustomerName = customer.IsRegistered() ? customer.Email : _localizationService.GetResource("Admin.Customers.Guest"); - } - return m; - }), - Total = items.TotalCount - }; - - return Json(gridModel); - } - - [ChildActionOnly] - public virtual ActionResult ReportRegisteredCustomers() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return Content(""); - - return PartialView(); - } - - [HttpPost] - public virtual ActionResult ReportRegisteredCustomersList(DataSourceRequest command) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedKendoGridJson(); - - var model = GetReportRegisteredCustomersModel(); - var gridModel = new DataSourceResult - { - Data = model, - Total = model.Count - }; - - return Json(gridModel); - } - - [ChildActionOnly] - public virtual ActionResult CustomerStatistics() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return Content(""); - - //a vendor doesn't have access to this report - if (_workContext.CurrentVendor != null) - return Content(""); - - return PartialView(); - } - - [AcceptVerbs(HttpVerbs.Get)] - public virtual ActionResult LoadCustomerStatistics(string period) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return Content(""); - - var result = new List(); - - var nowDt = _dateTimeHelper.ConvertToUserTime(DateTime.Now); - var timeZone = _dateTimeHelper.CurrentTimeZone; - var searchCustomerRoleIds = new[] { _customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.Registered).Id }; - - var culture = new CultureInfo(_workContext.WorkingLanguage.LanguageCulture); - - switch (period) - { - case "year": - //year statistics - var yearAgoDt = nowDt.AddYears(-1).AddMonths(1); - var searchYearDateUser = new DateTime(yearAgoDt.Year, yearAgoDt.Month, 1); - if (!timeZone.IsInvalidTime(searchYearDateUser)) - { - for (int i = 0; i <= 12; i++) - { - result.Add(new - { - date = searchYearDateUser.Date.ToString("Y", culture), - value = _customerService.GetAllCustomers( - createdFromUtc: _dateTimeHelper.ConvertToUtcTime(searchYearDateUser, timeZone), - createdToUtc: _dateTimeHelper.ConvertToUtcTime(searchYearDateUser.AddMonths(1), timeZone), - customerRoleIds: searchCustomerRoleIds, - pageIndex: 0, - pageSize: 1).TotalCount.ToString() - }); - - searchYearDateUser = searchYearDateUser.AddMonths(1); - } - } - break; - - case "month": - //month statistics - var monthAgoDt = nowDt.AddDays(-30); - var searchMonthDateUser = new DateTime(monthAgoDt.Year, monthAgoDt.Month, monthAgoDt.Day); - if (!timeZone.IsInvalidTime(searchMonthDateUser)) - { - for (int i = 0; i <= 30; i++) - { - result.Add(new - { - date = searchMonthDateUser.Date.ToString("M", culture), - value = _customerService.GetAllCustomers( - createdFromUtc: _dateTimeHelper.ConvertToUtcTime(searchMonthDateUser, timeZone), - createdToUtc: _dateTimeHelper.ConvertToUtcTime(searchMonthDateUser.AddDays(1), timeZone), - customerRoleIds: searchCustomerRoleIds, - pageIndex: 0, - pageSize: 1).TotalCount.ToString() - }); - - searchMonthDateUser = searchMonthDateUser.AddDays(1); - } - } - break; - - case "week": - default: - //week statistics - var weekAgoDt = nowDt.AddDays(-7); - var searchWeekDateUser = new DateTime(weekAgoDt.Year, weekAgoDt.Month, weekAgoDt.Day); - if (!timeZone.IsInvalidTime(searchWeekDateUser)) - { - for (int i = 0; i <= 7; i++) - { - result.Add(new - { - date = searchWeekDateUser.Date.ToString("d dddd", culture), - value = _customerService.GetAllCustomers( - createdFromUtc: _dateTimeHelper.ConvertToUtcTime(searchWeekDateUser, timeZone), - createdToUtc: _dateTimeHelper.ConvertToUtcTime(searchWeekDateUser.AddDays(1), timeZone), - customerRoleIds: searchCustomerRoleIds, - pageIndex: 0, - pageSize: 1).TotalCount.ToString() - }); - - searchWeekDateUser = searchWeekDateUser.AddDays(1); - } - } - break; - } - - return Json(result, JsonRequestBehavior.AllowGet); - } - - #endregion - - #region Current shopping cart/ wishlist - - [HttpPost] - public virtual ActionResult GetCartList(int customerId, int cartTypeId) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedKendoGridJson(); - - var customer = _customerService.GetCustomerById(customerId); - var cart = customer.ShoppingCartItems.Where(x => x.ShoppingCartTypeId == cartTypeId).ToList(); - - var gridModel = new DataSourceResult - { - Data = cart.Select(sci => - { - decimal taxRate; - var store = _storeService.GetStoreById(sci.StoreId); - var sciModel = new ShoppingCartItemModel - { - Id = sci.Id, - Store = store != null ? store.Name : "Unknown", - ProductId = sci.ProductId, - Quantity = sci.Quantity, - ProductName = sci.Product.Name, - AttributeInfo = _productAttributeFormatter.FormatAttributes(sci.Product, sci.AttributesXml), - UnitPrice = _priceFormatter.FormatPrice(_taxService.GetProductPrice(sci.Product, _priceCalculationService.GetUnitPrice(sci), out taxRate)), - Total = _priceFormatter.FormatPrice(_taxService.GetProductPrice(sci.Product, _priceCalculationService.GetSubTotal(sci), out taxRate)), - UpdatedOn = _dateTimeHelper.ConvertToUserTime(sci.UpdatedOnUtc, DateTimeKind.Utc) - }; - return sciModel; - }), - Total = cart.Count - }; - - return Json(gridModel); - } - - #endregion - - #region Activity log - - [HttpPost] - public virtual ActionResult ListActivityLog(DataSourceRequest command, int customerId) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedKendoGridJson(); - - var activityLog = _customerActivityService.GetAllActivities(null, null, customerId, 0, command.Page - 1, command.PageSize); - var gridModel = new DataSourceResult - { - Data = activityLog.Select(x => - { - var m = new CustomerModel.ActivityLogModel - { - Id = x.Id, - ActivityLogTypeName = x.ActivityLogType.Name, - Comment = x.Comment, - CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc), - IpAddress = x.IpAddress - }; - return m; - - }), - Total = activityLog.TotalCount - }; - - return Json(gridModel); - } - - #endregion - - #region Back in stock subscriptions - - [HttpPost] - public virtual ActionResult BackInStockSubscriptionList(DataSourceRequest command, int customerId) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedKendoGridJson(); - - var subscriptions = _backInStockSubscriptionService.GetAllSubscriptionsByCustomerId(customerId, - 0, command.Page - 1, command.PageSize); - var gridModel = new DataSourceResult - { - Data = subscriptions.Select(x => - { - var store = _storeService.GetStoreById(x.StoreId); - var product = x.Product; - var m = new CustomerModel.BackInStockSubscriptionModel - { - Id = x.Id, - StoreName = store != null ? store.Name : "Unknown", - ProductId = x.ProductId, - ProductName = product != null ? product.Name : "Unknown", - CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc) - }; - return m; - - }), - Total = subscriptions.TotalCount - }; - - return Json(gridModel); - } - - #endregion - - #region Export / Import - - [HttpPost, ActionName("List")] - [FormValueRequired("exportexcel-all")] - public virtual ActionResult ExportExcelAll(CustomerListModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); - - var searchDayOfBirth = 0; - int searchMonthOfBirth = 0; - if (!String.IsNullOrWhiteSpace(model.SearchDayOfBirth)) - searchDayOfBirth = Convert.ToInt32(model.SearchDayOfBirth); - if (!String.IsNullOrWhiteSpace(model.SearchMonthOfBirth)) - searchMonthOfBirth = Convert.ToInt32(model.SearchMonthOfBirth); - - var customers = _customerService.GetAllCustomers( - customerRoleIds: model.SearchCustomerRoleIds.ToArray(), - email: model.SearchEmail, - username: model.SearchUsername, - firstName: model.SearchFirstName, - lastName: model.SearchLastName, - dayOfBirth: searchDayOfBirth, - monthOfBirth: searchMonthOfBirth, - company: model.SearchCompany, - phone: model.SearchPhone, - zipPostalCode: model.SearchZipPostalCode, - loadOnlyWithShoppingCart: false); - - try - { - byte[] bytes = _exportManager.ExportCustomersToXlsx(customers); - return File(bytes, MimeTypes.TextXlsx, "customers.xlsx"); - } - catch (Exception exc) - { - ErrorNotification(exc); - return RedirectToAction("List"); - } - } - - [HttpPost] - public virtual ActionResult ExportExcelSelected(string selectedIds) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); - - var customers = new List(); - if (selectedIds != null) - { - var ids = selectedIds - .Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => Convert.ToInt32(x)) - .ToArray(); - customers.AddRange(_customerService.GetCustomersByIds(ids)); - } - - try - { - byte[] bytes = _exportManager.ExportCustomersToXlsx(customers); - return File(bytes, MimeTypes.TextXlsx, "customers.xlsx"); - } - catch (Exception exc) - { - ErrorNotification(exc); - return RedirectToAction("List"); - } - } - - [HttpPost, ActionName("List")] - [FormValueRequired("exportxml-all")] - public virtual ActionResult ExportXmlAll(CustomerListModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); - - var searchDayOfBirth = 0; - int searchMonthOfBirth = 0; - if (!String.IsNullOrWhiteSpace(model.SearchDayOfBirth)) - searchDayOfBirth = Convert.ToInt32(model.SearchDayOfBirth); - if (!String.IsNullOrWhiteSpace(model.SearchMonthOfBirth)) - searchMonthOfBirth = Convert.ToInt32(model.SearchMonthOfBirth); - - var customers = _customerService.GetAllCustomers( - customerRoleIds: model.SearchCustomerRoleIds.ToArray(), - email: model.SearchEmail, - username: model.SearchUsername, - firstName: model.SearchFirstName, - lastName: model.SearchLastName, - dayOfBirth: searchDayOfBirth, - monthOfBirth: searchMonthOfBirth, - company: model.SearchCompany, - phone: model.SearchPhone, - zipPostalCode: model.SearchZipPostalCode, - loadOnlyWithShoppingCart: false); - - try - { - var xml = _exportManager.ExportCustomersToXml(customers); - return new XmlDownloadResult(xml, "customers.xml"); - } - catch (Exception exc) - { - ErrorNotification(exc); - return RedirectToAction("List"); - } - } - - [HttpPost] - public virtual ActionResult ExportXmlSelected(string selectedIds) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); - - var customers = new List(); - if (selectedIds != null) - { - var ids = selectedIds - .Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => Convert.ToInt32(x)) - .ToArray(); - customers.AddRange(_customerService.GetCustomersByIds(ids)); - } - - var xml = _exportManager.ExportCustomersToXml(customers); - return new XmlDownloadResult(xml, "customers.xml"); - } - - #endregion - } -} +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Web.Mvc; +using Nop.Admin.Extensions; +using Nop.Admin.Helpers; +using Nop.Admin.Models.Common; +using Nop.Admin.Models.Customers; +using Nop.Admin.Models.ShoppingCart; +using Nop.Core; +using Nop.Core.Caching; +using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Common; +using Nop.Core.Domain.Customers; +using Nop.Core.Domain.Directory; +using Nop.Core.Domain.Forums; +using Nop.Core.Domain.Messages; +using Nop.Core.Domain.Orders; +using Nop.Core.Domain.Payments; +using Nop.Core.Domain.Shipping; +using Nop.Core.Domain.Tax; +using Nop.Services; +using Nop.Services.Affiliates; +using Nop.Services.Authentication.External; +using Nop.Services.Catalog; +using Nop.Services.Common; +using Nop.Services.Customers; +using Nop.Services.Directory; +using Nop.Services.ExportImport; +using Nop.Services.Forums; +using Nop.Services.Helpers; +using Nop.Services.Localization; +using Nop.Services.Logging; +using Nop.Services.Messages; +using Nop.Services.Orders; +using Nop.Services.Security; +using Nop.Services.Stores; +using Nop.Services.Tax; +using Nop.Services.Vendors; +using Nop.Web.Framework; +using Nop.Web.Framework.Controllers; +using Nop.Web.Framework.Kendoui; +using Nop.Web.Framework.Mvc; + +namespace Nop.Admin.Controllers +{ + public partial class CustomerController : BaseAdminController + { + #region Fields + + private readonly ICustomerService _customerService; + private readonly INewsLetterSubscriptionService _newsLetterSubscriptionService; + private readonly IGenericAttributeService _genericAttributeService; + private readonly ICustomerRegistrationService _customerRegistrationService; + private readonly ICustomerReportService _customerReportService; + private readonly IDateTimeHelper _dateTimeHelper; + private readonly ILocalizationService _localizationService; + private readonly DateTimeSettings _dateTimeSettings; + private readonly TaxSettings _taxSettings; + private readonly RewardPointsSettings _rewardPointsSettings; + private readonly ICountryService _countryService; + private readonly IStateProvinceService _stateProvinceService; + private readonly IAddressService _addressService; + private readonly CustomerSettings _customerSettings; + private readonly ITaxService _taxService; + private readonly IWorkContext _workContext; + private readonly IVendorService _vendorService; + private readonly IStoreContext _storeContext; + private readonly IPriceFormatter _priceFormatter; + private readonly IOrderService _orderService; + private readonly IExportManager _exportManager; + private readonly ICustomerActivityService _customerActivityService; + private readonly IBackInStockSubscriptionService _backInStockSubscriptionService; + private readonly IPriceCalculationService _priceCalculationService; + private readonly IProductAttributeFormatter _productAttributeFormatter; + private readonly IPermissionService _permissionService; + private readonly IQueuedEmailService _queuedEmailService; + private readonly EmailAccountSettings _emailAccountSettings; + private readonly IEmailAccountService _emailAccountService; + private readonly ForumSettings _forumSettings; + private readonly IForumService _forumService; + private readonly IOpenAuthenticationService _openAuthenticationService; + private readonly AddressSettings _addressSettings; + private readonly IStoreService _storeService; + private readonly ICustomerAttributeParser _customerAttributeParser; + private readonly ICustomerAttributeService _customerAttributeService; + private readonly IAddressAttributeParser _addressAttributeParser; + private readonly IAddressAttributeService _addressAttributeService; + private readonly IAddressAttributeFormatter _addressAttributeFormatter; + private readonly IAffiliateService _affiliateService; + private readonly IWorkflowMessageService _workflowMessageService; + private readonly IRewardPointService _rewardPointService; + private readonly ICacheManager _cacheManager; + + #endregion + + #region Constructors + + public CustomerController(ICustomerService customerService, + INewsLetterSubscriptionService newsLetterSubscriptionService, + IGenericAttributeService genericAttributeService, + ICustomerRegistrationService customerRegistrationService, + ICustomerReportService customerReportService, + IDateTimeHelper dateTimeHelper, + ILocalizationService localizationService, + DateTimeSettings dateTimeSettings, + TaxSettings taxSettings, + RewardPointsSettings rewardPointsSettings, + ICountryService countryService, + IStateProvinceService stateProvinceService, + IAddressService addressService, + CustomerSettings customerSettings, + ITaxService taxService, + IWorkContext workContext, + IVendorService vendorService, + IStoreContext storeContext, + IPriceFormatter priceFormatter, + IOrderService orderService, + IExportManager exportManager, + ICustomerActivityService customerActivityService, + IBackInStockSubscriptionService backInStockSubscriptionService, + IPriceCalculationService priceCalculationService, + IProductAttributeFormatter productAttributeFormatter, + IPermissionService permissionService, + IQueuedEmailService queuedEmailService, + EmailAccountSettings emailAccountSettings, + IEmailAccountService emailAccountService, + ForumSettings forumSettings, + IForumService forumService, + IOpenAuthenticationService openAuthenticationService, + AddressSettings addressSettings, + IStoreService storeService, + ICustomerAttributeParser customerAttributeParser, + ICustomerAttributeService customerAttributeService, + IAddressAttributeParser addressAttributeParser, + IAddressAttributeService addressAttributeService, + IAddressAttributeFormatter addressAttributeFormatter, + IAffiliateService affiliateService, + IWorkflowMessageService workflowMessageService, + IRewardPointService rewardPointService, + ICacheManager cacheManager) + { + this._customerService = customerService; + this._newsLetterSubscriptionService = newsLetterSubscriptionService; + this._genericAttributeService = genericAttributeService; + this._customerRegistrationService = customerRegistrationService; + this._customerReportService = customerReportService; + this._dateTimeHelper = dateTimeHelper; + this._localizationService = localizationService; + this._dateTimeSettings = dateTimeSettings; + this._taxSettings = taxSettings; + this._rewardPointsSettings = rewardPointsSettings; + this._countryService = countryService; + this._stateProvinceService = stateProvinceService; + this._addressService = addressService; + this._customerSettings = customerSettings; + this._taxService = taxService; + this._workContext = workContext; + this._vendorService = vendorService; + this._storeContext = storeContext; + this._priceFormatter = priceFormatter; + this._orderService = orderService; + this._exportManager = exportManager; + this._customerActivityService = customerActivityService; + this._backInStockSubscriptionService = backInStockSubscriptionService; + this._priceCalculationService = priceCalculationService; + this._productAttributeFormatter = productAttributeFormatter; + this._permissionService = permissionService; + this._queuedEmailService = queuedEmailService; + this._emailAccountSettings = emailAccountSettings; + this._emailAccountService = emailAccountService; + this._forumSettings = forumSettings; + this._forumService = forumService; + this._openAuthenticationService = openAuthenticationService; + this._addressSettings = addressSettings; + this._storeService = storeService; + this._customerAttributeParser = customerAttributeParser; + this._customerAttributeService = customerAttributeService; + this._addressAttributeParser = addressAttributeParser; + this._addressAttributeService = addressAttributeService; + this._addressAttributeFormatter = addressAttributeFormatter; + this._affiliateService = affiliateService; + this._workflowMessageService = workflowMessageService; + this._rewardPointService = rewardPointService; + this._cacheManager = cacheManager; + } + + #endregion + + #region Utilities + + [NonAction] + protected virtual string GetCustomerRolesNames(IList customerRoles, string separator = ",") + { + var sb = new StringBuilder(); + for (int i = 0; i < customerRoles.Count; i++) + { + sb.Append(customerRoles[i].Name); + if (i != customerRoles.Count - 1) + { + sb.Append(separator); + sb.Append(" "); + } + } + return sb.ToString(); + } + + [NonAction] + protected virtual IList GetReportRegisteredCustomersModel() + { + var report = new List(); + report.Add(new RegisteredCustomerReportLineModel + { + Period = _localizationService.GetResource("Admin.Customers.Reports.RegisteredCustomers.Fields.Period.7days"), + Customers = _customerReportService.GetRegisteredCustomersReport(7) + }); + + report.Add(new RegisteredCustomerReportLineModel + { + Period = _localizationService.GetResource("Admin.Customers.Reports.RegisteredCustomers.Fields.Period.14days"), + Customers = _customerReportService.GetRegisteredCustomersReport(14) + }); + report.Add(new RegisteredCustomerReportLineModel + { + Period = _localizationService.GetResource("Admin.Customers.Reports.RegisteredCustomers.Fields.Period.month"), + Customers = _customerReportService.GetRegisteredCustomersReport(30) + }); + report.Add(new RegisteredCustomerReportLineModel + { + Period = _localizationService.GetResource("Admin.Customers.Reports.RegisteredCustomers.Fields.Period.year"), + Customers = _customerReportService.GetRegisteredCustomersReport(365) + }); + + return report; + } + + [NonAction] + protected virtual IList GetAssociatedExternalAuthRecords(Customer customer) + { + if (customer == null) + throw new ArgumentNullException("customer"); + + var result = new List(); + foreach (var record in _openAuthenticationService.GetExternalIdentifiersFor(customer)) + { + var method = _openAuthenticationService.LoadExternalAuthenticationMethodBySystemName(record.ProviderSystemName); + if (method == null) + continue; + + result.Add(new CustomerModel.AssociatedExternalAuthModel + { + Id = record.Id, + Email = record.Email, + ExternalIdentifier = record.ExternalIdentifier, + AuthMethodName = method.PluginDescriptor.FriendlyName + }); + } + + return result; + } + + [NonAction] + protected virtual CustomerModel PrepareCustomerModelForList(Customer customer) + { + return new CustomerModel + { + Id = customer.Id, + Email = customer.IsRegistered() ? customer.Email : _localizationService.GetResource("Admin.Customers.Guest"), + Username = customer.Username, + FullName = customer.GetFullName(), + Company = customer.GetAttribute(SystemCustomerAttributeNames.Company), + Phone = customer.GetAttribute(SystemCustomerAttributeNames.Phone), + ZipPostalCode = customer.GetAttribute(SystemCustomerAttributeNames.ZipPostalCode), + CustomerRoleNames = GetCustomerRolesNames(customer.CustomerRoles.ToList()), + Active = customer.Active, + CreatedOn = _dateTimeHelper.ConvertToUserTime(customer.CreatedOnUtc, DateTimeKind.Utc), + LastActivityDate = _dateTimeHelper.ConvertToUserTime(customer.LastActivityDateUtc, DateTimeKind.Utc), + }; + } + + [NonAction] + protected virtual string ValidateCustomerRoles(IList customerRoles) + { + if (customerRoles == null) + throw new ArgumentNullException("customerRoles"); + + //ensure a customer is not added to both 'Guests' and 'Registered' customer roles + //ensure that a customer is in at least one required role ('Guests' and 'Registered') + bool isInGuestsRole = customerRoles.FirstOrDefault(cr => cr.SystemName == SystemCustomerRoleNames.Guests) != null; + bool isInRegisteredRole = customerRoles.FirstOrDefault(cr => cr.SystemName == SystemCustomerRoleNames.Registered) != null; + if (isInGuestsRole && isInRegisteredRole) + return _localizationService.GetResource("Admin.Customers.Customers.GuestsAndRegisteredRolesError"); + if (!isInGuestsRole && !isInRegisteredRole) + return _localizationService.GetResource("Admin.Customers.Customers.AddCustomerToGuestsOrRegisteredRoleError"); + + //no errors + return ""; + } + + [NonAction] + protected virtual void PrepareVendorsModel(CustomerModel model) + { + if (model == null) + throw new ArgumentNullException("model"); + + model.AvailableVendors.Add(new SelectListItem + { + Text = _localizationService.GetResource("Admin.Customers.Customers.Fields.Vendor.None"), + Value = "0" + }); + var vendors = SelectListHelper.GetVendorList(_vendorService, _cacheManager, true); + foreach (var v in vendors) + model.AvailableVendors.Add(v); + } + + [NonAction] + protected virtual void PrepareCustomerAttributeModel(CustomerModel model, Customer customer) + { + var customerAttributes = _customerAttributeService.GetAllCustomerAttributes(); + foreach (var attribute in customerAttributes) + { + var attributeModel = new CustomerModel.CustomerAttributeModel + { + Id = attribute.Id, + Name = attribute.Name, + IsRequired = attribute.IsRequired, + AttributeControlType = attribute.AttributeControlType, + }; + + if (attribute.ShouldHaveValues()) + { + //values + var attributeValues = _customerAttributeService.GetCustomerAttributeValues(attribute.Id); + foreach (var attributeValue in attributeValues) + { + var attributeValueModel = new CustomerModel.CustomerAttributeValueModel + { + Id = attributeValue.Id, + Name = attributeValue.Name, + IsPreSelected = attributeValue.IsPreSelected + }; + attributeModel.Values.Add(attributeValueModel); + } + } + + + //set already selected attributes + if (customer != null) + { + var selectedCustomerAttributes = customer.GetAttribute(SystemCustomerAttributeNames.CustomCustomerAttributes, _genericAttributeService); + switch (attribute.AttributeControlType) + { + case AttributeControlType.DropdownList: + case AttributeControlType.RadioList: + case AttributeControlType.Checkboxes: + { + if (!String.IsNullOrEmpty(selectedCustomerAttributes)) + { + //clear default selection + foreach (var item in attributeModel.Values) + item.IsPreSelected = false; + + //select new values + var selectedValues = _customerAttributeParser.ParseCustomerAttributeValues(selectedCustomerAttributes); + foreach (var attributeValue in selectedValues) + foreach (var item in attributeModel.Values) + if (attributeValue.Id == item.Id) + item.IsPreSelected = true; + } + } + break; + case AttributeControlType.ReadonlyCheckboxes: + { + //do nothing + //values are already pre-set + } + break; + case AttributeControlType.TextBox: + case AttributeControlType.MultilineTextbox: + { + if (!String.IsNullOrEmpty(selectedCustomerAttributes)) + { + var enteredText = _customerAttributeParser.ParseValues(selectedCustomerAttributes, attribute.Id); + if (enteredText.Any()) + attributeModel.DefaultValue = enteredText[0]; + } + } + break; + case AttributeControlType.Datepicker: + case AttributeControlType.ColorSquares: + case AttributeControlType.ImageSquares: + case AttributeControlType.FileUpload: + default: + //not supported attribute control types + break; + } + } + + model.CustomerAttributes.Add(attributeModel); + } + } + + [NonAction] + protected virtual string ParseCustomCustomerAttributes( FormCollection form) + { + if (form == null) + throw new ArgumentNullException("form"); + + string attributesXml = ""; + var customerAttributes = _customerAttributeService.GetAllCustomerAttributes(); + foreach (var attribute in customerAttributes) + { + string controlId = string.Format("customer_attribute_{0}", attribute.Id); + switch (attribute.AttributeControlType) + { + case AttributeControlType.DropdownList: + case AttributeControlType.RadioList: + { + var ctrlAttributes = form[controlId]; + if (!String.IsNullOrEmpty(ctrlAttributes)) + { + int selectedAttributeId = int.Parse(ctrlAttributes); + if (selectedAttributeId > 0) + attributesXml = _customerAttributeParser.AddCustomerAttribute(attributesXml, + attribute, selectedAttributeId.ToString()); + } + } + break; + case AttributeControlType.Checkboxes: + { + var cblAttributes = form[controlId]; + if (!String.IsNullOrEmpty(cblAttributes)) + { + foreach (var item in cblAttributes.Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries)) + { + int selectedAttributeId = int.Parse(item); + if (selectedAttributeId > 0) + attributesXml = _customerAttributeParser.AddCustomerAttribute(attributesXml, + attribute, selectedAttributeId.ToString()); + } + } + } + break; + case AttributeControlType.ReadonlyCheckboxes: + { + //load read-only (already server-side selected) values + var attributeValues = _customerAttributeService.GetCustomerAttributeValues(attribute.Id); + foreach (var selectedAttributeId in attributeValues + .Where(v => v.IsPreSelected) + .Select(v => v.Id) + .ToList()) + { + attributesXml = _customerAttributeParser.AddCustomerAttribute(attributesXml, + attribute, selectedAttributeId.ToString()); + } + } + break; + case AttributeControlType.TextBox: + case AttributeControlType.MultilineTextbox: + { + var ctrlAttributes = form[controlId]; + if (!String.IsNullOrEmpty(ctrlAttributes)) + { + string enteredText = ctrlAttributes.Trim(); + attributesXml = _customerAttributeParser.AddCustomerAttribute(attributesXml, + attribute, enteredText); + } + } + break; + case AttributeControlType.Datepicker: + case AttributeControlType.ColorSquares: + case AttributeControlType.ImageSquares: + case AttributeControlType.FileUpload: + //not supported customer attributes + default: + break; + } + } + + return attributesXml; + } + + [NonAction] + protected virtual void PrepareCustomerModel(CustomerModel model, Customer customer, bool excludeProperties) + { + var allStores = _storeService.GetAllStores(); + if (customer != null) + { + model.Id = customer.Id; + if (!excludeProperties) + { + model.Email = customer.Email; + model.Username = customer.Username; + model.VendorId = customer.VendorId; + model.AdminComment = customer.AdminComment; + model.IsTaxExempt = customer.IsTaxExempt; + model.Active = customer.Active; + + if (customer.RegisteredInStoreId == 0 || allStores.All(s => s.Id != customer.RegisteredInStoreId)) + model.RegisteredInStore = string.Empty; + else + model.RegisteredInStore = allStores.First(s => s.Id == customer.RegisteredInStoreId).Name; + + var affiliate = _affiliateService.GetAffiliateById(customer.AffiliateId); + if (affiliate != null) + { + model.AffiliateId = affiliate.Id; + model.AffiliateName = affiliate.GetFullName(); + } + + model.TimeZoneId = customer.GetAttribute(SystemCustomerAttributeNames.TimeZoneId); + model.VatNumber = customer.GetAttribute(SystemCustomerAttributeNames.VatNumber); + model.VatNumberStatusNote = ((VatNumberStatus)customer.GetAttribute(SystemCustomerAttributeNames.VatNumberStatusId)) + .GetLocalizedEnum(_localizationService, _workContext); + model.CreatedOn = _dateTimeHelper.ConvertToUserTime(customer.CreatedOnUtc, DateTimeKind.Utc); + model.LastActivityDate = _dateTimeHelper.ConvertToUserTime(customer.LastActivityDateUtc, DateTimeKind.Utc); + model.LastIpAddress = customer.LastIpAddress; + model.LastVisitedPage = customer.GetAttribute(SystemCustomerAttributeNames.LastVisitedPage); + + model.SelectedCustomerRoleIds = customer.CustomerRoles.Select(cr => cr.Id).ToList(); + + //newsletter subscriptions + if (!String.IsNullOrEmpty(customer.Email)) + { + var newsletterSubscriptionStoreIds = new List(); + foreach (var store in allStores) + { + var newsletterSubscription = _newsLetterSubscriptionService + .GetNewsLetterSubscriptionByEmailAndStoreId(customer.Email, store.Id); + if (newsletterSubscription != null) + newsletterSubscriptionStoreIds.Add(store.Id); + model.SelectedNewsletterSubscriptionStoreIds = newsletterSubscriptionStoreIds.ToArray(); + } + } + + //form fields + model.FirstName = customer.GetAttribute(SystemCustomerAttributeNames.FirstName); + model.LastName = customer.GetAttribute(SystemCustomerAttributeNames.LastName); + model.Gender = customer.GetAttribute(SystemCustomerAttributeNames.Gender); + model.DateOfBirth = customer.GetAttribute(SystemCustomerAttributeNames.DateOfBirth); + model.Company = customer.GetAttribute(SystemCustomerAttributeNames.Company); + model.StreetAddress = customer.GetAttribute(SystemCustomerAttributeNames.StreetAddress); + model.StreetAddress2 = customer.GetAttribute(SystemCustomerAttributeNames.StreetAddress2); + model.ZipPostalCode = customer.GetAttribute(SystemCustomerAttributeNames.ZipPostalCode); + model.City = customer.GetAttribute(SystemCustomerAttributeNames.City); + model.CountryId = customer.GetAttribute(SystemCustomerAttributeNames.CountryId); + model.StateProvinceId = customer.GetAttribute(SystemCustomerAttributeNames.StateProvinceId); + model.Phone = customer.GetAttribute(SystemCustomerAttributeNames.Phone); + model.Fax = customer.GetAttribute(SystemCustomerAttributeNames.Fax); + } + } + + model.UsernamesEnabled = _customerSettings.UsernamesEnabled; + model.AllowCustomersToSetTimeZone = _dateTimeSettings.AllowCustomersToSetTimeZone; + foreach (var tzi in _dateTimeHelper.GetSystemTimeZones()) + model.AvailableTimeZones.Add(new SelectListItem { Text = tzi.DisplayName, Value = tzi.Id, Selected = (tzi.Id == model.TimeZoneId) }); + if (customer != null) + { + model.DisplayVatNumber = _taxSettings.EuVatEnabled; + } + else + { + model.DisplayVatNumber = false; + } + + //vendors + PrepareVendorsModel(model); + //customer attributes + PrepareCustomerAttributeModel(model, customer); + + model.GenderEnabled = _customerSettings.GenderEnabled; + model.DateOfBirthEnabled = _customerSettings.DateOfBirthEnabled; + model.CompanyEnabled = _customerSettings.CompanyEnabled; + model.StreetAddressEnabled = _customerSettings.StreetAddressEnabled; + model.StreetAddress2Enabled = _customerSettings.StreetAddress2Enabled; + model.ZipPostalCodeEnabled = _customerSettings.ZipPostalCodeEnabled; + model.CityEnabled = _customerSettings.CityEnabled; + model.CountryEnabled = _customerSettings.CountryEnabled; + model.StateProvinceEnabled = _customerSettings.StateProvinceEnabled; + model.PhoneEnabled = _customerSettings.PhoneEnabled; + model.FaxEnabled = _customerSettings.FaxEnabled; + + //countries and states + if (_customerSettings.CountryEnabled) + { + model.AvailableCountries.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.SelectCountry"), Value = "0" }); + foreach (var c in _countryService.GetAllCountries(showHidden: true)) + { + model.AvailableCountries.Add(new SelectListItem + { + Text = c.Name, + Value = c.Id.ToString(), + Selected = c.Id == model.CountryId + }); + } + + if (_customerSettings.StateProvinceEnabled) + { + //states + var states = _stateProvinceService.GetStateProvincesByCountryId(model.CountryId).ToList(); + if (states.Any()) + { + model.AvailableStates.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.SelectState"), Value = "0" }); + + foreach (var s in states) + { + model.AvailableStates.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString(), Selected = (s.Id == model.StateProvinceId) }); + } + } + else + { + bool anyCountrySelected = model.AvailableCountries.Any(x => x.Selected); + + model.AvailableStates.Add(new SelectListItem + { + Text = _localizationService.GetResource(anyCountrySelected ? "Admin.Address.OtherNonUS" : "Admin.Address.SelectState"), + Value = "0" + }); + } + } + } + + //newsletter subscriptions + model.AvailableNewsletterSubscriptionStores = allStores + .Select(s => new CustomerModel.StoreModel() {Id = s.Id, Name = s.Name }) + .ToList(); + + //customer roles + var allRoles = _customerService.GetAllCustomerRoles(true); + var adminRole = allRoles.FirstOrDefault(c => c.SystemName == SystemCustomerRoleNames.Registered); + //precheck Registered Role as a default role while creating a new customer through admin + if (customer == null && adminRole != null) + { + model.SelectedCustomerRoleIds.Add(adminRole.Id); + } + foreach (var role in allRoles) + { + model.AvailableCustomerRoles.Add(new SelectListItem + { + Text = role.Name, + Value = role.Id.ToString(), + Selected = model.SelectedCustomerRoleIds.Contains(role.Id) + }); + } + + //reward points history + if (customer != null) + { + model.DisplayRewardPointsHistory = _rewardPointsSettings.Enabled; + model.AddRewardPointsValue = 0; + model.AddRewardPointsValuePurchased = 0; + model.AddRewardPointsMessage = "Some comment here..."; + + //stores + foreach (var store in allStores) + { + model.RewardPointsAvailableStores.Add(new SelectListItem + { + Text = store.Name, + Value = store.Id.ToString(), + Selected = (store.Id == _storeContext.CurrentStore.Id) + }); + } + } + else + { + model.DisplayRewardPointsHistory = false; + } + //external authentication records + if (customer != null) + { + model.AssociatedExternalAuthRecords = GetAssociatedExternalAuthRecords(customer); + } + //sending of the welcome message: + //1. "admin approval" registration method + //2. already created customer + //3. registered + model.AllowSendingOfWelcomeMessage = _customerSettings.UserRegistrationType == UserRegistrationType.AdminApproval && + customer != null && + customer.IsRegistered(); + //sending of the activation message + //1. "email validation" registration method + //2. already created customer + //3. registered + //4. not active + model.AllowReSendingOfActivationMessage = _customerSettings.UserRegistrationType == UserRegistrationType.EmailValidation && + customer != null && + customer.IsRegistered() && + !customer.Active; + } + + [NonAction] + protected virtual void PrepareAddressModel(CustomerAddressModel model, Address address, Customer customer, bool excludeProperties) + { + if (customer == null) + throw new ArgumentNullException("customer"); + + model.CustomerId = customer.Id; + if (address != null) + { + if (!excludeProperties) + { + model.Address = address.ToModel(); + } + } + + if (model.Address == null) + model.Address = new AddressModel(); + + model.Address.FirstNameEnabled = true; + model.Address.FirstNameRequired = true; + model.Address.LastNameEnabled = true; + model.Address.LastNameRequired = true; + model.Address.EmailEnabled = true; + model.Address.EmailRequired = true; + model.Address.CompanyEnabled = _addressSettings.CompanyEnabled; + model.Address.CompanyRequired = _addressSettings.CompanyRequired; + model.Address.CountryEnabled = _addressSettings.CountryEnabled; + model.Address.CountryRequired = _addressSettings.CountryEnabled; //country is required when enabled + model.Address.StateProvinceEnabled = _addressSettings.StateProvinceEnabled; + model.Address.StateProvinceRequired = _addressSettings.StateProvinceEnabled; //province is required when enabled + model.Address.CityEnabled = _addressSettings.CityEnabled; + model.Address.CityRequired = _addressSettings.CityRequired; + model.Address.StreetAddressEnabled = _addressSettings.StreetAddressEnabled; + model.Address.StreetAddressRequired = _addressSettings.StreetAddressRequired; + model.Address.StreetAddress2Enabled = _addressSettings.StreetAddress2Enabled; + model.Address.StreetAddress2Required = _addressSettings.StreetAddress2Required; + model.Address.ZipPostalCodeEnabled = _addressSettings.ZipPostalCodeEnabled; + model.Address.ZipPostalCodeRequired = _addressSettings.ZipPostalCodeRequired; + model.Address.PhoneEnabled = _addressSettings.PhoneEnabled; + model.Address.PhoneRequired = _addressSettings.PhoneRequired; + model.Address.FaxEnabled = _addressSettings.FaxEnabled; + model.Address.FaxRequired = _addressSettings.FaxRequired; + //countries + model.Address.AvailableCountries.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.SelectCountry"), Value = "0" }); + foreach (var c in _countryService.GetAllCountries(showHidden: true)) + model.Address.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id.ToString(), Selected = (c.Id == model.Address.CountryId) }); + //states + var states = model.Address.CountryId.HasValue ? _stateProvinceService.GetStateProvincesByCountryId(model.Address.CountryId.Value, showHidden: true).ToList() : new List(); + if (states.Any()) + { + foreach (var s in states) + model.Address.AvailableStates.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString(), Selected = (s.Id == model.Address.StateProvinceId) }); + } + else + model.Address.AvailableStates.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.OtherNonUS"), Value = "0" }); + //customer attribute services + model.Address.PrepareCustomAddressAttributes(address, _addressAttributeService, _addressAttributeParser); + } + + [NonAction] + private bool SecondAdminAccountExists(Customer customer) + { + var customers = _customerService.GetAllCustomers(customerRoleIds: new[] {_customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.Administrators).Id}); + + return customers.Any(c => c.Active && c.Id != customer.Id); + } + #endregion + + #region Customers + + public virtual ActionResult Index() + { + return RedirectToAction("List"); + } + + public virtual ActionResult List() + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedView(); + + //load registered customers by default + var defaultRoleIds = new List {_customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.Registered).Id}; + var model = new CustomerListModel + { + UsernamesEnabled = _customerSettings.UsernamesEnabled, + DateOfBirthEnabled = _customerSettings.DateOfBirthEnabled, + CompanyEnabled = _customerSettings.CompanyEnabled, + PhoneEnabled = _customerSettings.PhoneEnabled, + ZipPostalCodeEnabled = _customerSettings.ZipPostalCodeEnabled, + SearchCustomerRoleIds = defaultRoleIds, + }; + var allRoles = _customerService.GetAllCustomerRoles(true); + foreach (var role in allRoles) + { + model.AvailableCustomerRoles.Add(new SelectListItem + { + Text = role.Name, + Value = role.Id.ToString(), + Selected = defaultRoleIds.Any(x => x == role.Id) + }); + } + + return View(model); + } + + [HttpPost] + public virtual ActionResult CustomerList(DataSourceRequest command, CustomerListModel model, + [ModelBinder(typeof(CommaSeparatedModelBinder))] int[] searchCustomerRoleIds) + { + //we use own own binder for searchCustomerRoleIds property + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedKendoGridJson(); + + var searchDayOfBirth = 0; + int searchMonthOfBirth = 0; + if (!String.IsNullOrWhiteSpace(model.SearchDayOfBirth)) + searchDayOfBirth = Convert.ToInt32(model.SearchDayOfBirth); + if (!String.IsNullOrWhiteSpace(model.SearchMonthOfBirth)) + searchMonthOfBirth = Convert.ToInt32(model.SearchMonthOfBirth); + + var customers = _customerService.GetAllCustomers( + customerRoleIds: searchCustomerRoleIds, + email: model.SearchEmail, + username: model.SearchUsername, + firstName: model.SearchFirstName, + lastName: model.SearchLastName, + dayOfBirth: searchDayOfBirth, + monthOfBirth: searchMonthOfBirth, + company: model.SearchCompany, + phone: model.SearchPhone, + zipPostalCode: model.SearchZipPostalCode, + ipAddress: model.SearchIpAddress, + loadOnlyWithShoppingCart: false, + pageIndex: command.Page - 1, + pageSize: command.PageSize); + var gridModel = new DataSourceResult + { + Data = customers.Select(PrepareCustomerModelForList), + Total = customers.TotalCount + }; + + return Json(gridModel); + } + + public virtual ActionResult Create() + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedView(); + + var model = new CustomerModel(); + PrepareCustomerModel(model, null, false); + //default value + model.Active = true; + return View(model); + } + + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] + [FormValueRequired("save", "save-continue")] + [ValidateInput(false)] + public virtual ActionResult Create(CustomerModel model, bool continueEditing, FormCollection form) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedView(); + + if (!String.IsNullOrWhiteSpace(model.Email)) + { + var cust2 = _customerService.GetCustomerByEmail(model.Email); + if (cust2 != null) + ModelState.AddModelError("", "Email is already registered"); + } + if (!String.IsNullOrWhiteSpace(model.Username) & _customerSettings.UsernamesEnabled) + { + var cust2 = _customerService.GetCustomerByUsername(model.Username); + if (cust2 != null) + ModelState.AddModelError("", "Username is already registered"); + } + + //validate customer roles + var allCustomerRoles = _customerService.GetAllCustomerRoles(true); + var newCustomerRoles = new List(); + foreach (var customerRole in allCustomerRoles) + if (model.SelectedCustomerRoleIds.Contains(customerRole.Id)) + newCustomerRoles.Add(customerRole); + var customerRolesError = ValidateCustomerRoles(newCustomerRoles); + if (!String.IsNullOrEmpty(customerRolesError)) + { + ModelState.AddModelError("", customerRolesError); + ErrorNotification(customerRolesError, false); + } + + // Ensure that valid email address is entered if Registered role is checked to avoid registered customers with empty email address + if (newCustomerRoles.Any() && newCustomerRoles.FirstOrDefault(c => c.SystemName == SystemCustomerRoleNames.Registered) != null && !CommonHelper.IsValidEmail(model.Email)) + { + ModelState.AddModelError("", _localizationService.GetResource("Admin.Customers.Customers.ValidEmailRequiredRegisteredRole")); + ErrorNotification(_localizationService.GetResource("Admin.Customers.Customers.ValidEmailRequiredRegisteredRole"), false); + } + + //custom customer attributes + var customerAttributesXml = ParseCustomCustomerAttributes(form); + if (newCustomerRoles.Any() && newCustomerRoles.FirstOrDefault(c => c.SystemName == SystemCustomerRoleNames.Registered) != null) + { + var customerAttributeWarnings = _customerAttributeParser.GetAttributeWarnings(customerAttributesXml); + foreach (var error in customerAttributeWarnings) + { + ModelState.AddModelError("", error); + } + } + + if (ModelState.IsValid) + { + var customer = new Customer + { + CustomerGuid = Guid.NewGuid(), + Email = model.Email, + Username = model.Username, + VendorId = model.VendorId, + AdminComment = model.AdminComment, + IsTaxExempt = model.IsTaxExempt, + Active = model.Active, + CreatedOnUtc = DateTime.UtcNow, + LastActivityDateUtc = DateTime.UtcNow, + RegisteredInStoreId = _storeContext.CurrentStore.Id + }; + _customerService.InsertCustomer(customer); + + //form fields + if (_dateTimeSettings.AllowCustomersToSetTimeZone) + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.TimeZoneId, model.TimeZoneId); + if (_customerSettings.GenderEnabled) + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Gender, model.Gender); + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.FirstName, model.FirstName); + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.LastName, model.LastName); + if (_customerSettings.DateOfBirthEnabled) + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.DateOfBirth, model.DateOfBirth); + if (_customerSettings.CompanyEnabled) + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Company, model.Company); + if (_customerSettings.StreetAddressEnabled) + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.StreetAddress, model.StreetAddress); + if (_customerSettings.StreetAddress2Enabled) + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.StreetAddress2, model.StreetAddress2); + if (_customerSettings.ZipPostalCodeEnabled) + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.ZipPostalCode, model.ZipPostalCode); + if (_customerSettings.CityEnabled) + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.City, model.City); + if (_customerSettings.CountryEnabled) + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.CountryId, model.CountryId); + if (_customerSettings.CountryEnabled && _customerSettings.StateProvinceEnabled) + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.StateProvinceId, model.StateProvinceId); + if (_customerSettings.PhoneEnabled) + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Phone, model.Phone); + if (_customerSettings.FaxEnabled) + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Fax, model.Fax); + + //custom customer attributes + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.CustomCustomerAttributes, customerAttributesXml); + + + //newsletter subscriptions + if (!String.IsNullOrEmpty(customer.Email)) + { + var allStores = _storeService.GetAllStores(); + foreach (var store in allStores) + { + var newsletterSubscription = _newsLetterSubscriptionService + .GetNewsLetterSubscriptionByEmailAndStoreId(customer.Email, store.Id); + if (model.SelectedNewsletterSubscriptionStoreIds != null && + model.SelectedNewsletterSubscriptionStoreIds.Contains(store.Id)) + { + //subscribed + if (newsletterSubscription == null) + { + _newsLetterSubscriptionService.InsertNewsLetterSubscription(new NewsLetterSubscription + { + NewsLetterSubscriptionGuid = Guid.NewGuid(), + Email = customer.Email, + Active = true, + StoreId = store.Id, + CreatedOnUtc = DateTime.UtcNow + }); + } + } + else + { + //not subscribed + if (newsletterSubscription != null) + { + _newsLetterSubscriptionService.DeleteNewsLetterSubscription(newsletterSubscription); + } + } + } + } + + //password + if (!String.IsNullOrWhiteSpace(model.Password)) + { + var changePassRequest = new ChangePasswordRequest(model.Email, false, _customerSettings.DefaultPasswordFormat, model.Password); + var changePassResult = _customerRegistrationService.ChangePassword(changePassRequest); + if (!changePassResult.Success) + { + foreach (var changePassError in changePassResult.Errors) + ErrorNotification(changePassError); + } + } + + //customer roles + foreach (var customerRole in newCustomerRoles) + { + //ensure that the current customer cannot add to "Administrators" system role if he's not an admin himself + if (customerRole.SystemName == SystemCustomerRoleNames.Administrators && + !_workContext.CurrentCustomer.IsAdmin()) + continue; + + customer.CustomerRoles.Add(customerRole); + } + _customerService.UpdateCustomer(customer); + + + //ensure that a customer with a vendor associated is not in "Administrators" role + //otherwise, he won't have access to other functionality in admin area + if (customer.IsAdmin() && customer.VendorId > 0) + { + customer.VendorId = 0; + _customerService.UpdateCustomer(customer); + ErrorNotification(_localizationService.GetResource("Admin.Customers.Customers.AdminCouldNotbeVendor")); + } + + //ensure that a customer in the Vendors role has a vendor account associated. + //otherwise, he will have access to ALL products + if (customer.IsVendor() && customer.VendorId == 0) + { + var vendorRole = customer + .CustomerRoles + .FirstOrDefault(x => x.SystemName == SystemCustomerRoleNames.Vendors); + customer.CustomerRoles.Remove(vendorRole); + _customerService.UpdateCustomer(customer); + ErrorNotification(_localizationService.GetResource("Admin.Customers.Customers.CannotBeInVendoRoleWithoutVendorAssociated")); + } + + //activity log + _customerActivityService.InsertActivity("AddNewCustomer", _localizationService.GetResource("ActivityLog.AddNewCustomer"), customer.Id); + + SuccessNotification(_localizationService.GetResource("Admin.Customers.Customers.Added")); + + if (continueEditing) + { + //selected tab + SaveSelectedTabName(); + + return RedirectToAction("Edit", new {id = customer.Id}); + } + return RedirectToAction("List"); + } + + //If we got this far, something failed, redisplay form + PrepareCustomerModel(model, null, true); + return View(model); + } + + public virtual ActionResult Edit(int id) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedView(); + + var customer = _customerService.GetCustomerById(id); + if (customer == null || customer.Deleted) + //No customer found with the specified id + return RedirectToAction("List"); + + var model = new CustomerModel(); + PrepareCustomerModel(model, customer, false); + return View(model); + } + + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] + [FormValueRequired("save", "save-continue")] + [ValidateInput(false)] + public virtual ActionResult Edit(CustomerModel model, bool continueEditing, FormCollection form) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedView(); + + var customer = _customerService.GetCustomerById(model.Id); + if (customer == null || customer.Deleted) + //No customer found with the specified id + return RedirectToAction("List"); + + //validate customer roles + var allCustomerRoles = _customerService.GetAllCustomerRoles(true); + var newCustomerRoles = new List(); + foreach (var customerRole in allCustomerRoles) + if (model.SelectedCustomerRoleIds.Contains(customerRole.Id)) + newCustomerRoles.Add(customerRole); + var customerRolesError = ValidateCustomerRoles(newCustomerRoles); + if (!String.IsNullOrEmpty(customerRolesError)) + { + ModelState.AddModelError("", customerRolesError); + ErrorNotification(customerRolesError, false); + } + + // Ensure that valid email address is entered if Registered role is checked to avoid registered customers with empty email address + if (newCustomerRoles.Any() && newCustomerRoles.FirstOrDefault(c => c.SystemName == SystemCustomerRoleNames.Registered) != null && !CommonHelper.IsValidEmail(model.Email)) + { + ModelState.AddModelError("", _localizationService.GetResource("Admin.Customers.Customers.ValidEmailRequiredRegisteredRole")); + ErrorNotification(_localizationService.GetResource("Admin.Customers.Customers.ValidEmailRequiredRegisteredRole"), false); + } + + //custom customer attributes + var customerAttributesXml = ParseCustomCustomerAttributes(form); + if (newCustomerRoles.Any() && newCustomerRoles.FirstOrDefault(c => c.SystemName == SystemCustomerRoleNames.Registered) != null) + { + var customerAttributeWarnings = _customerAttributeParser.GetAttributeWarnings(customerAttributesXml); + foreach (var error in customerAttributeWarnings) + { + ModelState.AddModelError("", error); + } + } + + if (ModelState.IsValid) + { + try + { + customer.AdminComment = model.AdminComment; + customer.IsTaxExempt = model.IsTaxExempt; + + //prevent deactivation of the last active administrator + if (!customer.IsAdmin() || model.Active || SecondAdminAccountExists(customer)) + customer.Active = model.Active; + else + ErrorNotification(_localizationService.GetResource("Admin.Customers.Customers.AdminAccountShouldExists.Deactivate")); + + //email + if (!String.IsNullOrWhiteSpace(model.Email)) + { + _customerRegistrationService.SetEmail(customer, model.Email, false); + } + else + { + customer.Email = model.Email; + } + + //username + if (_customerSettings.UsernamesEnabled) + { + if (!String.IsNullOrWhiteSpace(model.Username)) + { + _customerRegistrationService.SetUsername(customer, model.Username); + } + else + { + customer.Username = model.Username; + } + } + + //VAT number + if (_taxSettings.EuVatEnabled) + { + var prevVatNumber = customer.GetAttribute(SystemCustomerAttributeNames.VatNumber); + + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.VatNumber, model.VatNumber); + //set VAT number status + if (!String.IsNullOrEmpty(model.VatNumber)) + { + if (!model.VatNumber.Equals(prevVatNumber, StringComparison.InvariantCultureIgnoreCase)) + { + _genericAttributeService.SaveAttribute(customer, + SystemCustomerAttributeNames.VatNumberStatusId, + (int)_taxService.GetVatNumberStatus(model.VatNumber)); + } + } + else + { + _genericAttributeService.SaveAttribute(customer, + SystemCustomerAttributeNames.VatNumberStatusId, + (int)VatNumberStatus.Empty); + } + } + + //vendor + customer.VendorId = model.VendorId; + + //form fields + if (_dateTimeSettings.AllowCustomersToSetTimeZone) + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.TimeZoneId, model.TimeZoneId); + if (_customerSettings.GenderEnabled) + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Gender, model.Gender); + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.FirstName, model.FirstName); + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.LastName, model.LastName); + if (_customerSettings.DateOfBirthEnabled) + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.DateOfBirth, model.DateOfBirth); + if (_customerSettings.CompanyEnabled) + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Company, model.Company); + if (_customerSettings.StreetAddressEnabled) + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.StreetAddress, model.StreetAddress); + if (_customerSettings.StreetAddress2Enabled) + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.StreetAddress2, model.StreetAddress2); + if (_customerSettings.ZipPostalCodeEnabled) + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.ZipPostalCode, model.ZipPostalCode); + if (_customerSettings.CityEnabled) + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.City, model.City); + if (_customerSettings.CountryEnabled) + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.CountryId, model.CountryId); + if (_customerSettings.CountryEnabled && _customerSettings.StateProvinceEnabled) + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.StateProvinceId, model.StateProvinceId); + if (_customerSettings.PhoneEnabled) + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Phone, model.Phone); + if (_customerSettings.FaxEnabled) + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Fax, model.Fax); + + //custom customer attributes + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.CustomCustomerAttributes, customerAttributesXml); + + //newsletter subscriptions + if (!String.IsNullOrEmpty(customer.Email)) + { + var allStores = _storeService.GetAllStores(); + foreach (var store in allStores) + { + var newsletterSubscription = _newsLetterSubscriptionService + .GetNewsLetterSubscriptionByEmailAndStoreId(customer.Email, store.Id); + if (model.SelectedNewsletterSubscriptionStoreIds != null && + model.SelectedNewsletterSubscriptionStoreIds.Contains(store.Id)) + { + //subscribed + if (newsletterSubscription == null) + { + _newsLetterSubscriptionService.InsertNewsLetterSubscription(new NewsLetterSubscription + { + NewsLetterSubscriptionGuid = Guid.NewGuid(), + Email = customer.Email, + Active = true, + StoreId = store.Id, + CreatedOnUtc = DateTime.UtcNow + }); + } + } + else + { + //not subscribed + if (newsletterSubscription != null) + { + _newsLetterSubscriptionService.DeleteNewsLetterSubscription(newsletterSubscription); + } + } + } + } + + + //customer roles + foreach (var customerRole in allCustomerRoles) + { + //ensure that the current customer cannot add/remove to/from "Administrators" system role + //if he's not an admin himself + if (customerRole.SystemName == SystemCustomerRoleNames.Administrators && + !_workContext.CurrentCustomer.IsAdmin()) + continue; + + if (model.SelectedCustomerRoleIds.Contains(customerRole.Id)) + { + //new role + if (customer.CustomerRoles.Count(cr => cr.Id == customerRole.Id) == 0) + customer.CustomerRoles.Add(customerRole); + } + else + { + //prevent attempts to delete the administrator role from the user, if the user is the last active administrator + if (customerRole.SystemName == SystemCustomerRoleNames.Administrators && !SecondAdminAccountExists(customer)) + { + ErrorNotification(_localizationService.GetResource("Admin.Customers.Customers.AdminAccountShouldExists.DeleteRole")); + continue; + } + + //remove role + if (customer.CustomerRoles.Count(cr => cr.Id == customerRole.Id) > 0) + customer.CustomerRoles.Remove(customerRole); + } + } + _customerService.UpdateCustomer(customer); + + + //ensure that a customer with a vendor associated is not in "Administrators" role + //otherwise, he won't have access to the other functionality in admin area + if (customer.IsAdmin() && customer.VendorId > 0) + { + customer.VendorId = 0; + _customerService.UpdateCustomer(customer); + ErrorNotification(_localizationService.GetResource("Admin.Customers.Customers.AdminCouldNotbeVendor")); + } + + //ensure that a customer in the Vendors role has a vendor account associated. + //otherwise, he will have access to ALL products + if (customer.IsVendor() && customer.VendorId == 0) + { + var vendorRole = customer + .CustomerRoles + .FirstOrDefault(x => x.SystemName == SystemCustomerRoleNames.Vendors); + customer.CustomerRoles.Remove(vendorRole); + _customerService.UpdateCustomer(customer); + ErrorNotification(_localizationService.GetResource("Admin.Customers.Customers.CannotBeInVendoRoleWithoutVendorAssociated")); + } + + + //activity log + _customerActivityService.InsertActivity("EditCustomer", _localizationService.GetResource("ActivityLog.EditCustomer"), customer.Id); + + SuccessNotification(_localizationService.GetResource("Admin.Customers.Customers.Updated")); + if (continueEditing) + { + //selected tab + SaveSelectedTabName(); + + return RedirectToAction("Edit", new {id = customer.Id}); + } + return RedirectToAction("List"); + } + catch (Exception exc) + { + ErrorNotification(exc.Message, false); + } + } + + + //If we got this far, something failed, redisplay form + PrepareCustomerModel(model, customer, true); + return View(model); + } + + [HttpPost, ActionName("Edit")] + [FormValueRequired("changepassword")] + public virtual ActionResult ChangePassword(CustomerModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedView(); + + var customer = _customerService.GetCustomerById(model.Id); + if (customer == null) + //No customer found with the specified id + return RedirectToAction("List"); + + //ensure that the current customer cannot change passwords of "Administrators" if he's not an admin himself + if (customer.IsAdmin() && !_workContext.CurrentCustomer.IsAdmin()) + { + ErrorNotification(_localizationService.GetResource("Admin.Customers.Customers.OnlyAdminCanChangePassword")); + return RedirectToAction("Edit", new { id = customer.Id }); + } + + if (ModelState.IsValid) + { + var changePassRequest = new ChangePasswordRequest(model.Email, + false, _customerSettings.DefaultPasswordFormat, model.Password); + var changePassResult = _customerRegistrationService.ChangePassword(changePassRequest); + if (changePassResult.Success) + SuccessNotification(_localizationService.GetResource("Admin.Customers.Customers.PasswordChanged")); + else + foreach (var error in changePassResult.Errors) + ErrorNotification(error); + } + + return RedirectToAction("Edit", new {id = customer.Id}); + } + + [HttpPost, ActionName("Edit")] + [FormValueRequired("markVatNumberAsValid")] + public virtual ActionResult MarkVatNumberAsValid(CustomerModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedView(); + + var customer = _customerService.GetCustomerById(model.Id); + if (customer == null) + //No customer found with the specified id + return RedirectToAction("List"); + + _genericAttributeService.SaveAttribute(customer, + SystemCustomerAttributeNames.VatNumberStatusId, + (int)VatNumberStatus.Valid); + + return RedirectToAction("Edit", new {id = customer.Id}); + } + + [HttpPost, ActionName("Edit")] + [FormValueRequired("markVatNumberAsInvalid")] + public virtual ActionResult MarkVatNumberAsInvalid(CustomerModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedView(); + + var customer = _customerService.GetCustomerById(model.Id); + if (customer == null) + //No customer found with the specified id + return RedirectToAction("List"); + + _genericAttributeService.SaveAttribute(customer, + SystemCustomerAttributeNames.VatNumberStatusId, + (int)VatNumberStatus.Invalid); + + return RedirectToAction("Edit", new {id = customer.Id}); + } + + [HttpPost, ActionName("Edit")] + [FormValueRequired("remove-affiliate")] + public virtual ActionResult RemoveAffiliate(CustomerModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedView(); + + var customer = _customerService.GetCustomerById(model.Id); + if (customer == null) + //No customer found with the specified id + return RedirectToAction("List"); + + customer.AffiliateId = 0; + _customerService.UpdateCustomer(customer); + + return RedirectToAction("Edit", new { id = customer.Id }); + } + + [HttpPost] + public virtual ActionResult Delete(int id) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedView(); + + var customer = _customerService.GetCustomerById(id); + if (customer == null) + //No customer found with the specified id + return RedirectToAction("List"); + + try + { + //prevent attempts to delete the user, if it is the last active administrator + if (customer.IsAdmin() && !SecondAdminAccountExists(customer)) + { + ErrorNotification(_localizationService.GetResource("Admin.Customers.Customers.AdminAccountShouldExists.DeleteAdministrator")); + return RedirectToAction("Edit", new { id = customer.Id }); + } + + //ensure that the current customer cannot delete "Administrators" if he's not an admin himself + if (customer.IsAdmin() && !_workContext.CurrentCustomer.IsAdmin()) + { + ErrorNotification(_localizationService.GetResource("Admin.Customers.Customers.OnlyAdminCanDeleteAdmin")); + return RedirectToAction("Edit", new { id = customer.Id }); + } + + //delete + _customerService.DeleteCustomer(customer); + + //remove newsletter subscription (if exists) + foreach (var store in _storeService.GetAllStores()) + { + var subscription = _newsLetterSubscriptionService.GetNewsLetterSubscriptionByEmailAndStoreId(customer.Email, store.Id); + if (subscription != null) + _newsLetterSubscriptionService.DeleteNewsLetterSubscription(subscription); + } + + //activity log + _customerActivityService.InsertActivity("DeleteCustomer", _localizationService.GetResource("ActivityLog.DeleteCustomer"), customer.Id); + + SuccessNotification(_localizationService.GetResource("Admin.Customers.Customers.Deleted")); + return RedirectToAction("List"); + } + catch (Exception exc) + { + ErrorNotification(exc.Message); + return RedirectToAction("Edit", new { id = customer.Id }); + } + } + + [HttpPost, ActionName("Edit")] + [FormValueRequired("impersonate")] + public virtual ActionResult Impersonate(int id) + { + if (!_permissionService.Authorize(StandardPermissionProvider.AllowCustomerImpersonation)) + return AccessDeniedView(); + + var customer = _customerService.GetCustomerById(id); + if (customer == null) + //No customer found with the specified id + return RedirectToAction("List"); + + //ensure that a non-admin user cannot impersonate as an administrator + //otherwise, that user can simply impersonate as an administrator and gain additional administrative privileges + if (!_workContext.CurrentCustomer.IsAdmin() && customer.IsAdmin()) + { + ErrorNotification(_localizationService.GetResource("Admin.Customers.Customers.NonAdminNotImpersonateAsAdminError")); + return RedirectToAction("Edit", customer.Id); + } + + //activity log + _customerActivityService.InsertActivity("Impersonation.Started", _localizationService.GetResource("ActivityLog.Impersonation.Started.StoreOwner"), customer.Email, customer.Id); + _customerActivityService.InsertActivity(customer, "Impersonation.Started", _localizationService.GetResource("ActivityLog.Impersonation.Started.Customer"), _workContext.CurrentCustomer.Email, _workContext.CurrentCustomer.Id); + + //ensure login is not required + customer.RequireReLogin = false; + _customerService.UpdateCustomer(customer); + _genericAttributeService.SaveAttribute(_workContext.CurrentCustomer, SystemCustomerAttributeNames.ImpersonatedCustomerId, customer.Id); + + return RedirectToAction("Index", "Home", new { area = "" }); + } + + [HttpPost, ActionName("Edit")] + [FormValueRequired("send-welcome-message")] + public virtual ActionResult SendWelcomeMessage(CustomerModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedView(); + + var customer = _customerService.GetCustomerById(model.Id); + if (customer == null) + //No customer found with the specified id + return RedirectToAction("List"); + + _workflowMessageService.SendCustomerWelcomeMessage(customer, _workContext.WorkingLanguage.Id); + + SuccessNotification(_localizationService.GetResource("Admin.Customers.Customers.SendWelcomeMessage.Success")); + + return RedirectToAction("Edit", new { id = customer.Id }); + } + + [HttpPost, ActionName("Edit")] + [FormValueRequired("resend-activation-message")] + public virtual ActionResult ReSendActivationMessage(CustomerModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedView(); + + var customer = _customerService.GetCustomerById(model.Id); + if (customer == null) + //No customer found with the specified id + return RedirectToAction("List"); + + //email validation message + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.AccountActivationToken, Guid.NewGuid().ToString()); + _workflowMessageService.SendCustomerEmailValidationMessage(customer, _workContext.WorkingLanguage.Id); + + SuccessNotification(_localizationService.GetResource("Admin.Customers.Customers.ReSendActivationMessage.Success")); + + return RedirectToAction("Edit", new { id = customer.Id }); + } + + public virtual ActionResult SendEmail(CustomerModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedView(); + + var customer = _customerService.GetCustomerById(model.Id); + if (customer == null) + //No customer found with the specified id + return RedirectToAction("List"); + + try + { + if (String.IsNullOrWhiteSpace(customer.Email)) + throw new NopException("Customer email is empty"); + if (!CommonHelper.IsValidEmail(customer.Email)) + throw new NopException("Customer email is not valid"); + if (String.IsNullOrWhiteSpace(model.SendEmail.Subject)) + throw new NopException("Email subject is empty"); + if (String.IsNullOrWhiteSpace(model.SendEmail.Body)) + throw new NopException("Email body is empty"); + + var emailAccount = _emailAccountService.GetEmailAccountById(_emailAccountSettings.DefaultEmailAccountId); + if (emailAccount == null) + emailAccount = _emailAccountService.GetAllEmailAccounts().FirstOrDefault(); + if (emailAccount == null) + throw new NopException("Email account can't be loaded"); + var email = new QueuedEmail + { + Priority = QueuedEmailPriority.High, + EmailAccountId = emailAccount.Id, + FromName = emailAccount.DisplayName, + From = emailAccount.Email, + ToName = customer.GetFullName(), + To = customer.Email, + Subject = model.SendEmail.Subject, + Body = model.SendEmail.Body, + CreatedOnUtc = DateTime.UtcNow, + DontSendBeforeDateUtc = (model.SendEmail.SendImmediately || !model.SendEmail.DontSendBeforeDate.HasValue) ? + null : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.SendEmail.DontSendBeforeDate.Value) + }; + _queuedEmailService.InsertQueuedEmail(email); + SuccessNotification(_localizationService.GetResource("Admin.Customers.Customers.SendEmail.Queued")); + } + catch (Exception exc) + { + ErrorNotification(exc.Message); + } + + return RedirectToAction("Edit", new { id = customer.Id }); + } + + public virtual ActionResult SendPm(CustomerModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedView(); + + var customer = _customerService.GetCustomerById(model.Id); + if (customer == null) + //No customer found with the specified id + return RedirectToAction("List"); + + try + { + if (!_forumSettings.AllowPrivateMessages) + throw new NopException("Private messages are disabled"); + if (customer.IsGuest()) + throw new NopException("Customer should be registered"); + if (String.IsNullOrWhiteSpace(model.SendPm.Subject)) + throw new NopException("PM subject is empty"); + if (String.IsNullOrWhiteSpace(model.SendPm.Message)) + throw new NopException("PM message is empty"); + + + var privateMessage = new PrivateMessage + { + StoreId = _storeContext.CurrentStore.Id, + ToCustomerId = customer.Id, + FromCustomerId = _workContext.CurrentCustomer.Id, + Subject = model.SendPm.Subject, + Text = model.SendPm.Message, + IsDeletedByAuthor = false, + IsDeletedByRecipient = false, + IsRead = false, + CreatedOnUtc = DateTime.UtcNow + }; + + _forumService.InsertPrivateMessage(privateMessage); + SuccessNotification(_localizationService.GetResource("Admin.Customers.Customers.SendPM.Sent")); + } + catch (Exception exc) + { + ErrorNotification(exc.Message); + } + + return RedirectToAction("Edit", new { id = customer.Id }); + } + + #endregion + + #region Reward points history + + [HttpPost] + public virtual ActionResult RewardPointsHistorySelect(DataSourceRequest command, int customerId) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedKendoGridJson(); + + var customer = _customerService.GetCustomerById(customerId); + if (customer == null) + throw new ArgumentException("No customer found with the specified id"); + + var rewardPoints = _rewardPointService.GetRewardPointsHistory(customer.Id, true, true, command.Page - 1, command.PageSize); + var gridModel = new DataSourceResult + { + Data = rewardPoints.Select(rph => + { + var store = _storeService.GetStoreById(rph.StoreId); + var activatingDate = _dateTimeHelper.ConvertToUserTime(rph.CreatedOnUtc, DateTimeKind.Utc); + + return new CustomerModel.RewardPointsHistoryModel + { + Id = rph.Id, + StoreName = store != null ? store.Name : "Unknown", + Points = rph.Points, + PointsPurchased = rph.PointsPurchased, + PointsBalance = rph.PointsBalance.HasValue ? rph.PointsBalance.ToString() + : string.Format(_localizationService.GetResource("Admin.Customers.Customers.RewardPoints.ActivatedLater"), activatingDate), + PointsBalancePurchased = rph.PointsBalancePurchased.HasValue ? rph.PointsBalancePurchased.ToString() + : string.Format(_localizationService.GetResource("Admin.Customers.Customers.RewardPoints.ActivatedLater"), activatingDate), + Message = rph.Message, + CreatedOn = activatingDate + }; + }), + Total = rewardPoints.TotalCount + }; + + return Json(gridModel); + } + + [ValidateInput(false)] + public virtual ActionResult RewardPointsHistoryAdd(int customerId, int storeId, int[] addRewardPointsValue, string addRewardPointsMessage) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedView(); + + var customer = _customerService.GetCustomerById(customerId); + if (customer == null) + return Json(new { Result = false }, JsonRequestBehavior.AllowGet); + + var addRewardPoints = new RewardPoints(_rewardPointService) + { + Points = addRewardPointsValue[0], + PointsPurchased = addRewardPointsValue[1], + }; + + _rewardPointService.AddRewardPointsHistoryEntry(customer, + addRewardPoints, storeId, addRewardPointsMessage); + + //activity log + _customerActivityService.InsertActivity("AddCustomerRewardPoints", _localizationService.GetResource("ActivityLog.AddCustomerRewardPoints"), addRewardPointsValue[0], addRewardPointsValue[1]); + + return Json(new { Result = true }, JsonRequestBehavior.AllowGet); + } + + [HttpPost] + [ValidateInput(false)] + public virtual ActionResult RewardPointsHistoryUpdate(CustomerModel.RewardPointsHistoryModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedView(); + + if (model.Message != null) + model.Message = model.Message.Trim(); + + var rph = _rewardPointService.GetRewardPointsHistoryEntryById(model.Id); + if (rph == null) + return Content("No reward points history entry could be loaded with the specified ID"); + + rph.Message = model.Message; + + _rewardPointService.UpdateRewardPointsHistoryEntry(rph); + + //activity log + _customerActivityService.InsertActivity("EditCustomerRewardPoints", _localizationService.GetResource("ActivityLog.EditCustomerRewardPoints"), rph.Id); + + return new NullJsonResult(); + } + #endregion + + #region Addresses + + [HttpPost] + public virtual ActionResult AddressesSelect(int customerId, DataSourceRequest command) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedKendoGridJson(); + + var customer = _customerService.GetCustomerById(customerId); + if (customer == null) + throw new ArgumentException("No customer found with the specified id", "customerId"); + + var addresses = customer.Addresses.OrderByDescending(a => a.CreatedOnUtc).ThenByDescending(a => a.Id).ToList(); + var gridModel = new DataSourceResult + { + Data = addresses.Select(x => + { + var model = x.ToModel(); + var addressHtmlSb = new StringBuilder("
    "); + if (_addressSettings.CompanyEnabled && !String.IsNullOrEmpty(model.Company)) + addressHtmlSb.AppendFormat("{0}
    ", Server.HtmlEncode(model.Company)); + if (_addressSettings.StreetAddressEnabled && !String.IsNullOrEmpty(model.Address1)) + addressHtmlSb.AppendFormat("{0}
    ", Server.HtmlEncode(model.Address1)); + if (_addressSettings.StreetAddress2Enabled && !String.IsNullOrEmpty(model.Address2)) + addressHtmlSb.AppendFormat("{0}
    ", Server.HtmlEncode(model.Address2)); + if (_addressSettings.CityEnabled && !String.IsNullOrEmpty(model.City)) + addressHtmlSb.AppendFormat("{0},", Server.HtmlEncode(model.City)); + if (_addressSettings.StateProvinceEnabled && !String.IsNullOrEmpty(model.StateProvinceName)) + addressHtmlSb.AppendFormat("{0},", Server.HtmlEncode(model.StateProvinceName)); + if (_addressSettings.ZipPostalCodeEnabled && !String.IsNullOrEmpty(model.ZipPostalCode)) + addressHtmlSb.AppendFormat("{0}
    ", Server.HtmlEncode(model.ZipPostalCode)); + if (_addressSettings.CountryEnabled && !String.IsNullOrEmpty(model.CountryName)) + addressHtmlSb.AppendFormat("{0}", Server.HtmlEncode(model.CountryName)); + var customAttributesFormatted = _addressAttributeFormatter.FormatAttributes(x.CustomAttributes); + if (!String.IsNullOrEmpty(customAttributesFormatted)) + { + //already encoded + addressHtmlSb.AppendFormat("
    {0}", customAttributesFormatted); + } + addressHtmlSb.Append("
    "); + model.AddressHtml = addressHtmlSb.ToString(); + return model; + }), + Total = addresses.Count + }; + + return Json(gridModel); + } + + [HttpPost] + public virtual ActionResult AddressDelete(int id, int customerId) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedView(); + + var customer = _customerService.GetCustomerById(customerId); + if (customer == null) + throw new ArgumentException("No customer found with the specified id", "customerId"); + + var address = customer.Addresses.FirstOrDefault(a => a.Id == id); + if (address == null) + //No customer found with the specified id + return Content("No customer found with the specified id"); + customer.RemoveAddress(address); + _customerService.UpdateCustomer(customer); + //now delete the address record + _addressService.DeleteAddress(address); + + return new NullJsonResult(); + } + + public virtual ActionResult AddressCreate(int customerId) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedView(); + + var customer = _customerService.GetCustomerById(customerId); + if (customer == null) + //No customer found with the specified id + return RedirectToAction("List"); + + var model = new CustomerAddressModel(); + PrepareAddressModel(model, null, customer, false); + + return View(model); + } + + [HttpPost] + [ValidateInput(false)] + public virtual ActionResult AddressCreate(CustomerAddressModel model, FormCollection form) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedView(); + + var customer = _customerService.GetCustomerById(model.CustomerId); + if (customer == null) + //No customer found with the specified id + return RedirectToAction("List"); + + //custom address attributes + var customAttributes = form.ParseCustomAddressAttributes(_addressAttributeParser, _addressAttributeService); + var customAttributeWarnings = _addressAttributeParser.GetAttributeWarnings(customAttributes); + foreach (var error in customAttributeWarnings) + { + ModelState.AddModelError("", error); + } + + if (ModelState.IsValid) + { + var address = model.Address.ToEntity(); + address.CustomAttributes = customAttributes; + address.CreatedOnUtc = DateTime.UtcNow; + //some validation + if (address.CountryId == 0) + address.CountryId = null; + if (address.StateProvinceId == 0) + address.StateProvinceId = null; + customer.Addresses.Add(address); + _customerService.UpdateCustomer(customer); + + SuccessNotification(_localizationService.GetResource("Admin.Customers.Customers.Addresses.Added")); + return RedirectToAction("AddressEdit", new { addressId = address.Id, customerId = model.CustomerId }); + } + + //If we got this far, something failed, redisplay form + PrepareAddressModel(model, null, customer, true); + return View(model); + } + + public virtual ActionResult AddressEdit(int addressId, int customerId) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedView(); + + var customer = _customerService.GetCustomerById(customerId); + if (customer == null) + //No customer found with the specified id + return RedirectToAction("List"); + + var address = _addressService.GetAddressById(addressId); + if (address == null) + //No address found with the specified id + return RedirectToAction("Edit", new { id = customer.Id }); + + var model = new CustomerAddressModel(); + PrepareAddressModel(model, address, customer, false); + return View(model); + } + + [HttpPost] + [ValidateInput(false)] + public virtual ActionResult AddressEdit(CustomerAddressModel model, FormCollection form) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedView(); + + var customer = _customerService.GetCustomerById(model.CustomerId); + if (customer == null) + //No customer found with the specified id + return RedirectToAction("List"); + + var address = _addressService.GetAddressById(model.Address.Id); + if (address == null) + //No address found with the specified id + return RedirectToAction("Edit", new { id = customer.Id }); + + //custom address attributes + var customAttributes = form.ParseCustomAddressAttributes(_addressAttributeParser, _addressAttributeService); + var customAttributeWarnings = _addressAttributeParser.GetAttributeWarnings(customAttributes); + foreach (var error in customAttributeWarnings) + { + ModelState.AddModelError("", error); + } + + if (ModelState.IsValid) + { + address = model.Address.ToEntity(address); + address.CustomAttributes = customAttributes; + _addressService.UpdateAddress(address); + + SuccessNotification(_localizationService.GetResource("Admin.Customers.Customers.Addresses.Updated")); + return RedirectToAction("AddressEdit", new { addressId = model.Address.Id, customerId = model.CustomerId }); + } + + //If we got this far, something failed, redisplay form + PrepareAddressModel(model, address, customer, true); + + return View(model); + } + + #endregion + + #region Orders + + [HttpPost] + public virtual ActionResult OrderList(int customerId, DataSourceRequest command) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedKendoGridJson(); + + var orders = _orderService.SearchOrders(customerId: customerId); + + var gridModel = new DataSourceResult + { + Data = orders.PagedForCommand(command) + .Select(order => + { + var store = _storeService.GetStoreById(order.StoreId); + var orderModel = new CustomerModel.OrderModel + { + Id = order.Id, + OrderStatus = order.OrderStatus.GetLocalizedEnum(_localizationService, _workContext), + OrderStatusId = order.OrderStatusId, + PaymentStatus = order.PaymentStatus.GetLocalizedEnum(_localizationService, _workContext), + ShippingStatus = order.ShippingStatus.GetLocalizedEnum(_localizationService, _workContext), + OrderTotal = _priceFormatter.FormatPrice(order.OrderTotal, true, false), + StoreName = store != null ? store.Name : "Unknown", + CreatedOn = _dateTimeHelper.ConvertToUserTime(order.CreatedOnUtc, DateTimeKind.Utc), + CustomOrderNumber = order.CustomOrderNumber + }; + return orderModel; + }), + Total = orders.Count + }; + + + return Json(gridModel); + } + + #endregion + + #region Reports + + public virtual ActionResult Reports() + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedView(); + + var model = new CustomerReportsModel(); + //customers by number of orders + model.BestCustomersByNumberOfOrders = new BestCustomersReportModel(); + model.BestCustomersByNumberOfOrders.AvailableOrderStatuses = OrderStatus.Pending.ToSelectList(false).ToList(); + model.BestCustomersByNumberOfOrders.AvailableOrderStatuses.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + model.BestCustomersByNumberOfOrders.AvailablePaymentStatuses = PaymentStatus.Pending.ToSelectList(false).ToList(); + model.BestCustomersByNumberOfOrders.AvailablePaymentStatuses.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + model.BestCustomersByNumberOfOrders.AvailableShippingStatuses = ShippingStatus.NotYetShipped.ToSelectList(false).ToList(); + model.BestCustomersByNumberOfOrders.AvailableShippingStatuses.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + + //customers by order total + model.BestCustomersByOrderTotal = new BestCustomersReportModel(); + model.BestCustomersByOrderTotal.AvailableOrderStatuses = OrderStatus.Pending.ToSelectList(false).ToList(); + model.BestCustomersByOrderTotal.AvailableOrderStatuses.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + model.BestCustomersByOrderTotal.AvailablePaymentStatuses = PaymentStatus.Pending.ToSelectList(false).ToList(); + model.BestCustomersByOrderTotal.AvailablePaymentStatuses.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + model.BestCustomersByOrderTotal.AvailableShippingStatuses = ShippingStatus.NotYetShipped.ToSelectList(false).ToList(); + model.BestCustomersByOrderTotal.AvailableShippingStatuses.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + + return View(model); + } + + [HttpPost] + public virtual ActionResult ReportBestCustomersByOrderTotalList(DataSourceRequest command, BestCustomersReportModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedKendoGridJson(); + + DateTime? startDateValue = (model.StartDate == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); + + DateTime? endDateValue = (model.EndDate == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); + + OrderStatus? orderStatus = model.OrderStatusId > 0 ? (OrderStatus?)(model.OrderStatusId) : null; + PaymentStatus? paymentStatus = model.PaymentStatusId > 0 ? (PaymentStatus?)(model.PaymentStatusId) : null; + ShippingStatus? shippingStatus = model.ShippingStatusId > 0 ? (ShippingStatus?)(model.ShippingStatusId) : null; + + + var items = _customerReportService.GetBestCustomersReport(startDateValue, endDateValue, + orderStatus, paymentStatus, shippingStatus, 1, command.Page - 1, command.PageSize); + var gridModel = new DataSourceResult + { + Data = items.Select(x => + { + var m = new BestCustomerReportLineModel + { + CustomerId = x.CustomerId, + OrderTotal = _priceFormatter.FormatPrice(x.OrderTotal, true, false), + OrderCount = x.OrderCount, + }; + var customer = _customerService.GetCustomerById(x.CustomerId); + if (customer != null) + { + m.CustomerName = customer.IsRegistered() ? customer.Email : _localizationService.GetResource("Admin.Customers.Guest"); + } + return m; + }), + Total = items.TotalCount + }; + + return Json(gridModel); + } + [HttpPost] + public virtual ActionResult ReportBestCustomersByNumberOfOrdersList(DataSourceRequest command, BestCustomersReportModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedKendoGridJson(); + + DateTime? startDateValue = (model.StartDate == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); + + DateTime? endDateValue = (model.EndDate == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); + + OrderStatus? orderStatus = model.OrderStatusId > 0 ? (OrderStatus?)(model.OrderStatusId) : null; + PaymentStatus? paymentStatus = model.PaymentStatusId > 0 ? (PaymentStatus?)(model.PaymentStatusId) : null; + ShippingStatus? shippingStatus = model.ShippingStatusId > 0 ? (ShippingStatus?)(model.ShippingStatusId) : null; + + + var items = _customerReportService.GetBestCustomersReport(startDateValue, endDateValue, + orderStatus, paymentStatus, shippingStatus, 2, command.Page - 1, command.PageSize); + var gridModel = new DataSourceResult + { + Data = items.Select(x => + { + var m = new BestCustomerReportLineModel + { + CustomerId = x.CustomerId, + OrderTotal = _priceFormatter.FormatPrice(x.OrderTotal, true, false), + OrderCount = x.OrderCount, + }; + var customer = _customerService.GetCustomerById(x.CustomerId); + if (customer != null) + { + m.CustomerName = customer.IsRegistered() ? customer.Email : _localizationService.GetResource("Admin.Customers.Guest"); + } + return m; + }), + Total = items.TotalCount + }; + + return Json(gridModel); + } + + [ChildActionOnly] + public virtual ActionResult ReportRegisteredCustomers() + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return Content(""); + + return PartialView(); + } + + [HttpPost] + public virtual ActionResult ReportRegisteredCustomersList(DataSourceRequest command) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedKendoGridJson(); + + var model = GetReportRegisteredCustomersModel(); + var gridModel = new DataSourceResult + { + Data = model, + Total = model.Count + }; + + return Json(gridModel); + } + + [ChildActionOnly] + public virtual ActionResult CustomerStatistics() + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + return Content(""); + + //a vendor doesn't have access to this report + if (_workContext.CurrentVendor != null) + return Content(""); + + return PartialView(); + } + + [AcceptVerbs(HttpVerbs.Get)] + public virtual ActionResult LoadCustomerStatistics(string period) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return Content(""); + + var result = new List(); + + var nowDt = _dateTimeHelper.ConvertToUserTime(DateTime.Now); + var timeZone = _dateTimeHelper.CurrentTimeZone; + var searchCustomerRoleIds = new[] { _customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.Registered).Id }; + + var culture = new CultureInfo(_workContext.WorkingLanguage.LanguageCulture); + + switch (period) + { + case "year": + //year statistics + var yearAgoDt = nowDt.AddYears(-1).AddMonths(1); + var searchYearDateUser = new DateTime(yearAgoDt.Year, yearAgoDt.Month, 1); + if (!timeZone.IsInvalidTime(searchYearDateUser)) + { + for (int i = 0; i <= 12; i++) + { + result.Add(new + { + date = searchYearDateUser.Date.ToString("Y", culture), + value = _customerService.GetAllCustomers( + createdFromUtc: _dateTimeHelper.ConvertToUtcTime(searchYearDateUser, timeZone), + createdToUtc: _dateTimeHelper.ConvertToUtcTime(searchYearDateUser.AddMonths(1), timeZone), + customerRoleIds: searchCustomerRoleIds, + pageIndex: 0, + pageSize: 1).TotalCount.ToString() + }); + + searchYearDateUser = searchYearDateUser.AddMonths(1); + } + } + break; + + case "month": + //month statistics + var monthAgoDt = nowDt.AddDays(-30); + var searchMonthDateUser = new DateTime(monthAgoDt.Year, monthAgoDt.Month, monthAgoDt.Day); + if (!timeZone.IsInvalidTime(searchMonthDateUser)) + { + for (int i = 0; i <= 30; i++) + { + result.Add(new + { + date = searchMonthDateUser.Date.ToString("M", culture), + value = _customerService.GetAllCustomers( + createdFromUtc: _dateTimeHelper.ConvertToUtcTime(searchMonthDateUser, timeZone), + createdToUtc: _dateTimeHelper.ConvertToUtcTime(searchMonthDateUser.AddDays(1), timeZone), + customerRoleIds: searchCustomerRoleIds, + pageIndex: 0, + pageSize: 1).TotalCount.ToString() + }); + + searchMonthDateUser = searchMonthDateUser.AddDays(1); + } + } + break; + + case "week": + default: + //week statistics + var weekAgoDt = nowDt.AddDays(-7); + var searchWeekDateUser = new DateTime(weekAgoDt.Year, weekAgoDt.Month, weekAgoDt.Day); + if (!timeZone.IsInvalidTime(searchWeekDateUser)) + { + for (int i = 0; i <= 7; i++) + { + result.Add(new + { + date = searchWeekDateUser.Date.ToString("d dddd", culture), + value = _customerService.GetAllCustomers( + createdFromUtc: _dateTimeHelper.ConvertToUtcTime(searchWeekDateUser, timeZone), + createdToUtc: _dateTimeHelper.ConvertToUtcTime(searchWeekDateUser.AddDays(1), timeZone), + customerRoleIds: searchCustomerRoleIds, + pageIndex: 0, + pageSize: 1).TotalCount.ToString() + }); + + searchWeekDateUser = searchWeekDateUser.AddDays(1); + } + } + break; + } + + return Json(result, JsonRequestBehavior.AllowGet); + } + + #endregion + + #region Current shopping cart/ wishlist + + [HttpPost] + public virtual ActionResult GetCartList(int customerId, int cartTypeId) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedKendoGridJson(); + + var customer = _customerService.GetCustomerById(customerId); + var cart = customer.ShoppingCartItems.Where(x => x.ShoppingCartTypeId == cartTypeId).ToList(); + + var gridModel = new DataSourceResult + { + Data = cart.Select(sci => + { + decimal taxRate; + var store = _storeService.GetStoreById(sci.StoreId); + var sciModel = new ShoppingCartItemModel + { + Id = sci.Id, + Store = store != null ? store.Name : "Unknown", + ProductId = sci.ProductId, + Quantity = sci.Quantity, + ProductName = sci.Product.Name, + AttributeInfo = _productAttributeFormatter.FormatAttributes(sci.Product, sci.AttributesXml), + UnitPrice = _priceFormatter.FormatPrice(_taxService.GetProductPrice(sci.Product, _priceCalculationService.GetUnitPrice(sci), out taxRate)), + Total = _priceFormatter.FormatPrice(_taxService.GetProductPrice(sci.Product, _priceCalculationService.GetSubTotal(sci), out taxRate)), + UpdatedOn = _dateTimeHelper.ConvertToUserTime(sci.UpdatedOnUtc, DateTimeKind.Utc) + }; + return sciModel; + }), + Total = cart.Count + }; + + return Json(gridModel); + } + + #endregion + + #region Activity log + + [HttpPost] + public virtual ActionResult ListActivityLog(DataSourceRequest command, int customerId) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedKendoGridJson(); + + var activityLog = _customerActivityService.GetAllActivities(null, null, customerId, 0, command.Page - 1, command.PageSize); + var gridModel = new DataSourceResult + { + Data = activityLog.Select(x => + { + var m = new CustomerModel.ActivityLogModel + { + Id = x.Id, + ActivityLogTypeName = x.ActivityLogType.Name, + Comment = x.Comment, + CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc), + IpAddress = x.IpAddress + }; + return m; + + }), + Total = activityLog.TotalCount + }; + + return Json(gridModel); + } + + #endregion + + #region Back in stock subscriptions + + [HttpPost] + public virtual ActionResult BackInStockSubscriptionList(DataSourceRequest command, int customerId) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedKendoGridJson(); + + var subscriptions = _backInStockSubscriptionService.GetAllSubscriptionsByCustomerId(customerId, + 0, command.Page - 1, command.PageSize); + var gridModel = new DataSourceResult + { + Data = subscriptions.Select(x => + { + var store = _storeService.GetStoreById(x.StoreId); + var product = x.Product; + var m = new CustomerModel.BackInStockSubscriptionModel + { + Id = x.Id, + StoreName = store != null ? store.Name : "Unknown", + ProductId = x.ProductId, + ProductName = product != null ? product.Name : "Unknown", + CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc) + }; + return m; + + }), + Total = subscriptions.TotalCount + }; + + return Json(gridModel); + } + + #endregion + + #region Export / Import + + [HttpPost, ActionName("List")] + [FormValueRequired("exportexcel-all")] + public virtual ActionResult ExportExcelAll(CustomerListModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedView(); + + var searchDayOfBirth = 0; + int searchMonthOfBirth = 0; + if (!String.IsNullOrWhiteSpace(model.SearchDayOfBirth)) + searchDayOfBirth = Convert.ToInt32(model.SearchDayOfBirth); + if (!String.IsNullOrWhiteSpace(model.SearchMonthOfBirth)) + searchMonthOfBirth = Convert.ToInt32(model.SearchMonthOfBirth); + + var customers = _customerService.GetAllCustomers( + customerRoleIds: model.SearchCustomerRoleIds.ToArray(), + email: model.SearchEmail, + username: model.SearchUsername, + firstName: model.SearchFirstName, + lastName: model.SearchLastName, + dayOfBirth: searchDayOfBirth, + monthOfBirth: searchMonthOfBirth, + company: model.SearchCompany, + phone: model.SearchPhone, + zipPostalCode: model.SearchZipPostalCode, + loadOnlyWithShoppingCart: false); + + try + { + byte[] bytes = _exportManager.ExportCustomersToXlsx(customers); + return File(bytes, MimeTypes.TextXlsx, "customers.xlsx"); + } + catch (Exception exc) + { + ErrorNotification(exc); + return RedirectToAction("List"); + } + } + + [HttpPost] + public virtual ActionResult ExportExcelSelected(string selectedIds) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedView(); + + var customers = new List(); + if (selectedIds != null) + { + var ids = selectedIds + .Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(x => Convert.ToInt32(x)) + .ToArray(); + customers.AddRange(_customerService.GetCustomersByIds(ids)); + } + + try + { + byte[] bytes = _exportManager.ExportCustomersToXlsx(customers); + return File(bytes, MimeTypes.TextXlsx, "customers.xlsx"); + } + catch (Exception exc) + { + ErrorNotification(exc); + return RedirectToAction("List"); + } + } + + [HttpPost, ActionName("List")] + [FormValueRequired("exportxml-all")] + public virtual ActionResult ExportXmlAll(CustomerListModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedView(); + + var searchDayOfBirth = 0; + int searchMonthOfBirth = 0; + if (!String.IsNullOrWhiteSpace(model.SearchDayOfBirth)) + searchDayOfBirth = Convert.ToInt32(model.SearchDayOfBirth); + if (!String.IsNullOrWhiteSpace(model.SearchMonthOfBirth)) + searchMonthOfBirth = Convert.ToInt32(model.SearchMonthOfBirth); + + var customers = _customerService.GetAllCustomers( + customerRoleIds: model.SearchCustomerRoleIds.ToArray(), + email: model.SearchEmail, + username: model.SearchUsername, + firstName: model.SearchFirstName, + lastName: model.SearchLastName, + dayOfBirth: searchDayOfBirth, + monthOfBirth: searchMonthOfBirth, + company: model.SearchCompany, + phone: model.SearchPhone, + zipPostalCode: model.SearchZipPostalCode, + loadOnlyWithShoppingCart: false); + + try + { + var xml = _exportManager.ExportCustomersToXml(customers); + return new XmlDownloadResult(xml, "customers.xml"); + } + catch (Exception exc) + { + ErrorNotification(exc); + return RedirectToAction("List"); + } + } + + [HttpPost] + public virtual ActionResult ExportXmlSelected(string selectedIds) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + return AccessDeniedView(); + + var customers = new List(); + if (selectedIds != null) + { + var ids = selectedIds + .Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(x => Convert.ToInt32(x)) + .ToArray(); + customers.AddRange(_customerService.GetCustomersByIds(ids)); + } + + var xml = _exportManager.ExportCustomersToXml(customers); + return new XmlDownloadResult(xml, "customers.xml"); + } + + #endregion + } +} diff --git a/src/Presentation/Nop.Web/Administration/Controllers/OrderController.cs b/src/Presentation/Nop.Web/Administration/Controllers/OrderController.cs index f4fe5176184..433080358d0 100644 --- a/src/Presentation/Nop.Web/Administration/Controllers/OrderController.cs +++ b/src/Presentation/Nop.Web/Administration/Controllers/OrderController.cs @@ -510,17 +510,21 @@ protected virtual void PrepareOrderDetailsModel(OrderModel model, Order order) //shipping model.OrderShippingInclTax = _priceFormatter.FormatShippingPrice(order.OrderShippingInclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, true); model.OrderShippingExclTax = _priceFormatter.FormatShippingPrice(order.OrderShippingExclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, false); + model.OrderShippingNonTaxable = _priceFormatter.FormatPrice(order.OrderShippingNonTaxable, true, false); model.OrderShippingInclTaxValue = order.OrderShippingInclTax; model.OrderShippingExclTaxValue = order.OrderShippingExclTax; + model.OrderShippingNonTaxableValue = order.OrderShippingNonTaxable; //payment method additional fee if (order.PaymentMethodAdditionalFeeInclTax > decimal.Zero) { model.PaymentMethodAdditionalFeeInclTax = _priceFormatter.FormatPaymentMethodAdditionalFee(order.PaymentMethodAdditionalFeeInclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, true); model.PaymentMethodAdditionalFeeExclTax = _priceFormatter.FormatPaymentMethodAdditionalFee(order.PaymentMethodAdditionalFeeExclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, false); + model.PaymentMethodAdditionalFeeNonTaxable = _priceFormatter.FormatPaymentMethodAdditionalFee(order.PaymentMethodAdditionalFeeNonTaxable, true, primaryStoreCurrency, _workContext.WorkingLanguage, false); } model.PaymentMethodAdditionalFeeInclTaxValue = order.PaymentMethodAdditionalFeeInclTax; model.PaymentMethodAdditionalFeeExclTaxValue = order.PaymentMethodAdditionalFeeExclTax; + model.PaymentMethodAdditionalFeeNonTaxableValue = order.PaymentMethodAdditionalFeeNonTaxable; //tax @@ -536,7 +540,7 @@ protected virtual void PrepareOrderDetailsModel(OrderModel model, Order order) Amount = _priceFormatter.FormatPrice(tr.Value.Amount, true, false), DiscountAmount = _priceFormatter.FormatPrice(tr.Value.DiscountAmount, true, false), BaseAmount = _priceFormatter.FormatPrice(tr.Value.BaseAmount, true, false), - VatAmount = _priceFormatter.FormatPrice(tr.Value.VatAmount, true, false), + TaxAmount = _priceFormatter.FormatPrice(tr.Value.TaxAmount, true, false), }); } model.DisplayTaxRates = displayTaxRates; @@ -546,8 +550,12 @@ protected virtual void PrepareOrderDetailsModel(OrderModel model, Order order) //discount if (order.OrderDiscount > 0) + { model.OrderTotalDiscount = _priceFormatter.FormatPrice(-order.OrderDiscount, true, false); + model.OrderTotalDiscountIncl = _priceFormatter.FormatPrice(-order.OrderDiscountIncl, true, false); + } model.OrderTotalDiscountValue = order.OrderDiscount; + model.OrderTotalDiscountInclValue = order.OrderDiscountIncl; //gift cards foreach (var gcuh in order.GiftCardUsageHistory) @@ -573,6 +581,10 @@ protected virtual void PrepareOrderDetailsModel(OrderModel model, Order order) model.OrderAmountValue = order.OrderAmount; model.OrderAmountIncl = _priceFormatter.FormatPrice(order.OrderAmountIncl, true, false); model.OrderAmountInclValue = order.OrderAmountIncl; + model.EarnedRewardPointsBaseAmountExcl = _priceFormatter.FormatPrice(order.EarnedRewardPointsBaseAmountExcl, true, primaryStoreCurrency, _workContext.WorkingLanguage, false); + model.EarnedRewardPointsBaseAmountExclValue = order.EarnedRewardPointsBaseAmountExcl; + model.EarnedRewardPointsBaseAmountIncl = _priceFormatter.FormatPrice(order.EarnedRewardPointsBaseAmountIncl, true, primaryStoreCurrency, _workContext.WorkingLanguage, true); + model.EarnedRewardPointsBaseAmountInclValue = order.EarnedRewardPointsBaseAmountIncl; //refunded amount if (order.RefundedAmount > decimal.Zero) @@ -675,6 +687,7 @@ protected virtual void PrepareOrderDetailsModel(OrderModel model, Order order) model.BillingAddress.CountryEnabled = _addressSettings.CountryEnabled; model.BillingAddress.CountryRequired = _addressSettings.CountryEnabled; //country is required when enabled model.BillingAddress.StateProvinceEnabled = _addressSettings.StateProvinceEnabled; + model.BillingAddress.StateProvinceRequired = _addressSettings.StateProvinceEnabled; //province is required when enabled model.BillingAddress.CityEnabled = _addressSettings.CityEnabled; model.BillingAddress.CityRequired = _addressSettings.CityRequired; model.BillingAddress.StreetAddressEnabled = _addressSettings.StreetAddressEnabled; @@ -709,6 +722,7 @@ protected virtual void PrepareOrderDetailsModel(OrderModel model, Order order) model.ShippingAddress.CountryEnabled = _addressSettings.CountryEnabled; model.ShippingAddress.CountryRequired = _addressSettings.CountryEnabled; //country is required when enabled model.ShippingAddress.StateProvinceEnabled = _addressSettings.StateProvinceEnabled; + model.ShippingAddress.StateProvinceRequired = _addressSettings.StateProvinceEnabled;//province is required when enabled model.ShippingAddress.CityEnabled = _addressSettings.CityEnabled; model.ShippingAddress.CityRequired = _addressSettings.CityRequired; model.ShippingAddress.StreetAddressEnabled = _addressSettings.StreetAddressEnabled; @@ -769,7 +783,7 @@ protected virtual void PrepareOrderDetailsModel(OrderModel model, Order order) DownloadCount = orderItem.DownloadCount, DownloadActivationType = orderItem.Product.DownloadActivationType, IsDownloadActivated = orderItem.IsDownloadActivated, - VatRate = orderItem.VatRate + TaxRate = orderItem.TaxRate }; //picture var orderItemPicture = orderItem.Product.GetProductPicture(orderItem.AttributesXml, _pictureService, _productAttributeParser); @@ -864,7 +878,7 @@ protected virtual OrderModel.AddOrderProductModel.ProductDetailsModel PrepareAdd SubTotalExclTax = presetPriceExclTax, SubTotalInclTax = presetPriceInclTax, AutoUpdateOrderTotals = _orderSettings.AutoUpdateOrderTotalsOnEditingOrder, - VatRate = taxRate + TaxRate = taxRate }; //attributes @@ -1191,7 +1205,7 @@ public virtual ActionResult OrderList(DataSourceRequest command, OrderListModel { Id = x.Id, StoreName = store != null ? store.Name : "Unknown", - OrderTotal = _priceFormatter.FormatPrice(x.OrderTotal, true, false), + OrderTotal = _priceFormatter.FormatPrice(x.OrderAmountIncl, true, false), OrderStatus = x.OrderStatus.GetLocalizedEnum(_localizationService, _workContext), OrderStatusId = x.OrderStatusId, PaymentStatus = x.PaymentStatus.GetLocalizedEnum(_localizationService, _workContext), @@ -2142,14 +2156,19 @@ public virtual ActionResult EditOrderTotals(int id, OrderModel model) order.OrderSubTotalDiscountExclTax = model.OrderSubTotalDiscountExclTaxValue; order.OrderShippingInclTax = model.OrderShippingInclTaxValue; order.OrderShippingExclTax = model.OrderShippingExclTaxValue; + order.OrderShippingNonTaxable = model.OrderShippingNonTaxableValue; order.PaymentMethodAdditionalFeeInclTax = model.PaymentMethodAdditionalFeeInclTaxValue; order.PaymentMethodAdditionalFeeExclTax = model.PaymentMethodAdditionalFeeExclTaxValue; + order.PaymentMethodAdditionalFeeNonTaxable = model.PaymentMethodAdditionalFeeNonTaxableValue; order.TaxRates = model.TaxRatesValue; order.OrderTax = model.TaxValue; order.OrderDiscount = model.OrderTotalDiscountValue; order.OrderTotal = model.OrderTotalValue; order.OrderAmount = model.OrderAmountValue; order.OrderAmountIncl = model.OrderAmountInclValue; + order.OrderDiscountIncl = model.OrderTotalDiscountInclValue; + order.EarnedRewardPointsBaseAmountExcl = model.EarnedRewardPointsBaseAmountExclValue; + order.EarnedRewardPointsBaseAmountIncl = model.EarnedRewardPointsBaseAmountInclValue; _orderService.UpdateOrder(order); //add a note @@ -2285,7 +2304,7 @@ public virtual ActionResult EditOrderItem(int id, FormCollection form) decimal unitPriceInclTax, unitPriceExclTax, discountInclTax, discountExclTax,priceInclTax,priceExclTax; - int quantity; decimal vatrate; + int quantity; decimal taxrate; if (!decimal.TryParse(form["pvUnitPriceInclTax" + orderItemId], out unitPriceInclTax)) unitPriceInclTax = orderItem.UnitPriceInclTax; if (!decimal.TryParse(form["pvUnitPriceExclTax" + orderItemId], out unitPriceExclTax)) @@ -2300,8 +2319,8 @@ public virtual ActionResult EditOrderItem(int id, FormCollection form) priceInclTax = orderItem.PriceInclTax; if (!decimal.TryParse(form["pvPriceExclTax" + orderItemId], out priceExclTax)) priceExclTax = orderItem.PriceExclTax; - if (!decimal.TryParse(form["pvVatRate" + orderItemId], out vatrate)) - vatrate = orderItem.VatRate; + if (!decimal.TryParse(form["pvTaxRate" + orderItemId], out taxrate)) + taxrate = orderItem.TaxRate; if (quantity > 0) { @@ -2316,7 +2335,7 @@ public virtual ActionResult EditOrderItem(int id, FormCollection form) orderItem.DiscountAmountExclTax = discountExclTax; orderItem.PriceInclTax = priceInclTax; orderItem.PriceExclTax = priceExclTax; - orderItem.VatRate = vatrate; + orderItem.TaxRate = taxrate; _orderService.UpdateOrder(order); } @@ -2347,7 +2366,7 @@ public virtual ActionResult EditOrderItem(int id, FormCollection form) SubTotalInclTax = priceInclTax, SubTotalExclTax = priceExclTax, Quantity = quantity, - VatRate = vatrate + TaxRate = taxrate }; _orderProcessingService.UpdateOrderTotals(updateOrderParameters); @@ -2736,8 +2755,8 @@ public virtual ActionResult AddProductToOrderDetails(int orderId, int productId, decimal.TryParse(form["SubTotalInclTax"], out priceInclTax); decimal priceExclTax; decimal.TryParse(form["SubTotalExclTax"], out priceExclTax); - decimal vatRate; - decimal.TryParse(form["VatRate"], out vatRate); + decimal taxRate; + decimal.TryParse(form["TaxRate"], out taxRate); //warnings var warnings = new List(); @@ -2755,6 +2774,8 @@ public virtual ActionResult AddProductToOrderDetails(int orderId, int productId, null, null, true, out discountAmount, out scDiscounts); + decimal price = _taxService.GetProductPrice(product, 0, finalPrice, _workContext.TaxDisplayType == TaxDisplayType.IncludingTax, _workContext.CurrentCustomer, _taxSettings.PricesIncludeTax, out taxRate, ref attributesXml); + #region Gift cards string recipientName = ""; @@ -2842,7 +2863,7 @@ public virtual ActionResult AddProductToOrderDetails(int orderId, int productId, LicenseDownloadId = 0, RentalStartDateUtc = rentalStartDate, RentalEndDateUtc = rentalEndDate, - VatRate = vatRate + TaxRate = taxRate }; order.OrderItems.Add(orderItem); _orderService.UpdateOrder(order); @@ -2861,7 +2882,7 @@ public virtual ActionResult AddProductToOrderDetails(int orderId, int productId, SubTotalInclTax = priceInclTax, SubTotalExclTax = priceExclTax, Quantity = quantity, - VatRate = vatRate + TaxRate = taxRate }; _orderProcessingService.UpdateOrderTotals(updateOrderParameters); @@ -2948,6 +2969,7 @@ public virtual ActionResult AddressEdit(int addressId, int orderId) model.Address.CountryEnabled = _addressSettings.CountryEnabled; model.Address.CountryRequired = _addressSettings.CountryEnabled; //country is required when enabled model.Address.StateProvinceEnabled = _addressSettings.StateProvinceEnabled; + model.Address.StateProvinceRequired = _addressSettings.StateProvinceEnabled; //province is required when enabled model.Address.CityEnabled = _addressSettings.CityEnabled; model.Address.CityRequired = _addressSettings.CityRequired; model.Address.StreetAddressEnabled = _addressSettings.StreetAddressEnabled; @@ -3041,6 +3063,7 @@ public virtual ActionResult AddressEdit(OrderAddressModel model, FormCollection model.Address.CountryEnabled = _addressSettings.CountryEnabled; model.Address.CountryRequired = _addressSettings.CountryEnabled; //country is required when enabled model.Address.StateProvinceEnabled = _addressSettings.StateProvinceEnabled; + model.Address.StateProvinceRequired = _addressSettings.StateProvinceEnabled; //province is required when enabled model.Address.CityEnabled = _addressSettings.CityEnabled; model.Address.CityRequired = _addressSettings.CityRequired; model.Address.StreetAddressEnabled = _addressSettings.StreetAddressEnabled; diff --git a/src/Presentation/Nop.Web/Administration/Controllers/SettingController.cs b/src/Presentation/Nop.Web/Administration/Controllers/SettingController.cs index 4f48048c76a..2a89ad45a66 100644 --- a/src/Presentation/Nop.Web/Administration/Controllers/SettingController.cs +++ b/src/Presentation/Nop.Web/Administration/Controllers/SettingController.cs @@ -81,24 +81,24 @@ public partial class SettingController : BaseAdminController #region Ctor public SettingController(ISettingService settingService, - ICountryService countryService, + ICountryService countryService, IStateProvinceService stateProvinceService, - IAddressService addressService, + IAddressService addressService, ITaxCategoryService taxCategoryService, ICurrencyService currencyService, - IPictureService pictureService, - ILocalizationService localizationService, + IPictureService pictureService, + ILocalizationService localizationService, IDateTimeHelper dateTimeHelper, IOrderService orderService, IEncryptionService encryptionService, IThemeProvider themeProvider, - ICustomerService customerService, + ICustomerService customerService, ICustomerActivityService customerActivityService, IPermissionService permissionService, - IFulltextService fulltextService, + IFulltextService fulltextService, IMaintenanceService maintenanceService, IStoreService storeService, - IWorkContext workContext, + IWorkContext workContext, IGenericAttributeService genericAttributeService, IReturnRequestService returnRequestService, ILanguageService languageService, @@ -247,7 +247,7 @@ public virtual ActionResult Blog(BlogSettingsModel model) blogSettings = model.ToEntity(blogSettings); /* We do not clear cache after each setting update. - * This behavior can increase performance because cached settings will not be cleared + * This behavior can increase performance because cached settings will not be cleared * and loaded from database after each update */ _settingService.SaveSettingOverridablePerStore(blogSettings, x => x.Enabled, model.Enabled_OverrideForStore, storeScope, false); _settingService.SaveSettingOverridablePerStore(blogSettings, x => x.PostsPageSize, model.PostsPageSize_OverrideForStore, storeScope, false); @@ -308,7 +308,7 @@ public virtual ActionResult Vendor(VendorSettingsModel model) vendorSettings = model.ToEntity(vendorSettings); /* We do not clear cache after each setting update. - * This behavior can increase performance because cached settings will not be cleared + * This behavior can increase performance because cached settings will not be cleared * and loaded from database after each update */ _settingService.SaveSettingOverridablePerStore(vendorSettings, x => x.VendorsBlockItemsToDisplay, model.VendorsBlockItemsToDisplay_OverrideForStore, storeScope, false); _settingService.SaveSettingOverridablePerStore(vendorSettings, x => x.ShowVendorOnProductDetailsPage, model.ShowVendorOnProductDetailsPage_OverrideForStore, storeScope, false); @@ -386,7 +386,7 @@ public virtual ActionResult Forum(ForumSettingsModel model) forumSettings = model.ToEntity(forumSettings); /* We do not clear cache after each setting update. - * This behavior can increase performance because cached settings will not be cleared + * This behavior can increase performance because cached settings will not be cleared * and loaded from database after each update */ _settingService.SaveSettingOverridablePerStore(forumSettings, x => x.ForumsEnabled, model.ForumsEnabled_OverrideForStore, storeScope, false); _settingService.SaveSettingOverridablePerStore(forumSettings, x => x.RelativeDateTimeFormattingEnabled, model.RelativeDateTimeFormattingEnabled_OverrideForStore, storeScope, false); @@ -411,7 +411,7 @@ public virtual ActionResult Forum(ForumSettingsModel model) _settingService.SaveSettingOverridablePerStore(forumSettings, x => x.ForumFeedCount, model.ForumFeedCount_OverrideForStore, storeScope, false); _settingService.SaveSettingOverridablePerStore(forumSettings, x => x.SearchResultsPageSize, model.SearchResultsPageSize_OverrideForStore, storeScope, false); _settingService.SaveSettingOverridablePerStore(forumSettings, x => x.ActiveDiscussionsPageSize, model.ActiveDiscussionsPageSize_OverrideForStore, storeScope, false); - + //now clear settings cache _settingService.ClearCache(); @@ -460,7 +460,7 @@ public virtual ActionResult News(NewsSettingsModel model) newsSettings = model.ToEntity(newsSettings); /* We do not clear cache after each setting update. - * This behavior can increase performance because cached settings will not be cleared + * This behavior can increase performance because cached settings will not be cleared * and loaded from database after each update */ _settingService.SaveSettingOverridablePerStore(newsSettings, x => x.Enabled, model.Enabled_OverrideForStore, storeScope, false); _settingService.SaveSettingOverridablePerStore(newsSettings, x => x.AllowNotRegisteredUsersToLeaveComments, model.AllowNotRegisteredUsersToLeaveComments_OverrideForStore, storeScope, false); @@ -542,7 +542,7 @@ public virtual ActionResult Shipping() model.ShippingOriginAddress.StreetAddressEnabled = true; model.ShippingOriginAddress.ZipPostalCodeEnabled = true; model.ShippingOriginAddress.ZipPostalCodeRequired = true; - + return View(model); } [HttpPost] @@ -558,7 +558,7 @@ public virtual ActionResult Shipping(ShippingSettingsModel model) shippingSettings = model.ToEntity(shippingSettings); /* We do not clear cache after each setting update. - * This behavior can increase performance because cached settings will not be cleared + * This behavior can increase performance because cached settings will not be cleared * and loaded from database after each update */ _settingService.SaveSettingOverridablePerStore(shippingSettings, x => x.ShipToSameAddress, model.ShipToSameAddress_OverrideForStore, storeScope, false); _settingService.SaveSettingOverridablePerStore(shippingSettings, x => x.AllowPickUpInStore, model.AllowPickUpInStore_OverrideForStore, storeScope, false); @@ -712,7 +712,7 @@ public virtual ActionResult Tax(TaxSettingsModel model) taxSettings = model.ToEntity(taxSettings); /* We do not clear cache after each setting update. - * This behavior can increase performance because cached settings will not be cleared + * This behavior can increase performance because cached settings will not be cleared * and loaded from database after each update */ _settingService.SaveSettingOverridablePerStore(taxSettings, x => x.PricesIncludeTax, model.PricesIncludeTax_OverrideForStore, storeScope, false); _settingService.SaveSettingOverridablePerStore(taxSettings, x => x.AllowCustomersToSelectTaxDisplayType, model.AllowCustomersToSelectTaxDisplayType_OverrideForStore, storeScope, false); @@ -763,7 +763,7 @@ public virtual ActionResult Tax(TaxSettingsModel model) _settingService.SaveSettingOverridablePerStore(taxSettings, x => x.EuVatUseWebService, model.EuVatUseWebService_OverrideForStore, storeScope, false); _settingService.SaveSettingOverridablePerStore(taxSettings, x => x.EuVatAssumeValid, model.EuVatAssumeValid_OverrideForStore, storeScope, false); _settingService.SaveSettingOverridablePerStore(taxSettings, x => x.EuVatEmailAdminWhenNewVatSubmitted, model.EuVatEmailAdminWhenNewVatSubmitted_OverrideForStore, storeScope, false); - + //now clear settings cache _settingService.ClearCache(); @@ -860,7 +860,7 @@ public virtual ActionResult Catalog(CatalogSettingsModel model) catalogSettings = model.ToEntity(catalogSettings); /* We do not clear cache after each setting update. - * This behavior can increase performance because cached settings will not be cleared + * This behavior can increase performance because cached settings will not be cleared * and loaded from database after each update */ _settingService.SaveSettingOverridablePerStore(catalogSettings, x => x.AllowViewUnpublishedProductPage, model.AllowViewUnpublishedProductPage_OverrideForStore, storeScope, false); _settingService.SaveSettingOverridablePerStore(catalogSettings, x => x.DisplayDiscontinuedMessageForUnpublishedProducts, model.DisplayDiscontinuedMessageForUnpublishedProducts_OverrideForStore, storeScope, false); @@ -1011,8 +1011,15 @@ public virtual ActionResult RewardPoints() model.DisplayHowMuchWillBeEarned_OverrideForStore = _settingService.SettingExists(rewardPointsSettings, x => x.DisplayHowMuchWillBeEarned, storeScope); model.PointsForRegistration_OverrideForStore = _settingService.SettingExists(rewardPointsSettings, x => x.PointsForRegistration, storeScope); model.PageSize_OverrideForStore = _settingService.SettingExists(rewardPointsSettings, x => x.PageSize, storeScope); + model.EarnedRewardPointsAreTaxable_OverrideForStore = _settingService.SettingExists(rewardPointsSettings, x => x.EarnedRewardPointsAreTaxable, storeScope); + model.AwardPointsIncludeShipping_OverrideForStore = _settingService.SettingExists(rewardPointsSettings, x => x.AwardPointsIncludeShipping, storeScope); + model.AwardPointsIncludePaymentMethodAdditionalFee_OverrideForStore = _settingService.SettingExists(rewardPointsSettings, x => x.AwardPointsIncludePaymentMethodAdditionalFee, storeScope); + model.AwardPointsExcludeGiftCard_OverrideForStore = _settingService.SettingExists(rewardPointsSettings, x => x.AwardPointsExcludeGiftCard, storeScope); + model.AwardPointsExcludePurchasedRewardPoints_OverrideForStore = _settingService.SettingExists(rewardPointsSettings, x => x.AwardPointsExcludePurchasedRewardPoints, storeScope); + model.EarnRewardPointsOnlyWhenUsingPurchasedRewardPoints_OverrideForStore = _settingService.SettingExists(rewardPointsSettings, x => x.EarnRewardPointsOnlyWhenUsingPurchasedRewardPoints, storeScope); + } - var currencySettings = _settingService.LoadSetting(storeScope); + var currencySettings = _settingService.LoadSetting(storeScope); model.PrimaryStoreCurrencyCode = _currencyService.GetCurrencyById(currencySettings.PrimaryStoreCurrencyId).CurrencyCode; model.ActivatePointsImmediately = model.ActivationDelay <= 0; @@ -1036,7 +1043,7 @@ public virtual ActionResult RewardPoints(RewardPointsSettingsModel model) rewardPointsSettings.ActivationDelay = 0; /* We do not clear cache after each setting update. - * This behavior can increase performance because cached settings will not be cleared + * This behavior can increase performance because cached settings will not be cleared * and loaded from database after each update */ _settingService.SaveSettingOverridablePerStore(rewardPointsSettings, x => x.Enabled, model.Enabled_OverrideForStore, storeScope, false); _settingService.SaveSettingOverridablePerStore(rewardPointsSettings, x => x.ExchangeRate, model.ExchangeRate_OverrideForStore, storeScope, false); @@ -1049,6 +1056,12 @@ public virtual ActionResult RewardPoints(RewardPointsSettingsModel model) _settingService.SaveSettingOverridablePerStore(rewardPointsSettings, x => x.DisplayHowMuchWillBeEarned, model.DisplayHowMuchWillBeEarned_OverrideForStore, storeScope, false); _settingService.SaveSettingOverridablePerStore(rewardPointsSettings, x => x.PageSize, model.PageSize_OverrideForStore, storeScope, false); _settingService.SaveSetting(rewardPointsSettings, x => x.PointsAccumulatedForAllStores, 0, false); + _settingService.SaveSettingOverridablePerStore(rewardPointsSettings, x => x.EarnedRewardPointsAreTaxable, model.EarnedRewardPointsAreTaxable_OverrideForStore, storeScope, false); + _settingService.SaveSettingOverridablePerStore(rewardPointsSettings, x => x.AwardPointsIncludeShipping, model.AwardPointsIncludeShipping_OverrideForStore, storeScope, false); + _settingService.SaveSettingOverridablePerStore(rewardPointsSettings, x => x.AwardPointsIncludePaymentMethodAdditionalFee, model.AwardPointsIncludePaymentMethodAdditionalFee_OverrideForStore, storeScope, false); + _settingService.SaveSettingOverridablePerStore(rewardPointsSettings, x => x.AwardPointsExcludeGiftCard, model.AwardPointsExcludeGiftCard_OverrideForStore, storeScope, false); + _settingService.SaveSettingOverridablePerStore(rewardPointsSettings, x => x.AwardPointsExcludePurchasedRewardPoints, model.AwardPointsExcludePurchasedRewardPoints_OverrideForStore, storeScope, false); + _settingService.SaveSettingOverridablePerStore(rewardPointsSettings, x => x.EarnRewardPointsOnlyWhenUsingPurchasedRewardPoints, model.EarnRewardPointsOnlyWhenUsingPurchasedRewardPoints_OverrideForStore, storeScope, false); //now clear settings cache _settingService.ClearCache(); @@ -1130,7 +1143,7 @@ public virtual ActionResult Order(OrderSettingsModel model) orderSettings = model.ToEntity(orderSettings); /* We do not clear cache after each setting update. - * This behavior can increase performance because cached settings will not be cleared + * This behavior can increase performance because cached settings will not be cleared * and loaded from database after each update */ _settingService.SaveSettingOverridablePerStore(orderSettings, x => x.IsReOrderAllowed, model.IsReOrderAllowed_OverrideForStore, storeScope, false); _settingService.SaveSettingOverridablePerStore(orderSettings, x => x.MinOrderSubtotalAmount, model.MinOrderSubtotalAmount_OverrideForStore, storeScope, false); @@ -1157,10 +1170,10 @@ public virtual ActionResult Order(OrderSettingsModel model) _settingService.SaveSetting(orderSettings, x => x.DeactivateGiftCardsAfterCancellingOrder, 0, false); _settingService.SaveSetting(orderSettings, x => x.DeactivateGiftCardsAfterDeletingOrder, 0, false); _settingService.SaveSetting(orderSettings, x => x.CompleteOrderWhenDelivered, 0, false); - + //now clear settings cache _settingService.ClearCache(); - + //order ident if (model.OrderIdent.HasValue) { @@ -1492,7 +1505,7 @@ public virtual ActionResult ShoppingCart(ShoppingCartSettingsModel model) shoppingCartSettings = model.ToEntity(shoppingCartSettings); /* We do not clear cache after each setting update. - * This behavior can increase performance because cached settings will not be cleared + * This behavior can increase performance because cached settings will not be cleared * and loaded from database after each update */ _settingService.SaveSettingOverridablePerStore(shoppingCartSettings, x => x.DisplayCartAfterAddingProduct, model.DisplayCartAfterAddingProduct_OverrideForStore, storeScope, false); _settingService.SaveSettingOverridablePerStore(shoppingCartSettings, x => x.DisplayWishlistAfterAddingProduct, model.DisplayWishlistAfterAddingProduct_OverrideForStore, storeScope, false); @@ -1512,10 +1525,10 @@ public virtual ActionResult ShoppingCart(ShoppingCartSettingsModel model) _settingService.SaveSettingOverridablePerStore(shoppingCartSettings, x => x.ShowProductImagesInMiniShoppingCart, model.ShowProductImagesInMiniShoppingCart_OverrideForStore, storeScope, false); _settingService.SaveSettingOverridablePerStore(shoppingCartSettings, x => x.MiniShoppingCartProductNumber, model.MiniShoppingCartProductNumber_OverrideForStore, storeScope, false); _settingService.SaveSettingOverridablePerStore(shoppingCartSettings, x => x.AllowCartItemEditing, model.AllowCartItemEditing_OverrideForStore, storeScope, false); - + //now clear settings cache _settingService.ClearCache(); - + //activity log _customerActivityService.InsertActivity("EditSettings", _localizationService.GetResource("ActivityLog.EditSettings")); @@ -1571,7 +1584,7 @@ public virtual ActionResult Media(MediaSettingsModel model) mediaSettings = model.ToEntity(mediaSettings); /* We do not clear cache after each setting update. - * This behavior can increase performance because cached settings will not be cleared + * This behavior can increase performance because cached settings will not be cleared * and loaded from database after each update */ _settingService.SaveSettingOverridablePerStore(mediaSettings, x => x.AvatarPictureSize, model.AvatarPictureSize_OverrideForStore, storeScope, false); _settingService.SaveSettingOverridablePerStore(mediaSettings, x => x.ProductThumbPictureSize, model.ProductThumbPictureSize_OverrideForStore, storeScope, false); @@ -1591,7 +1604,7 @@ public virtual ActionResult Media(MediaSettingsModel model) //now clear settings cache _settingService.ClearCache(); - + //activity log _customerActivityService.InsertActivity("EditSettings", _localizationService.GetResource("ActivityLog.EditSettings")); @@ -1793,7 +1806,7 @@ public virtual ActionResult GeneralCommon() model.SeoSettings.OpenGraphMetaTags_OverrideForStore = _settingService.SettingExists(seoSettings, x => x.OpenGraphMetaTags, storeScope); model.SeoSettings.CustomHeadTags_OverrideForStore = _settingService.SettingExists(seoSettings, x => x.CustomHeadTags, storeScope); } - + //security settings var securitySettings = _settingService.LoadSetting(storeScope); model.SecuritySettings.EncryptionKey = securitySettings.EncryptionKey; @@ -1901,7 +1914,7 @@ public virtual ActionResult GeneralCommon(GeneralCommonSettingsModel model) commonSettings.SitemapIncludeProducts = model.StoreInformationSettings.SitemapIncludeProducts; /* We do not clear cache after each setting update. - * This behavior can increase performance because cached settings will not be cleared + * This behavior can increase performance because cached settings will not be cleared * and loaded from database after each update */ _settingService.SaveSettingOverridablePerStore(storeInformationSettings, x => x.StoreClosed, model.StoreInformationSettings.StoreClosed_OverrideForStore, storeScope, false); @@ -1943,7 +1956,7 @@ public virtual ActionResult GeneralCommon(GeneralCommonSettingsModel model) seoSettings.CustomHeadTags = model.SeoSettings.CustomHeadTags; /* We do not clear cache after each setting update. - * This behavior can increase performance because cached settings will not be cleared + * This behavior can increase performance because cached settings will not be cleared * and loaded from database after each update */ _settingService.SaveSettingOverridablePerStore(seoSettings, x => x.PageTitleSeparator, model.SeoSettings.PageTitleSeparator_OverrideForStore, storeScope, false); _settingService.SaveSettingOverridablePerStore(seoSettings, x => x.PageTitleSeoAdjustment, model.SeoSettings.PageTitleSeoAdjustment_OverrideForStore, storeScope, false); @@ -1959,7 +1972,7 @@ public virtual ActionResult GeneralCommon(GeneralCommonSettingsModel model) _settingService.SaveSettingOverridablePerStore(seoSettings, x => x.TwitterMetaTags, model.SeoSettings.TwitterMetaTags_OverrideForStore, storeScope, false); _settingService.SaveSettingOverridablePerStore(seoSettings, x => x.OpenGraphMetaTags, model.SeoSettings.OpenGraphMetaTags_OverrideForStore, storeScope, false); _settingService.SaveSettingOverridablePerStore(seoSettings, x => x.CustomHeadTags, model.SeoSettings.CustomHeadTags_OverrideForStore, storeScope, false); - + //now clear settings cache _settingService.ClearCache(); @@ -1999,15 +2012,15 @@ public virtual ActionResult GeneralCommon(GeneralCommonSettingsModel model) pdfSettings.InvoiceFooterTextColumn1 = model.PdfSettings.InvoiceFooterTextColumn1; pdfSettings.InvoiceFooterTextColumn2 = model.PdfSettings.InvoiceFooterTextColumn2; /* We do not clear cache after each setting update. - * This behavior can increase performance because cached settings will not be cleared + * This behavior can increase performance because cached settings will not be cleared * and loaded from database after each update */ - + _settingService.SaveSettingOverridablePerStore(pdfSettings, x => x.LetterPageSizeEnabled, model.PdfSettings.LetterPageSizeEnabled_OverrideForStore, storeScope, false); _settingService.SaveSettingOverridablePerStore(pdfSettings, x => x.LogoPictureId, model.PdfSettings.LogoPictureId_OverrideForStore, storeScope, false); _settingService.SaveSettingOverridablePerStore(pdfSettings, x => x.DisablePdfInvoicesForPendingOrders, model.PdfSettings.DisablePdfInvoicesForPendingOrders_OverrideForStore, storeScope, false); _settingService.SaveSettingOverridablePerStore(pdfSettings, x => x.InvoiceFooterTextColumn1, model.PdfSettings.InvoiceFooterTextColumn1_OverrideForStore, storeScope, false); _settingService.SaveSettingOverridablePerStore(pdfSettings, x => x.InvoiceFooterTextColumn2, model.PdfSettings.InvoiceFooterTextColumn2_OverrideForStore, storeScope, false); - + //now clear settings cache _settingService.ClearCache(); @@ -2062,7 +2075,7 @@ public virtual ActionResult GeneralCommon(GeneralCommonSettingsModel model) _customerActivityService.InsertActivity("EditSettings", _localizationService.GetResource("ActivityLog.EditSettings")); SuccessNotification(_localizationService.GetResource("Admin.Configuration.Updated")); - + return RedirectToAction("GeneralCommon"); } [HttpPost, ActionName("GeneralCommon")] @@ -2182,7 +2195,7 @@ public virtual ActionResult ToggleFullText(GeneralCommonSettingsModel model) { ErrorNotification(exc); } - + return RedirectToAction("GeneralCommon"); } @@ -2194,13 +2207,13 @@ public virtual ActionResult AllSettings() { if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) return AccessDeniedView(); - + return View(); } [HttpPost] //do not validate request token (XSRF) //for some reasons it does not work with "filtering" support - [AdminAntiForgery(true)] + [AdminAntiForgery(true)] public virtual ActionResult AllSettings(DataSourceRequest command, AllSettingsListModel model) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageSettings)) diff --git a/src/Presentation/Nop.Web/Administration/Controllers/ShippingController.cs b/src/Presentation/Nop.Web/Administration/Controllers/ShippingController.cs index a5ba1ec10b6..97f9e2a6256 100644 --- a/src/Presentation/Nop.Web/Administration/Controllers/ShippingController.cs +++ b/src/Presentation/Nop.Web/Administration/Controllers/ShippingController.cs @@ -1,1001 +1,1003 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web.Mvc; -using System.Web.Routing; -using Nop.Admin.Extensions; -using Nop.Admin.Models.Directory; -using Nop.Admin.Models.Shipping; -using Nop.Core; -using Nop.Core.Domain.Directory; -using Nop.Core.Domain.Shipping; -using Nop.Core.Plugins; -using Nop.Services.Common; -using Nop.Services.Configuration; -using Nop.Services.Directory; -using Nop.Services.Localization; -using Nop.Services.Logging; -using Nop.Services.Security; -using Nop.Services.Shipping; -using Nop.Services.Shipping.Date; -using Nop.Web.Framework.Controllers; -using Nop.Web.Framework.Kendoui; -using Nop.Web.Framework.Mvc; - -namespace Nop.Admin.Controllers -{ - public partial class ShippingController : BaseAdminController - { - #region Fields - - private readonly IShippingService _shippingService; - private readonly ShippingSettings _shippingSettings; - private readonly ISettingService _settingService; - private readonly IAddressService _addressService; - private readonly ICountryService _countryService; - private readonly IStateProvinceService _stateProvinceService; - private readonly ILocalizationService _localizationService; - private readonly IPermissionService _permissionService; - private readonly ILocalizedEntityService _localizedEntityService; - private readonly ILanguageService _languageService; - private readonly IDateRangeService _dateRangeService; - private readonly IPluginFinder _pluginFinder; - private readonly IWebHelper _webHelper; - private readonly ICustomerActivityService _customerActivityService; - - #endregion - - #region Ctor - - public ShippingController(IShippingService shippingService, - ShippingSettings shippingSettings, - ISettingService settingService, - IAddressService addressService, - ICountryService countryService, - IStateProvinceService stateProvinceService, - ILocalizationService localizationService, - IPermissionService permissionService, - ILocalizedEntityService localizedEntityService, - ILanguageService languageService, - IDateRangeService dateRangeService, - IPluginFinder pluginFinder, - IWebHelper webHelper, - ICustomerActivityService customerActivityService) - { - this._shippingService = shippingService; - this._shippingSettings = shippingSettings; - this._settingService = settingService; - this._addressService = addressService; - this._countryService = countryService; - this._stateProvinceService = stateProvinceService; - this._localizationService = localizationService; - this._permissionService = permissionService; - this._localizedEntityService = localizedEntityService; - this._languageService = languageService; - this._dateRangeService = dateRangeService; - this._pluginFinder = pluginFinder; - this._webHelper = webHelper; - this._customerActivityService = customerActivityService; - } - - #endregion  - - #region Utilities - - [NonAction] - protected virtual void UpdateLocales(ShippingMethod shippingMethod, ShippingMethodModel model) - { - foreach (var localized in model.Locales) - { - _localizedEntityService.SaveLocalizedValue(shippingMethod, x => x.Name, localized.Name, localized.LanguageId); - _localizedEntityService.SaveLocalizedValue(shippingMethod, x => x.Description, localized.Description, localized.LanguageId); - } - } - - [NonAction] - protected virtual void UpdateLocales(DeliveryDate deliveryDate, DeliveryDateModel model) - { - foreach (var localized in model.Locales) - { - _localizedEntityService.SaveLocalizedValue(deliveryDate, x => x.Name, localized.Name, localized.LanguageId); - } - } - - [NonAction] - protected virtual void UpdateLocales(ProductAvailabilityRange productAvailabilityRange, ProductAvailabilityRangeModel model) - { - foreach (var localized in model.Locales) - { - _localizedEntityService.SaveLocalizedValue(productAvailabilityRange, x => x.Name, localized.Name, localized.LanguageId); - } - } - - #endregion - - #region Shipping rate computation methods - - public virtual ActionResult Providers() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - return View(); - } - - [HttpPost] - public virtual ActionResult Providers(DataSourceRequest command) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedKendoGridJson(); - - var shippingProvidersModel = new List(); - var shippingProviders = _shippingService.LoadAllShippingRateComputationMethods(); - foreach (var shippingProvider in shippingProviders) - { - var tmp1 = shippingProvider.ToModel(); - tmp1.IsActive = shippingProvider.IsShippingRateComputationMethodActive(_shippingSettings); - tmp1.LogoUrl = shippingProvider.PluginDescriptor.GetLogoUrl(_webHelper); - shippingProvidersModel.Add(tmp1); - } - shippingProvidersModel = shippingProvidersModel.ToList(); - var gridModel = new DataSourceResult - { - Data = shippingProvidersModel, - Total = shippingProvidersModel.Count() - }; - - return Json(gridModel); - } - - [HttpPost] - public virtual ActionResult ProviderUpdate([Bind(Exclude = "ConfigurationRouteValues")] ShippingRateComputationMethodModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - var srcm = _shippingService.LoadShippingRateComputationMethodBySystemName(model.SystemName); - if (srcm.IsShippingRateComputationMethodActive(_shippingSettings)) - { - if (!model.IsActive) - { - //mark as disabled - _shippingSettings.ActiveShippingRateComputationMethodSystemNames.Remove(srcm.PluginDescriptor.SystemName); - _settingService.SaveSetting(_shippingSettings); - } - } - else - { - if (model.IsActive) - { - //mark as active - _shippingSettings.ActiveShippingRateComputationMethodSystemNames.Add(srcm.PluginDescriptor.SystemName); - _settingService.SaveSetting(_shippingSettings); - } - } - var pluginDescriptor = srcm.PluginDescriptor; - //display order - pluginDescriptor.DisplayOrder = model.DisplayOrder; - PluginFileParser.SavePluginDescriptionFile(pluginDescriptor); - //reset plugin cache - _pluginFinder.ReloadPlugins(); - - return new NullJsonResult(); - } - - public virtual ActionResult ConfigureProvider(string systemName) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - var srcm = _shippingService.LoadShippingRateComputationMethodBySystemName(systemName); - if (srcm == null) - //No shipping rate computation method found with the specified id - return RedirectToAction("Providers"); - - var model = srcm.ToModel(); - string actionName, controllerName; - RouteValueDictionary routeValues; - srcm.GetConfigurationRoute(out actionName, out controllerName, out routeValues); - model.ConfigurationActionName = actionName; - model.ConfigurationControllerName = controllerName; - model.ConfigurationRouteValues = routeValues; - return View(model); - } - - #endregion - - #region Pickup point providers - - public virtual ActionResult PickupPointProviders() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - return View(); - } - - [HttpPost] - public virtual ActionResult PickupPointProviders(DataSourceRequest command) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedKendoGridJson(); - - var pickupPointProviderModel = new List(); - var allProviders = _shippingService.LoadAllPickupPointProviders(); - foreach (var provider in allProviders) - { - var model = provider.ToModel(); - model.IsActive = provider.IsPickupPointProviderActive(_shippingSettings); - model.LogoUrl = provider.PluginDescriptor.GetLogoUrl(_webHelper); - pickupPointProviderModel.Add(model); - } - - var gridModel = new DataSourceResult - { - Data = pickupPointProviderModel, - Total = pickupPointProviderModel.Count - }; - - return Json(gridModel); - } - - [HttpPost] - public virtual ActionResult PickupPointProviderUpdate([Bind(Exclude = "ConfigurationRouteValues")] PickupPointProviderModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - var pickupPointProvider = _shippingService.LoadPickupPointProviderBySystemName(model.SystemName); - if (pickupPointProvider.IsPickupPointProviderActive(_shippingSettings)) - { - if (!model.IsActive) - { - //mark as disabled - _shippingSettings.ActivePickupPointProviderSystemNames.Remove(pickupPointProvider.PluginDescriptor.SystemName); - _settingService.SaveSetting(_shippingSettings); - } - } - else - { - if (model.IsActive) - { - //mark as active - _shippingSettings.ActivePickupPointProviderSystemNames.Add(pickupPointProvider.PluginDescriptor.SystemName); - _settingService.SaveSetting(_shippingSettings); - } - } - var pluginDescriptor = pickupPointProvider.PluginDescriptor; - pluginDescriptor.DisplayOrder = model.DisplayOrder; - PluginFileParser.SavePluginDescriptionFile(pluginDescriptor); - //reset plugin cache - _pluginFinder.ReloadPlugins(); - - return new NullJsonResult(); - } - - public virtual ActionResult ConfigurePickupPointProvider(string systemName) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - var pickupPointProvider = _shippingService.LoadPickupPointProviderBySystemName(systemName); - if (pickupPointProvider == null) - return RedirectToAction("PickupPointProviders"); - - var model = pickupPointProvider.ToModel(); - string actionName; - string controllerName; - RouteValueDictionary routeValues; - pickupPointProvider.GetConfigurationRoute(out actionName, out controllerName, out routeValues); - model.ConfigurationActionName = actionName; - model.ConfigurationControllerName = controllerName; - model.ConfigurationRouteValues = routeValues; - return View(model); - } - - #endregion - - #region Shipping methods - - public virtual ActionResult Methods() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - return View(); - } - - [HttpPost] - public virtual ActionResult Methods(DataSourceRequest command) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedKendoGridJson(); - - var shippingMethodsModel = _shippingService.GetAllShippingMethods() - .Select(x => x.ToModel()) - .ToList(); - var gridModel = new DataSourceResult - { - Data = shippingMethodsModel, - Total = shippingMethodsModel.Count - }; - - return Json(gridModel); - } - - - public virtual ActionResult CreateMethod() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - var model = new ShippingMethodModel(); - //locales - AddLocales(_languageService, model.Locales); - return View(model); - } - - [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] - public virtual ActionResult CreateMethod(ShippingMethodModel model, bool continueEditing) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - if (ModelState.IsValid) - { - var sm = model.ToEntity(); - _shippingService.InsertShippingMethod(sm); - //locales - UpdateLocales(sm, model); - - SuccessNotification(_localizationService.GetResource("Admin.Configuration.Shipping.Methods.Added")); - return continueEditing ? RedirectToAction("EditMethod", new { id = sm.Id }) : RedirectToAction("Methods"); - } - - //If we got this far, something failed, redisplay form - return View(model); - } - - public virtual ActionResult EditMethod(int id) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - var sm = _shippingService.GetShippingMethodById(id); - if (sm == null) - //No shipping method found with the specified id - return RedirectToAction("Methods"); - - var model = sm.ToModel(); - //locales - AddLocales(_languageService, model.Locales, (locale, languageId) => - { - locale.Name = sm.GetLocalized(x => x.Name, languageId, false, false); - locale.Description = sm.GetLocalized(x => x.Description, languageId, false, false); - }); - - return View(model); - } - - [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] - public virtual ActionResult EditMethod(ShippingMethodModel model, bool continueEditing) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - var sm = _shippingService.GetShippingMethodById(model.Id); - if (sm == null) - //No shipping method found with the specified id - return RedirectToAction("Methods"); - - if (ModelState.IsValid) - { - sm = model.ToEntity(sm); - _shippingService.UpdateShippingMethod(sm); - //locales - UpdateLocales(sm, model); - SuccessNotification(_localizationService.GetResource("Admin.Configuration.Shipping.Methods.Updated")); - return continueEditing ? RedirectToAction("EditMethod", sm.Id) : RedirectToAction("Methods"); - } - - - //If we got this far, something failed, redisplay form - return View(model); - } - - [HttpPost] - public virtual ActionResult DeleteMethod(int id) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - var sm = _shippingService.GetShippingMethodById(id); - if (sm == null) - //No shipping method found with the specified id - return RedirectToAction("Methods"); - - _shippingService.DeleteShippingMethod(sm); - - SuccessNotification(_localizationService.GetResource("Admin.Configuration.Shipping.Methods.Deleted")); - return RedirectToAction("Methods"); - } - - #endregion - - #region Dates and ranges - - public virtual ActionResult DatesAndRanges() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - return View(); - } - - #region Delivery dates - - [HttpPost] - public virtual ActionResult DeliveryDates(DataSourceRequest command) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedKendoGridJson(); - - var deliveryDatesModel = _dateRangeService.GetAllDeliveryDates().Select(x => x.ToModel()).ToList(); - var gridModel = new DataSourceResult - { - Data = deliveryDatesModel, - Total = deliveryDatesModel.Count - }; - - return Json(gridModel); - } - - - public virtual ActionResult CreateDeliveryDate() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - var model = new DeliveryDateModel(); - - //locales - AddLocales(_languageService, model.Locales); - - return View(model); - } - - [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] - public virtual ActionResult CreateDeliveryDate(DeliveryDateModel model, bool continueEditing) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - if (ModelState.IsValid) - { - var deliveryDate = model.ToEntity(); - _dateRangeService.InsertDeliveryDate(deliveryDate); - - //locales - UpdateLocales(deliveryDate, model); - - SuccessNotification(_localizationService.GetResource("Admin.Configuration.Shipping.DeliveryDates.Added")); - - return continueEditing ? RedirectToAction("EditDeliveryDate", new { id = deliveryDate.Id }) : RedirectToAction("DatesAndRanges"); - } - - //If we got this far, something failed, redisplay form - return View(model); - } - - public virtual ActionResult EditDeliveryDate(int id) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - var deliveryDate = _dateRangeService.GetDeliveryDateById(id); - if (deliveryDate == null) - //No delivery date found with the specified id - return RedirectToAction("DatesAndRanges"); - - var model = deliveryDate.ToModel(); - - //locales - AddLocales(_languageService, model.Locales, (locale, languageId) => - { - locale.Name = deliveryDate.GetLocalized(x => x.Name, languageId, false, false); - }); - - return View(model); - } - - [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] - public virtual ActionResult EditDeliveryDate(DeliveryDateModel model, bool continueEditing) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - var deliveryDate = _dateRangeService.GetDeliveryDateById(model.Id); - if (deliveryDate == null) - //No delivery date found with the specified id - return RedirectToAction("DatesAndRanges"); - - if (ModelState.IsValid) - { - deliveryDate = model.ToEntity(deliveryDate); - _dateRangeService.UpdateDeliveryDate(deliveryDate); - - //locales - UpdateLocales(deliveryDate, model); - - SuccessNotification(_localizationService.GetResource("Admin.Configuration.Shipping.DeliveryDates.Updated")); - - return continueEditing ? RedirectToAction("EditDeliveryDate", deliveryDate.Id) : RedirectToAction("DatesAndRanges"); - } - - //If we got this far, something failed, redisplay form - return View(model); - } - - [HttpPost] - public virtual ActionResult DeleteDeliveryDate(int id) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - var deliveryDate = _dateRangeService.GetDeliveryDateById(id); - if (deliveryDate == null) - //No delivery date found with the specified id - return RedirectToAction("DatesAndRanges"); - - _dateRangeService.DeleteDeliveryDate(deliveryDate); - - SuccessNotification(_localizationService.GetResource("Admin.Configuration.Shipping.DeliveryDates.Deleted")); - - return RedirectToAction("DatesAndRanges"); - } - - #endregion - - #region Product availability ranges - - [HttpPost] - public virtual ActionResult ProductAvailabilityRanges(DataSourceRequest command) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedKendoGridJson(); - - var productAvailabilityRangesModel = _dateRangeService.GetAllProductAvailabilityRanges().Select(range => range.ToModel()).ToList(); - var gridModel = new DataSourceResult - { - Data = productAvailabilityRangesModel, - Total = productAvailabilityRangesModel.Count - }; - - return Json(gridModel); - } - - public virtual ActionResult CreateProductAvailabilityRange() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - var model = new ProductAvailabilityRangeModel(); - - //locales - AddLocales(_languageService, model.Locales); - - return View(model); - } - - [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] - public virtual ActionResult CreateProductAvailabilityRange(ProductAvailabilityRangeModel model, bool continueEditing) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - if (ModelState.IsValid) - { - var productAvailabilityRange = model.ToEntity(); - _dateRangeService.InsertProductAvailabilityRange(productAvailabilityRange); - - //locales - UpdateLocales(productAvailabilityRange, model); - - SuccessNotification(_localizationService.GetResource("Admin.Configuration.Shipping.ProductAvailabilityRanges.Added")); - - return continueEditing ? RedirectToAction("EditProductAvailabilityRange", new { id = productAvailabilityRange.Id }) : RedirectToAction("DatesAndRanges"); - } - - //If we got this far, something failed, redisplay form - return View(model); - } - - public virtual ActionResult EditProductAvailabilityRange(int id) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - var productAvailabilityRange = _dateRangeService.GetProductAvailabilityRangeById(id); - if (productAvailabilityRange == null) - //No availability range found with the specified id - return RedirectToAction("DatesAndRanges"); - - var model = productAvailabilityRange.ToModel(); - - //locales - AddLocales(_languageService, model.Locales, (locale, languageId) => - { - locale.Name = productAvailabilityRange.GetLocalized(x => x.Name, languageId, false, false); - }); - - return View(model); - } - - [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] - public virtual ActionResult EditProductAvailabilityRange(ProductAvailabilityRangeModel model, bool continueEditing) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - var productAvailabilityRange = _dateRangeService.GetProductAvailabilityRangeById(model.Id); - if (productAvailabilityRange == null) - //No availability range found with the specified id - return RedirectToAction("DatesAndRanges"); - - if (ModelState.IsValid) - { - productAvailabilityRange = model.ToEntity(productAvailabilityRange); - _dateRangeService.UpdateProductAvailabilityRange(productAvailabilityRange); - - //locales - UpdateLocales(productAvailabilityRange, model); - - SuccessNotification(_localizationService.GetResource("Admin.Configuration.Shipping.ProductAvailabilityRanges.Updated")); - - return continueEditing ? RedirectToAction("EditProductAvailabilityRange", productAvailabilityRange.Id) : RedirectToAction("DatesAndRanges"); - } - - //If we got this far, something failed, redisplay form - return View(model); - } - - [HttpPost] - public virtual ActionResult DeleteProductAvailabilityRange(int id) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - var productAvailabilityRange = _dateRangeService.GetProductAvailabilityRangeById(id); - if (productAvailabilityRange == null) - //No availability range found with the specified id - return RedirectToAction("DatesAndRanges"); - - _dateRangeService.DeleteProductAvailabilityRange(productAvailabilityRange); - - SuccessNotification(_localizationService.GetResource("Admin.Configuration.Shipping.ProductAvailabilityRanges.Deleted")); - - return RedirectToAction("DatesAndRanges"); - } - - #endregion - - #endregion - - #region Warehouses - - public virtual ActionResult Warehouses() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - return View(); - } - - [HttpPost] - public virtual ActionResult Warehouses(DataSourceRequest command) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedKendoGridJson(); - - var warehousesModel = _shippingService.GetAllWarehouses() - .Select(x => - { - var warehouseModel = new WarehouseModel - { - Id = x.Id, - Name = x.Name - //ignore address for list view (performance optimization) - }; - return warehouseModel; - }) - .ToList(); - var gridModel = new DataSourceResult - { - Data = warehousesModel, - Total = warehousesModel.Count - }; - - return Json(gridModel); - } - - - public virtual ActionResult CreateWarehouse() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - var model = new WarehouseModel(); - model.Address.AvailableCountries.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.SelectCountry"), Value = "0" }); - foreach (var c in _countryService.GetAllCountries(showHidden: true)) - model.Address.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id.ToString() }); - model.Address.AvailableStates.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.OtherNonUS"), Value = "0" }); - model.Address.CountryEnabled = true; - model.Address.CountryRequired = true; - model.Address.StateProvinceEnabled = true; - model.Address.CityEnabled = true; - model.Address.StreetAddressEnabled = true; - model.Address.ZipPostalCodeEnabled = true; - model.Address.ZipPostalCodeRequired = true; - model.Address.PhoneEnabled = true; - return View(model); - } - - [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] - public virtual ActionResult CreateWarehouse(WarehouseModel model, bool continueEditing) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - if (ModelState.IsValid) - { - var address = model.Address.ToEntity(); - address.CreatedOnUtc = DateTime.UtcNow; - _addressService.InsertAddress(address); - var warehouse = new Warehouse - { - Name = model.Name, - AdminComment = model.AdminComment, - AddressId = address.Id - }; - - _shippingService.InsertWarehouse(warehouse); - - //activity log - _customerActivityService.InsertActivity("AddNewWarehouse", _localizationService.GetResource("ActivityLog.AddNewWarehouse"), warehouse.Id); - - SuccessNotification(_localizationService.GetResource("Admin.Configuration.Shipping.Warehouses.Added")); - return continueEditing ? RedirectToAction("EditWarehouse", new { id = warehouse.Id }) : RedirectToAction("Warehouses"); - } - - //If we got this far, something failed, redisplay form - //countries - model.Address.AvailableCountries.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.SelectCountry"), Value = "0" }); - foreach (var c in _countryService.GetAllCountries(showHidden: true)) - model.Address.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id.ToString(), Selected = (c.Id == model.Address.CountryId) }); - //states - var states = model.Address.CountryId.HasValue ? _stateProvinceService.GetStateProvincesByCountryId(model.Address.CountryId.Value, showHidden: true).ToList() : new List(); - if (states.Any()) - { - foreach (var s in states) - model.Address.AvailableStates.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString(), Selected = (s.Id == model.Address.StateProvinceId) }); - } - else - model.Address.AvailableStates.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.OtherNonUS"), Value = "0" }); - - return View(model); - } - - public virtual ActionResult EditWarehouse(int id) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - var warehouse = _shippingService.GetWarehouseById(id); - if (warehouse == null) - //No warehouse found with the specified id - return RedirectToAction("Warehouses"); - - var address = _addressService.GetAddressById(warehouse.AddressId); - var model = new WarehouseModel - { - Id = warehouse.Id, - Name = warehouse.Name, - AdminComment = warehouse.AdminComment - }; - - if (address != null) - { - model.Address = address.ToModel(); - } - //countries - model.Address.AvailableCountries.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.SelectCountry"), Value = "0" }); - foreach (var c in _countryService.GetAllCountries(showHidden: true)) - model.Address.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id.ToString(), Selected = (address != null && c.Id == address.CountryId) }); - //states - var states = address != null && address.Country != null ? _stateProvinceService.GetStateProvincesByCountryId(address.Country.Id, showHidden: true).ToList() : new List(); - if (states.Any()) - { - foreach (var s in states) - model.Address.AvailableStates.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString(), Selected = (s.Id == address.StateProvinceId) }); - } - else - model.Address.AvailableStates.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.OtherNonUS"), Value = "0" }); - model.Address.CountryEnabled = true; - model.Address.CountryRequired = true; - model.Address.StateProvinceEnabled = true; - model.Address.CityEnabled = true; - model.Address.StreetAddressEnabled = true; - model.Address.ZipPostalCodeEnabled = true; - model.Address.ZipPostalCodeRequired = true; - model.Address.PhoneEnabled = true; - return View(model); - } - - [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] - public virtual ActionResult EditWarehouse(WarehouseModel model, bool continueEditing) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - var warehouse = _shippingService.GetWarehouseById(model.Id); - if (warehouse == null) - //No warehouse found with the specified id - return RedirectToAction("Warehouses"); - - if (ModelState.IsValid) - { - var address = _addressService.GetAddressById(warehouse.AddressId) ?? - new Core.Domain.Common.Address - { - CreatedOnUtc = DateTime.UtcNow, - }; - address = model.Address.ToEntity(address); - if (address.Id > 0) - _addressService.UpdateAddress(address); - else - _addressService.InsertAddress(address); - - - warehouse.Name = model.Name; - warehouse.AdminComment = model.AdminComment; - warehouse.AddressId = address.Id; - - _shippingService.UpdateWarehouse(warehouse); - - //activity log - _customerActivityService.InsertActivity("EditWarehouse", _localizationService.GetResource("ActivityLog.EditWarehouse"), warehouse.Id); - - SuccessNotification(_localizationService.GetResource("Admin.Configuration.Shipping.Warehouses.Updated")); - return continueEditing ? RedirectToAction("EditWarehouse", warehouse.Id) : RedirectToAction("Warehouses"); - } - - - //If we got this far, something failed, redisplay form - - //countries - model.Address.AvailableCountries.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.SelectCountry"), Value = "0" }); - foreach (var c in _countryService.GetAllCountries(showHidden: true)) - model.Address.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id.ToString(), Selected = (c.Id == model.Address.CountryId) }); - //states - var states = model.Address.CountryId.HasValue ? _stateProvinceService.GetStateProvincesByCountryId(model.Address.CountryId.Value, showHidden: true).ToList() : new List(); - if (states.Any()) - { - foreach (var s in states) - model.Address.AvailableStates.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString(), Selected = (s.Id == model.Address.StateProvinceId) }); - } - else - model.Address.AvailableStates.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.OtherNonUS"), Value = "0" }); - - return View(model); - } - - [HttpPost] - public virtual ActionResult DeleteWarehouse(int id) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - var warehouse = _shippingService.GetWarehouseById(id); - if (warehouse == null) - //No warehouse found with the specified id - return RedirectToAction("Warehouses"); - - _shippingService.DeleteWarehouse(warehouse); - - //activity log - _customerActivityService.InsertActivity("DeleteWarehouse", _localizationService.GetResource("ActivityLog.DeleteWarehouse"), warehouse.Id); - - SuccessNotification(_localizationService.GetResource("Admin.Configuration.Shipping.warehouses.Deleted")); - return RedirectToAction("Warehouses"); - } - - #endregion - - #region Restrictions - - public virtual ActionResult Restrictions() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - var model = new ShippingMethodRestrictionModel(); - - var countries = _countryService.GetAllCountries(showHidden: true); - var shippingMethods = _shippingService.GetAllShippingMethods(); - foreach (var country in countries) - { - model.AvailableCountries.Add(new CountryModel - { - Id = country.Id, - Name = country.Name - }); - } - foreach (var sm in shippingMethods) - { - model.AvailableShippingMethods.Add(new ShippingMethodModel - { - Id = sm.Id, - Name = sm.Name - }); - } - foreach (var country in countries) - foreach (var shippingMethod in shippingMethods) - { - bool restricted = shippingMethod.CountryRestrictionExists(country.Id); - if (!model.Restricted.ContainsKey(country.Id)) - model.Restricted[country.Id] = new Dictionary(); - model.Restricted[country.Id][shippingMethod.Id] = restricted; - } - - return View(model); - } - - [HttpPost, ActionName("Restrictions")] - public virtual ActionResult RestrictionSave(FormCollection form) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); - - var countries = _countryService.GetAllCountries(showHidden: true); - var shippingMethods = _shippingService.GetAllShippingMethods(); - - - foreach (var shippingMethod in shippingMethods) - { - string formKey = "restrict_" + shippingMethod.Id; - var countryIdsToRestrict = form[formKey] != null - ? form[formKey].Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(int.Parse) - .ToList() - : new List(); - - foreach (var country in countries) - { - - bool restrict = countryIdsToRestrict.Contains(country.Id); - if (restrict) - { - if (shippingMethod.RestrictedCountries.FirstOrDefault(c => c.Id == country.Id) == null) - { - shippingMethod.RestrictedCountries.Add(country); - _shippingService.UpdateShippingMethod(shippingMethod); - } - } - else - { - if (shippingMethod.RestrictedCountries.FirstOrDefault(c => c.Id == country.Id) != null) - { - shippingMethod.RestrictedCountries.Remove(country); - _shippingService.UpdateShippingMethod(shippingMethod); - } - } - } - } - - SuccessNotification(_localizationService.GetResource("Admin.Configuration.Shipping.Restrictions.Updated")); - return RedirectToAction("Restrictions"); - } - - #endregion - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web.Mvc; +using System.Web.Routing; +using Nop.Admin.Extensions; +using Nop.Admin.Models.Directory; +using Nop.Admin.Models.Shipping; +using Nop.Core; +using Nop.Core.Domain.Directory; +using Nop.Core.Domain.Shipping; +using Nop.Core.Plugins; +using Nop.Services.Common; +using Nop.Services.Configuration; +using Nop.Services.Directory; +using Nop.Services.Localization; +using Nop.Services.Logging; +using Nop.Services.Security; +using Nop.Services.Shipping; +using Nop.Services.Shipping.Date; +using Nop.Web.Framework.Controllers; +using Nop.Web.Framework.Kendoui; +using Nop.Web.Framework.Mvc; + +namespace Nop.Admin.Controllers +{ + public partial class ShippingController : BaseAdminController + { + #region Fields + + private readonly IShippingService _shippingService; + private readonly ShippingSettings _shippingSettings; + private readonly ISettingService _settingService; + private readonly IAddressService _addressService; + private readonly ICountryService _countryService; + private readonly IStateProvinceService _stateProvinceService; + private readonly ILocalizationService _localizationService; + private readonly IPermissionService _permissionService; + private readonly ILocalizedEntityService _localizedEntityService; + private readonly ILanguageService _languageService; + private readonly IDateRangeService _dateRangeService; + private readonly IPluginFinder _pluginFinder; + private readonly IWebHelper _webHelper; + private readonly ICustomerActivityService _customerActivityService; + + #endregion + + #region Ctor + + public ShippingController(IShippingService shippingService, + ShippingSettings shippingSettings, + ISettingService settingService, + IAddressService addressService, + ICountryService countryService, + IStateProvinceService stateProvinceService, + ILocalizationService localizationService, + IPermissionService permissionService, + ILocalizedEntityService localizedEntityService, + ILanguageService languageService, + IDateRangeService dateRangeService, + IPluginFinder pluginFinder, + IWebHelper webHelper, + ICustomerActivityService customerActivityService) + { + this._shippingService = shippingService; + this._shippingSettings = shippingSettings; + this._settingService = settingService; + this._addressService = addressService; + this._countryService = countryService; + this._stateProvinceService = stateProvinceService; + this._localizationService = localizationService; + this._permissionService = permissionService; + this._localizedEntityService = localizedEntityService; + this._languageService = languageService; + this._dateRangeService = dateRangeService; + this._pluginFinder = pluginFinder; + this._webHelper = webHelper; + this._customerActivityService = customerActivityService; + } + + #endregion  + + #region Utilities + + [NonAction] + protected virtual void UpdateLocales(ShippingMethod shippingMethod, ShippingMethodModel model) + { + foreach (var localized in model.Locales) + { + _localizedEntityService.SaveLocalizedValue(shippingMethod, x => x.Name, localized.Name, localized.LanguageId); + _localizedEntityService.SaveLocalizedValue(shippingMethod, x => x.Description, localized.Description, localized.LanguageId); + } + } + + [NonAction] + protected virtual void UpdateLocales(DeliveryDate deliveryDate, DeliveryDateModel model) + { + foreach (var localized in model.Locales) + { + _localizedEntityService.SaveLocalizedValue(deliveryDate, x => x.Name, localized.Name, localized.LanguageId); + } + } + + [NonAction] + protected virtual void UpdateLocales(ProductAvailabilityRange productAvailabilityRange, ProductAvailabilityRangeModel model) + { + foreach (var localized in model.Locales) + { + _localizedEntityService.SaveLocalizedValue(productAvailabilityRange, x => x.Name, localized.Name, localized.LanguageId); + } + } + + #endregion + + #region Shipping rate computation methods + + public virtual ActionResult Providers() + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + return View(); + } + + [HttpPost] + public virtual ActionResult Providers(DataSourceRequest command) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedKendoGridJson(); + + var shippingProvidersModel = new List(); + var shippingProviders = _shippingService.LoadAllShippingRateComputationMethods(); + foreach (var shippingProvider in shippingProviders) + { + var tmp1 = shippingProvider.ToModel(); + tmp1.IsActive = shippingProvider.IsShippingRateComputationMethodActive(_shippingSettings); + tmp1.LogoUrl = shippingProvider.PluginDescriptor.GetLogoUrl(_webHelper); + shippingProvidersModel.Add(tmp1); + } + shippingProvidersModel = shippingProvidersModel.ToList(); + var gridModel = new DataSourceResult + { + Data = shippingProvidersModel, + Total = shippingProvidersModel.Count() + }; + + return Json(gridModel); + } + + [HttpPost] + public virtual ActionResult ProviderUpdate([Bind(Exclude = "ConfigurationRouteValues")] ShippingRateComputationMethodModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + var srcm = _shippingService.LoadShippingRateComputationMethodBySystemName(model.SystemName); + if (srcm.IsShippingRateComputationMethodActive(_shippingSettings)) + { + if (!model.IsActive) + { + //mark as disabled + _shippingSettings.ActiveShippingRateComputationMethodSystemNames.Remove(srcm.PluginDescriptor.SystemName); + _settingService.SaveSetting(_shippingSettings); + } + } + else + { + if (model.IsActive) + { + //mark as active + _shippingSettings.ActiveShippingRateComputationMethodSystemNames.Add(srcm.PluginDescriptor.SystemName); + _settingService.SaveSetting(_shippingSettings); + } + } + var pluginDescriptor = srcm.PluginDescriptor; + //display order + pluginDescriptor.DisplayOrder = model.DisplayOrder; + PluginFileParser.SavePluginDescriptionFile(pluginDescriptor); + //reset plugin cache + _pluginFinder.ReloadPlugins(); + + return new NullJsonResult(); + } + + public virtual ActionResult ConfigureProvider(string systemName) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + var srcm = _shippingService.LoadShippingRateComputationMethodBySystemName(systemName); + if (srcm == null) + //No shipping rate computation method found with the specified id + return RedirectToAction("Providers"); + + var model = srcm.ToModel(); + string actionName, controllerName; + RouteValueDictionary routeValues; + srcm.GetConfigurationRoute(out actionName, out controllerName, out routeValues); + model.ConfigurationActionName = actionName; + model.ConfigurationControllerName = controllerName; + model.ConfigurationRouteValues = routeValues; + return View(model); + } + + #endregion + + #region Pickup point providers + + public virtual ActionResult PickupPointProviders() + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + return View(); + } + + [HttpPost] + public virtual ActionResult PickupPointProviders(DataSourceRequest command) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedKendoGridJson(); + + var pickupPointProviderModel = new List(); + var allProviders = _shippingService.LoadAllPickupPointProviders(); + foreach (var provider in allProviders) + { + var model = provider.ToModel(); + model.IsActive = provider.IsPickupPointProviderActive(_shippingSettings); + model.LogoUrl = provider.PluginDescriptor.GetLogoUrl(_webHelper); + pickupPointProviderModel.Add(model); + } + + var gridModel = new DataSourceResult + { + Data = pickupPointProviderModel, + Total = pickupPointProviderModel.Count + }; + + return Json(gridModel); + } + + [HttpPost] + public virtual ActionResult PickupPointProviderUpdate([Bind(Exclude = "ConfigurationRouteValues")] PickupPointProviderModel model) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + var pickupPointProvider = _shippingService.LoadPickupPointProviderBySystemName(model.SystemName); + if (pickupPointProvider.IsPickupPointProviderActive(_shippingSettings)) + { + if (!model.IsActive) + { + //mark as disabled + _shippingSettings.ActivePickupPointProviderSystemNames.Remove(pickupPointProvider.PluginDescriptor.SystemName); + _settingService.SaveSetting(_shippingSettings); + } + } + else + { + if (model.IsActive) + { + //mark as active + _shippingSettings.ActivePickupPointProviderSystemNames.Add(pickupPointProvider.PluginDescriptor.SystemName); + _settingService.SaveSetting(_shippingSettings); + } + } + var pluginDescriptor = pickupPointProvider.PluginDescriptor; + pluginDescriptor.DisplayOrder = model.DisplayOrder; + PluginFileParser.SavePluginDescriptionFile(pluginDescriptor); + //reset plugin cache + _pluginFinder.ReloadPlugins(); + + return new NullJsonResult(); + } + + public virtual ActionResult ConfigurePickupPointProvider(string systemName) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + var pickupPointProvider = _shippingService.LoadPickupPointProviderBySystemName(systemName); + if (pickupPointProvider == null) + return RedirectToAction("PickupPointProviders"); + + var model = pickupPointProvider.ToModel(); + string actionName; + string controllerName; + RouteValueDictionary routeValues; + pickupPointProvider.GetConfigurationRoute(out actionName, out controllerName, out routeValues); + model.ConfigurationActionName = actionName; + model.ConfigurationControllerName = controllerName; + model.ConfigurationRouteValues = routeValues; + return View(model); + } + + #endregion + + #region Shipping methods + + public virtual ActionResult Methods() + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + return View(); + } + + [HttpPost] + public virtual ActionResult Methods(DataSourceRequest command) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedKendoGridJson(); + + var shippingMethodsModel = _shippingService.GetAllShippingMethods() + .Select(x => x.ToModel()) + .ToList(); + var gridModel = new DataSourceResult + { + Data = shippingMethodsModel, + Total = shippingMethodsModel.Count + }; + + return Json(gridModel); + } + + + public virtual ActionResult CreateMethod() + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + var model = new ShippingMethodModel(); + //locales + AddLocales(_languageService, model.Locales); + return View(model); + } + + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] + public virtual ActionResult CreateMethod(ShippingMethodModel model, bool continueEditing) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + if (ModelState.IsValid) + { + var sm = model.ToEntity(); + _shippingService.InsertShippingMethod(sm); + //locales + UpdateLocales(sm, model); + + SuccessNotification(_localizationService.GetResource("Admin.Configuration.Shipping.Methods.Added")); + return continueEditing ? RedirectToAction("EditMethod", new { id = sm.Id }) : RedirectToAction("Methods"); + } + + //If we got this far, something failed, redisplay form + return View(model); + } + + public virtual ActionResult EditMethod(int id) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + var sm = _shippingService.GetShippingMethodById(id); + if (sm == null) + //No shipping method found with the specified id + return RedirectToAction("Methods"); + + var model = sm.ToModel(); + //locales + AddLocales(_languageService, model.Locales, (locale, languageId) => + { + locale.Name = sm.GetLocalized(x => x.Name, languageId, false, false); + locale.Description = sm.GetLocalized(x => x.Description, languageId, false, false); + }); + + return View(model); + } + + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] + public virtual ActionResult EditMethod(ShippingMethodModel model, bool continueEditing) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + var sm = _shippingService.GetShippingMethodById(model.Id); + if (sm == null) + //No shipping method found with the specified id + return RedirectToAction("Methods"); + + if (ModelState.IsValid) + { + sm = model.ToEntity(sm); + _shippingService.UpdateShippingMethod(sm); + //locales + UpdateLocales(sm, model); + SuccessNotification(_localizationService.GetResource("Admin.Configuration.Shipping.Methods.Updated")); + return continueEditing ? RedirectToAction("EditMethod", sm.Id) : RedirectToAction("Methods"); + } + + + //If we got this far, something failed, redisplay form + return View(model); + } + + [HttpPost] + public virtual ActionResult DeleteMethod(int id) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + var sm = _shippingService.GetShippingMethodById(id); + if (sm == null) + //No shipping method found with the specified id + return RedirectToAction("Methods"); + + _shippingService.DeleteShippingMethod(sm); + + SuccessNotification(_localizationService.GetResource("Admin.Configuration.Shipping.Methods.Deleted")); + return RedirectToAction("Methods"); + } + + #endregion + + #region Dates and ranges + + public virtual ActionResult DatesAndRanges() + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + return View(); + } + + #region Delivery dates + + [HttpPost] + public virtual ActionResult DeliveryDates(DataSourceRequest command) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedKendoGridJson(); + + var deliveryDatesModel = _dateRangeService.GetAllDeliveryDates().Select(x => x.ToModel()).ToList(); + var gridModel = new DataSourceResult + { + Data = deliveryDatesModel, + Total = deliveryDatesModel.Count + }; + + return Json(gridModel); + } + + + public virtual ActionResult CreateDeliveryDate() + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + var model = new DeliveryDateModel(); + + //locales + AddLocales(_languageService, model.Locales); + + return View(model); + } + + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] + public virtual ActionResult CreateDeliveryDate(DeliveryDateModel model, bool continueEditing) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + if (ModelState.IsValid) + { + var deliveryDate = model.ToEntity(); + _dateRangeService.InsertDeliveryDate(deliveryDate); + + //locales + UpdateLocales(deliveryDate, model); + + SuccessNotification(_localizationService.GetResource("Admin.Configuration.Shipping.DeliveryDates.Added")); + + return continueEditing ? RedirectToAction("EditDeliveryDate", new { id = deliveryDate.Id }) : RedirectToAction("DatesAndRanges"); + } + + //If we got this far, something failed, redisplay form + return View(model); + } + + public virtual ActionResult EditDeliveryDate(int id) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + var deliveryDate = _dateRangeService.GetDeliveryDateById(id); + if (deliveryDate == null) + //No delivery date found with the specified id + return RedirectToAction("DatesAndRanges"); + + var model = deliveryDate.ToModel(); + + //locales + AddLocales(_languageService, model.Locales, (locale, languageId) => + { + locale.Name = deliveryDate.GetLocalized(x => x.Name, languageId, false, false); + }); + + return View(model); + } + + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] + public virtual ActionResult EditDeliveryDate(DeliveryDateModel model, bool continueEditing) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + var deliveryDate = _dateRangeService.GetDeliveryDateById(model.Id); + if (deliveryDate == null) + //No delivery date found with the specified id + return RedirectToAction("DatesAndRanges"); + + if (ModelState.IsValid) + { + deliveryDate = model.ToEntity(deliveryDate); + _dateRangeService.UpdateDeliveryDate(deliveryDate); + + //locales + UpdateLocales(deliveryDate, model); + + SuccessNotification(_localizationService.GetResource("Admin.Configuration.Shipping.DeliveryDates.Updated")); + + return continueEditing ? RedirectToAction("EditDeliveryDate", deliveryDate.Id) : RedirectToAction("DatesAndRanges"); + } + + //If we got this far, something failed, redisplay form + return View(model); + } + + [HttpPost] + public virtual ActionResult DeleteDeliveryDate(int id) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + var deliveryDate = _dateRangeService.GetDeliveryDateById(id); + if (deliveryDate == null) + //No delivery date found with the specified id + return RedirectToAction("DatesAndRanges"); + + _dateRangeService.DeleteDeliveryDate(deliveryDate); + + SuccessNotification(_localizationService.GetResource("Admin.Configuration.Shipping.DeliveryDates.Deleted")); + + return RedirectToAction("DatesAndRanges"); + } + + #endregion + + #region Product availability ranges + + [HttpPost] + public virtual ActionResult ProductAvailabilityRanges(DataSourceRequest command) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedKendoGridJson(); + + var productAvailabilityRangesModel = _dateRangeService.GetAllProductAvailabilityRanges().Select(range => range.ToModel()).ToList(); + var gridModel = new DataSourceResult + { + Data = productAvailabilityRangesModel, + Total = productAvailabilityRangesModel.Count + }; + + return Json(gridModel); + } + + public virtual ActionResult CreateProductAvailabilityRange() + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + var model = new ProductAvailabilityRangeModel(); + + //locales + AddLocales(_languageService, model.Locales); + + return View(model); + } + + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] + public virtual ActionResult CreateProductAvailabilityRange(ProductAvailabilityRangeModel model, bool continueEditing) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + if (ModelState.IsValid) + { + var productAvailabilityRange = model.ToEntity(); + _dateRangeService.InsertProductAvailabilityRange(productAvailabilityRange); + + //locales + UpdateLocales(productAvailabilityRange, model); + + SuccessNotification(_localizationService.GetResource("Admin.Configuration.Shipping.ProductAvailabilityRanges.Added")); + + return continueEditing ? RedirectToAction("EditProductAvailabilityRange", new { id = productAvailabilityRange.Id }) : RedirectToAction("DatesAndRanges"); + } + + //If we got this far, something failed, redisplay form + return View(model); + } + + public virtual ActionResult EditProductAvailabilityRange(int id) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + var productAvailabilityRange = _dateRangeService.GetProductAvailabilityRangeById(id); + if (productAvailabilityRange == null) + //No availability range found with the specified id + return RedirectToAction("DatesAndRanges"); + + var model = productAvailabilityRange.ToModel(); + + //locales + AddLocales(_languageService, model.Locales, (locale, languageId) => + { + locale.Name = productAvailabilityRange.GetLocalized(x => x.Name, languageId, false, false); + }); + + return View(model); + } + + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] + public virtual ActionResult EditProductAvailabilityRange(ProductAvailabilityRangeModel model, bool continueEditing) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + var productAvailabilityRange = _dateRangeService.GetProductAvailabilityRangeById(model.Id); + if (productAvailabilityRange == null) + //No availability range found with the specified id + return RedirectToAction("DatesAndRanges"); + + if (ModelState.IsValid) + { + productAvailabilityRange = model.ToEntity(productAvailabilityRange); + _dateRangeService.UpdateProductAvailabilityRange(productAvailabilityRange); + + //locales + UpdateLocales(productAvailabilityRange, model); + + SuccessNotification(_localizationService.GetResource("Admin.Configuration.Shipping.ProductAvailabilityRanges.Updated")); + + return continueEditing ? RedirectToAction("EditProductAvailabilityRange", productAvailabilityRange.Id) : RedirectToAction("DatesAndRanges"); + } + + //If we got this far, something failed, redisplay form + return View(model); + } + + [HttpPost] + public virtual ActionResult DeleteProductAvailabilityRange(int id) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + var productAvailabilityRange = _dateRangeService.GetProductAvailabilityRangeById(id); + if (productAvailabilityRange == null) + //No availability range found with the specified id + return RedirectToAction("DatesAndRanges"); + + _dateRangeService.DeleteProductAvailabilityRange(productAvailabilityRange); + + SuccessNotification(_localizationService.GetResource("Admin.Configuration.Shipping.ProductAvailabilityRanges.Deleted")); + + return RedirectToAction("DatesAndRanges"); + } + + #endregion + + #endregion + + #region Warehouses + + public virtual ActionResult Warehouses() + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + return View(); + } + + [HttpPost] + public virtual ActionResult Warehouses(DataSourceRequest command) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedKendoGridJson(); + + var warehousesModel = _shippingService.GetAllWarehouses() + .Select(x => + { + var warehouseModel = new WarehouseModel + { + Id = x.Id, + Name = x.Name + //ignore address for list view (performance optimization) + }; + return warehouseModel; + }) + .ToList(); + var gridModel = new DataSourceResult + { + Data = warehousesModel, + Total = warehousesModel.Count + }; + + return Json(gridModel); + } + + + public virtual ActionResult CreateWarehouse() + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + var model = new WarehouseModel(); + model.Address.AvailableCountries.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.SelectCountry"), Value = "0" }); + foreach (var c in _countryService.GetAllCountries(showHidden: true)) + model.Address.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id.ToString() }); + model.Address.AvailableStates.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.OtherNonUS"), Value = "0" }); + model.Address.CountryEnabled = true; + model.Address.CountryRequired = true; + model.Address.StateProvinceEnabled = true; + model.Address.StateProvinceRequired = true; + model.Address.CityEnabled = true; + model.Address.StreetAddressEnabled = true; + model.Address.ZipPostalCodeEnabled = true; + model.Address.ZipPostalCodeRequired = true; + model.Address.PhoneEnabled = true; + return View(model); + } + + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] + public virtual ActionResult CreateWarehouse(WarehouseModel model, bool continueEditing) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + if (ModelState.IsValid) + { + var address = model.Address.ToEntity(); + address.CreatedOnUtc = DateTime.UtcNow; + _addressService.InsertAddress(address); + var warehouse = new Warehouse + { + Name = model.Name, + AdminComment = model.AdminComment, + AddressId = address.Id + }; + + _shippingService.InsertWarehouse(warehouse); + + //activity log + _customerActivityService.InsertActivity("AddNewWarehouse", _localizationService.GetResource("ActivityLog.AddNewWarehouse"), warehouse.Id); + + SuccessNotification(_localizationService.GetResource("Admin.Configuration.Shipping.Warehouses.Added")); + return continueEditing ? RedirectToAction("EditWarehouse", new { id = warehouse.Id }) : RedirectToAction("Warehouses"); + } + + //If we got this far, something failed, redisplay form + //countries + model.Address.AvailableCountries.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.SelectCountry"), Value = "0" }); + foreach (var c in _countryService.GetAllCountries(showHidden: true)) + model.Address.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id.ToString(), Selected = (c.Id == model.Address.CountryId) }); + //states + var states = model.Address.CountryId.HasValue ? _stateProvinceService.GetStateProvincesByCountryId(model.Address.CountryId.Value, showHidden: true).ToList() : new List(); + if (states.Any()) + { + foreach (var s in states) + model.Address.AvailableStates.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString(), Selected = (s.Id == model.Address.StateProvinceId) }); + } + else + model.Address.AvailableStates.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.OtherNonUS"), Value = "0" }); + + return View(model); + } + + public virtual ActionResult EditWarehouse(int id) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + var warehouse = _shippingService.GetWarehouseById(id); + if (warehouse == null) + //No warehouse found with the specified id + return RedirectToAction("Warehouses"); + + var address = _addressService.GetAddressById(warehouse.AddressId); + var model = new WarehouseModel + { + Id = warehouse.Id, + Name = warehouse.Name, + AdminComment = warehouse.AdminComment + }; + + if (address != null) + { + model.Address = address.ToModel(); + } + //countries + model.Address.AvailableCountries.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.SelectCountry"), Value = "0" }); + foreach (var c in _countryService.GetAllCountries(showHidden: true)) + model.Address.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id.ToString(), Selected = (address != null && c.Id == address.CountryId) }); + //states + var states = address != null && address.Country != null ? _stateProvinceService.GetStateProvincesByCountryId(address.Country.Id, showHidden: true).ToList() : new List(); + if (states.Any()) + { + foreach (var s in states) + model.Address.AvailableStates.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString(), Selected = (s.Id == address.StateProvinceId) }); + } + else + model.Address.AvailableStates.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.OtherNonUS"), Value = "0" }); + model.Address.CountryEnabled = true; + model.Address.CountryRequired = true; + model.Address.StateProvinceEnabled = true; + model.Address.StateProvinceRequired = true; + model.Address.CityEnabled = true; + model.Address.StreetAddressEnabled = true; + model.Address.ZipPostalCodeEnabled = true; + model.Address.ZipPostalCodeRequired = true; + model.Address.PhoneEnabled = true; + return View(model); + } + + [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] + public virtual ActionResult EditWarehouse(WarehouseModel model, bool continueEditing) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + var warehouse = _shippingService.GetWarehouseById(model.Id); + if (warehouse == null) + //No warehouse found with the specified id + return RedirectToAction("Warehouses"); + + if (ModelState.IsValid) + { + var address = _addressService.GetAddressById(warehouse.AddressId) ?? + new Core.Domain.Common.Address + { + CreatedOnUtc = DateTime.UtcNow, + }; + address = model.Address.ToEntity(address); + if (address.Id > 0) + _addressService.UpdateAddress(address); + else + _addressService.InsertAddress(address); + + + warehouse.Name = model.Name; + warehouse.AdminComment = model.AdminComment; + warehouse.AddressId = address.Id; + + _shippingService.UpdateWarehouse(warehouse); + + //activity log + _customerActivityService.InsertActivity("EditWarehouse", _localizationService.GetResource("ActivityLog.EditWarehouse"), warehouse.Id); + + SuccessNotification(_localizationService.GetResource("Admin.Configuration.Shipping.Warehouses.Updated")); + return continueEditing ? RedirectToAction("EditWarehouse", warehouse.Id) : RedirectToAction("Warehouses"); + } + + + //If we got this far, something failed, redisplay form + + //countries + model.Address.AvailableCountries.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.SelectCountry"), Value = "0" }); + foreach (var c in _countryService.GetAllCountries(showHidden: true)) + model.Address.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id.ToString(), Selected = (c.Id == model.Address.CountryId) }); + //states + var states = model.Address.CountryId.HasValue ? _stateProvinceService.GetStateProvincesByCountryId(model.Address.CountryId.Value, showHidden: true).ToList() : new List(); + if (states.Any()) + { + foreach (var s in states) + model.Address.AvailableStates.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString(), Selected = (s.Id == model.Address.StateProvinceId) }); + } + else + model.Address.AvailableStates.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.OtherNonUS"), Value = "0" }); + + return View(model); + } + + [HttpPost] + public virtual ActionResult DeleteWarehouse(int id) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + var warehouse = _shippingService.GetWarehouseById(id); + if (warehouse == null) + //No warehouse found with the specified id + return RedirectToAction("Warehouses"); + + _shippingService.DeleteWarehouse(warehouse); + + //activity log + _customerActivityService.InsertActivity("DeleteWarehouse", _localizationService.GetResource("ActivityLog.DeleteWarehouse"), warehouse.Id); + + SuccessNotification(_localizationService.GetResource("Admin.Configuration.Shipping.warehouses.Deleted")); + return RedirectToAction("Warehouses"); + } + + #endregion + + #region Restrictions + + public virtual ActionResult Restrictions() + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + var model = new ShippingMethodRestrictionModel(); + + var countries = _countryService.GetAllCountries(showHidden: true); + var shippingMethods = _shippingService.GetAllShippingMethods(); + foreach (var country in countries) + { + model.AvailableCountries.Add(new CountryModel + { + Id = country.Id, + Name = country.Name + }); + } + foreach (var sm in shippingMethods) + { + model.AvailableShippingMethods.Add(new ShippingMethodModel + { + Id = sm.Id, + Name = sm.Name + }); + } + foreach (var country in countries) + foreach (var shippingMethod in shippingMethods) + { + bool restricted = shippingMethod.CountryRestrictionExists(country.Id); + if (!model.Restricted.ContainsKey(country.Id)) + model.Restricted[country.Id] = new Dictionary(); + model.Restricted[country.Id][shippingMethod.Id] = restricted; + } + + return View(model); + } + + [HttpPost, ActionName("Restrictions")] + public virtual ActionResult RestrictionSave(FormCollection form) + { + if (!_permissionService.Authorize(StandardPermissionProvider.ManageShippingSettings)) + return AccessDeniedView(); + + var countries = _countryService.GetAllCountries(showHidden: true); + var shippingMethods = _shippingService.GetAllShippingMethods(); + + + foreach (var shippingMethod in shippingMethods) + { + string formKey = "restrict_" + shippingMethod.Id; + var countryIdsToRestrict = form[formKey] != null + ? form[formKey].Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(int.Parse) + .ToList() + : new List(); + + foreach (var country in countries) + { + + bool restrict = countryIdsToRestrict.Contains(country.Id); + if (restrict) + { + if (shippingMethod.RestrictedCountries.FirstOrDefault(c => c.Id == country.Id) == null) + { + shippingMethod.RestrictedCountries.Add(country); + _shippingService.UpdateShippingMethod(shippingMethod); + } + } + else + { + if (shippingMethod.RestrictedCountries.FirstOrDefault(c => c.Id == country.Id) != null) + { + shippingMethod.RestrictedCountries.Remove(country); + _shippingService.UpdateShippingMethod(shippingMethod); + } + } + } + } + + SuccessNotification(_localizationService.GetResource("Admin.Configuration.Shipping.Restrictions.Updated")); + return RedirectToAction("Restrictions"); + } + + #endregion + } +} diff --git a/src/Presentation/Nop.Web/Administration/Infrastructure/Mapper/AdminMapperConfiguration.cs b/src/Presentation/Nop.Web/Administration/Infrastructure/Mapper/AdminMapperConfiguration.cs index 058da925e2e..e3fdfbfa9e2 100644 --- a/src/Presentation/Nop.Web/Administration/Infrastructure/Mapper/AdminMapperConfiguration.cs +++ b/src/Presentation/Nop.Web/Administration/Infrastructure/Mapper/AdminMapperConfiguration.cs @@ -893,7 +893,8 @@ public Action GetConfiguration() .ForMember(dest => dest.ActivatePointsImmediately, mo => mo.Ignore()) .ForMember(dest => dest.DisplayHowMuchWillBeEarned_OverrideForStore, mo => mo.Ignore()) .ForMember(dest => dest.PageSize_OverrideForStore, mo => mo.Ignore()) - .ForMember(dest => dest.CustomProperties, mo => mo.Ignore()); + .ForMember(dest => dest.CustomProperties, mo => mo.Ignore()) + .ForMember(dest => dest.EarnedRewardPointsAreTaxable_OverrideForStore, mo => mo.Ignore()); cfg.CreateMap(); cfg.CreateMap() .ForMember(dest => dest.PrimaryStoreCurrencyCode, mo => mo.Ignore()) @@ -950,7 +951,8 @@ public Action GetConfiguration() cfg.CreateMap() .ForMember(dest => dest.RoundPricesDuringCalculation, mo => mo.Ignore()) .ForMember(dest => dest.GroupTierPricesForDistinctShoppingCartItems, mo => mo.Ignore()) - .ForMember(dest => dest.RenderAssociatedAttributeValueQuantity, mo => mo.Ignore()); + .ForMember(dest => dest.RenderAssociatedAttributeValueQuantity, mo => mo.Ignore()) + .ForMember(dest => dest.RenderProductAttributePrices, mo => mo.Ignore()); cfg.CreateMap() .ForMember(dest => dest.PicturesStoredIntoDatabase, mo => mo.Ignore()) .ForMember(dest => dest.ActiveStoreScopeConfiguration, mo => mo.Ignore()) diff --git a/src/Presentation/Nop.Web/Administration/Models/Catalog/ProductModel.cs b/src/Presentation/Nop.Web/Administration/Models/Catalog/ProductModel.cs index 142158122ce..bcb1d8453c4 100644 --- a/src/Presentation/Nop.Web/Administration/Models/Catalog/ProductModel.cs +++ b/src/Presentation/Nop.Web/Administration/Models/Catalog/ProductModel.cs @@ -1,1024 +1,1032 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Web.Mvc; -using FluentValidation.Attributes; -using Nop.Admin.Models.Settings; -using Nop.Admin.Validators.Catalog; -using Nop.Web.Framework; -using Nop.Web.Framework.Localization; -using Nop.Web.Framework.Mvc; - -namespace Nop.Admin.Models.Catalog -{ - [Validator(typeof(ProductValidator))] - public partial class ProductModel : BaseNopEntityModel, ILocalizedModel - { - public ProductModel() - { - Locales = new List(); - ProductPictureModels = new List(); - CopyProductModel = new CopyProductModel(); - AddPictureModel = new ProductPictureModel(); - AddSpecificationAttributeModel = new AddProductSpecificationAttributeModel(); - ProductWarehouseInventoryModels = new List(); - ProductEditorSettingsModel = new ProductEditorSettingsModel(); - StockQuantityHistory = new StockQuantityHistoryModel(); - - AvailableBasepriceUnits = new List(); - AvailableBasepriceBaseUnits = new List(); - AvailableProductTemplates = new List(); - AvailableTaxCategories = new List(); - AvailableDeliveryDates = new List(); - AvailableProductAvailabilityRanges = new List(); - AvailableWarehouses = new List(); - AvailableProductAttributes = new List(); - ProductsTypesSupportedByProductTemplates = new Dictionary>(); - - AvailableVendors = new List(); - - SelectedStoreIds = new List(); - AvailableStores = new List(); - - SelectedManufacturerIds = new List(); - AvailableManufacturers = new List(); - - SelectedCategoryIds = new List(); - AvailableCategories = new List(); - - SelectedCustomerRoleIds = new List(); - AvailableCustomerRoles = new List(); - - SelectedDiscountIds = new List(); - AvailableDiscounts = new List(); - } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.ID")] - public override int Id { get; set; } - - //picture thumbnail - [NopResourceDisplayName("Admin.Catalog.Products.Fields.PictureThumbnailUrl")] - public string PictureThumbnailUrl { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.ProductType")] - public int ProductTypeId { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.Fields.ProductType")] - public string ProductTypeName { get; set; } - - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.AssociatedToProductName")] - public int AssociatedToProductId { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.Fields.AssociatedToProductName")] - public string AssociatedToProductName { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.VisibleIndividually")] - public bool VisibleIndividually { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.ProductTemplate")] - public int ProductTemplateId { get; set; } - public IList AvailableProductTemplates { get; set; } - // - public Dictionary> ProductsTypesSupportedByProductTemplates { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.Name")] - [AllowHtml] - public string Name { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.ShortDescription")] - [AllowHtml] - public string ShortDescription { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.FullDescription")] - [AllowHtml] - public string FullDescription { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.AdminComment")] - [AllowHtml] - public string AdminComment { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.ShowOnHomePage")] - public bool ShowOnHomePage { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.MetaKeywords")] - [AllowHtml] - public string MetaKeywords { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.MetaDescription")] - [AllowHtml] - public string MetaDescription { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.MetaTitle")] - [AllowHtml] - public string MetaTitle { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.SeName")] - [AllowHtml] - public string SeName { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.AllowCustomerReviews")] - public bool AllowCustomerReviews { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.ProductTags")] - public string ProductTags { get; set; } - - - - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.Sku")] - [AllowHtml] - public string Sku { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.ManufacturerPartNumber")] - [AllowHtml] - public string ManufacturerPartNumber { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.GTIN")] - [AllowHtml] - public virtual string Gtin { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.IsGiftCard")] - public bool IsGiftCard { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.Fields.GiftCardType")] - public int GiftCardTypeId { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.Fields.OverriddenGiftCardAmount")] - [UIHint("DecimalNullable")] - public decimal? OverriddenGiftCardAmount { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.RequireOtherProducts")] - public bool RequireOtherProducts { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.RequiredProductIds")] - public string RequiredProductIds { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.AutomaticallyAddRequiredProducts")] - public bool AutomaticallyAddRequiredProducts { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.IsDownload")] - public bool IsDownload { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.Download")] - [UIHint("Download")] - public int DownloadId { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.UnlimitedDownloads")] - public bool UnlimitedDownloads { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.MaxNumberOfDownloads")] - public int MaxNumberOfDownloads { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.DownloadExpirationDays")] - [UIHint("Int32Nullable")] - public int? DownloadExpirationDays { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.DownloadActivationType")] - public int DownloadActivationTypeId { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.HasSampleDownload")] - public bool HasSampleDownload { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.SampleDownload")] - [UIHint("Download")] - public int SampleDownloadId { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.HasUserAgreement")] - public bool HasUserAgreement { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.UserAgreementText")] - [AllowHtml] - public string UserAgreementText { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.IsRecurring")] - public bool IsRecurring { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.RecurringCycleLength")] - public int RecurringCycleLength { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.RecurringCyclePeriod")] - public int RecurringCyclePeriodId { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.RecurringTotalCycles")] - public int RecurringTotalCycles { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.IsRental")] - public bool IsRental { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.RentalPriceLength")] - public int RentalPriceLength { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.RentalPricePeriod")] - public int RentalPricePeriodId { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.IsShipEnabled")] - public bool IsShipEnabled { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.IsFreeShipping")] - public bool IsFreeShipping { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.ShipSeparately")] - public bool ShipSeparately { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.AdditionalShippingCharge")] - public decimal AdditionalShippingCharge { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.DeliveryDate")] - public int DeliveryDateId { get; set; } - public IList AvailableDeliveryDates { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.IsTaxExempt")] - public bool IsTaxExempt { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.TaxCategory")] - public int TaxCategoryId { get; set; } - public IList AvailableTaxCategories { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.IsTelecommunicationsOrBroadcastingOrElectronicServices")] - public bool IsTelecommunicationsOrBroadcastingOrElectronicServices { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.ManageInventoryMethod")] - public int ManageInventoryMethodId { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.ProductAvailabilityRange")] - public int ProductAvailabilityRangeId { get; set; } - public IList AvailableProductAvailabilityRanges { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.UseMultipleWarehouses")] - public bool UseMultipleWarehouses { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.Warehouse")] - public int WarehouseId { get; set; } - public IList AvailableWarehouses { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.StockQuantity")] - public int StockQuantity { get; set; } - public int LastStockQuantity { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.Fields.StockQuantity")] - public string StockQuantityStr { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.DisplayStockAvailability")] - public bool DisplayStockAvailability { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.DisplayStockQuantity")] - public bool DisplayStockQuantity { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.MinStockQuantity")] - public int MinStockQuantity { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.LowStockActivity")] - public int LowStockActivityId { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.NotifyAdminForQuantityBelow")] - public int NotifyAdminForQuantityBelow { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.BackorderMode")] - public int BackorderModeId { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.AllowBackInStockSubscriptions")] - public bool AllowBackInStockSubscriptions { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.OrderMinimumQuantity")] - public int OrderMinimumQuantity { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.OrderMaximumQuantity")] - public int OrderMaximumQuantity { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.AllowedQuantities")] - public string AllowedQuantities { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.AllowAddingOnlyExistingAttributeCombinations")] - public bool AllowAddingOnlyExistingAttributeCombinations { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.NotReturnable")] - public bool NotReturnable { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.DisableBuyButton")] - public bool DisableBuyButton { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.DisableWishlistButton")] - public bool DisableWishlistButton { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.AvailableForPreOrder")] - public bool AvailableForPreOrder { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.PreOrderAvailabilityStartDateTimeUtc")] - [UIHint("DateTimeNullable")] - public DateTime? PreOrderAvailabilityStartDateTimeUtc { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.CallForPrice")] - public bool CallForPrice { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.Price")] - public decimal Price { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.OldPrice")] - public decimal OldPrice { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.ProductCost")] - public decimal ProductCost { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.CustomerEntersPrice")] - public bool CustomerEntersPrice { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.MinimumCustomerEnteredPrice")] - public decimal MinimumCustomerEnteredPrice { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.MaximumCustomerEnteredPrice")] - public decimal MaximumCustomerEnteredPrice { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.BasepriceEnabled")] - public bool BasepriceEnabled { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.Fields.BasepriceAmount")] - public decimal BasepriceAmount { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.Fields.BasepriceUnit")] - public int BasepriceUnitId { get; set; } - public IList AvailableBasepriceUnits { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.Fields.BasepriceBaseAmount")] - public decimal BasepriceBaseAmount { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.Fields.BasepriceBaseUnit")] - public int BasepriceBaseUnitId { get; set; } - public IList AvailableBasepriceBaseUnits { get; set; } - - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.MarkAsNew")] - public bool MarkAsNew { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.Fields.MarkAsNewStartDateTimeUtc")] - [UIHint("DateTimeNullable")] - public DateTime? MarkAsNewStartDateTimeUtc { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.Fields.MarkAsNewEndDateTimeUtc")] - [UIHint("DateTimeNullable")] - public DateTime? MarkAsNewEndDateTimeUtc { get; set; } - - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.Weight")] - public decimal Weight { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.Length")] - public decimal Length { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.Width")] - public decimal Width { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.Height")] - public decimal Height { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.AvailableStartDateTime")] - [UIHint("DateTimeNullable")] - public DateTime? AvailableStartDateTimeUtc { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.AvailableEndDateTime")] - [UIHint("DateTimeNullable")] - public DateTime? AvailableEndDateTimeUtc { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.DisplayOrder")] - public int DisplayOrder { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.Published")] - public bool Published { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.CreatedOn")] - public DateTime? CreatedOn { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.Fields.UpdatedOn")] - public DateTime? UpdatedOn { get; set; } - - - public string PrimaryStoreCurrencyCode { get; set; } - public string BaseDimensionIn { get; set; } - public string BaseWeightIn { get; set; } - - public IList Locales { get; set; } - - - - //ACL (customer roles) - [NopResourceDisplayName("Admin.Catalog.Products.Fields.AclCustomerRoles")] - [UIHint("MultiSelect")] - public IList SelectedCustomerRoleIds { get; set; } - public IList AvailableCustomerRoles { get; set; } - - //store mapping - [NopResourceDisplayName("Admin.Catalog.Products.Fields.LimitedToStores")] - [UIHint("MultiSelect")] - public IList SelectedStoreIds { get; set; } - public IList AvailableStores { get; set; } - - //categories - [NopResourceDisplayName("Admin.Catalog.Products.Fields.Categories")] - [UIHint("MultiSelect")] - public IList SelectedCategoryIds { get; set; } - public IList AvailableCategories { get; set; } - - //manufacturers - [NopResourceDisplayName("Admin.Catalog.Products.Fields.Manufacturers")] - [UIHint("MultiSelect")] - public IList SelectedManufacturerIds { get; set; } - public IList AvailableManufacturers { get; set; } - - //vendors - [NopResourceDisplayName("Admin.Catalog.Products.Fields.Vendor")] - public int VendorId { get; set; } - public IList AvailableVendors { get; set; } - - //discounts - [NopResourceDisplayName("Admin.Catalog.Products.Fields.Discounts")] - [UIHint("MultiSelect")] - public IList SelectedDiscountIds { get; set; } - public IList AvailableDiscounts { get; set; } - - //vendor - public bool IsLoggedInAsVendor { get; set; } - - //product attributes - public IList AvailableProductAttributes { get; set; } - - //pictures - public ProductPictureModel AddPictureModel { get; set; } - public IList ProductPictureModels { get; set; } - - //add specification attribute model - public AddProductSpecificationAttributeModel AddSpecificationAttributeModel { get; set; } - - //multiple warehouses - [NopResourceDisplayName("Admin.Catalog.Products.ProductWarehouseInventory")] - public IList ProductWarehouseInventoryModels { get; set; } - - //copy product - public CopyProductModel CopyProductModel { get; set; } - - //editor settings - public ProductEditorSettingsModel ProductEditorSettingsModel { get; set; } - - //stock quantity history - public StockQuantityHistoryModel StockQuantityHistory { get; set; } - - #region Nested classes - - public partial class AddRequiredProductModel : BaseNopModel - { - public AddRequiredProductModel() - { - AvailableCategories = new List(); - AvailableManufacturers = new List(); - AvailableStores = new List(); - AvailableVendors = new List(); - AvailableProductTypes = new List(); - } - - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchProductName")] - [AllowHtml] - public string SearchProductName { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchCategory")] - public int SearchCategoryId { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchManufacturer")] - public int SearchManufacturerId { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchStore")] - public int SearchStoreId { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchVendor")] - public int SearchVendorId { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchProductType")] - public int SearchProductTypeId { get; set; } - - public IList AvailableCategories { get; set; } - public IList AvailableManufacturers { get; set; } - public IList AvailableStores { get; set; } - public IList AvailableVendors { get; set; } - public IList AvailableProductTypes { get; set; } - - //vendor - public bool IsLoggedInAsVendor { get; set; } - } - - public partial class AddProductSpecificationAttributeModel : BaseNopModel - { - public AddProductSpecificationAttributeModel() - { - AvailableAttributes = new List(); - AvailableOptions = new List(); - } - - [NopResourceDisplayName("Admin.Catalog.Products.SpecificationAttributes.Fields.SpecificationAttribute")] - public int SpecificationAttributeId { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.SpecificationAttributes.Fields.AttributeType")] - public int AttributeTypeId { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.SpecificationAttributes.Fields.SpecificationAttributeOption")] - public int SpecificationAttributeOptionId { get; set; } - - [AllowHtml] - [NopResourceDisplayName("Admin.Catalog.Products.SpecificationAttributes.Fields.CustomValue")] - public string CustomValue { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.SpecificationAttributes.Fields.AllowFiltering")] - public bool AllowFiltering { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.SpecificationAttributes.Fields.ShowOnProductPage")] - public bool ShowOnProductPage { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.SpecificationAttributes.Fields.DisplayOrder")] - public int DisplayOrder { get; set; } - - public IList AvailableAttributes { get; set; } - public IList AvailableOptions { get; set; } - } - - public partial class ProductPictureModel : BaseNopEntityModel - { - public int ProductId { get; set; } - - [UIHint("Picture")] - [NopResourceDisplayName("Admin.Catalog.Products.Pictures.Fields.Picture")] - public int PictureId { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Pictures.Fields.Picture")] - public string PictureUrl { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Pictures.Fields.DisplayOrder")] - public int DisplayOrder { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Pictures.Fields.OverrideAltAttribute")] - [AllowHtml] - public string OverrideAltAttribute { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Pictures.Fields.OverrideTitleAttribute")] - [AllowHtml] - public string OverrideTitleAttribute { get; set; } - } - - public partial class RelatedProductModel : BaseNopEntityModel - { - public int ProductId2 { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.RelatedProducts.Fields.Product")] - public string Product2Name { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.RelatedProducts.Fields.DisplayOrder")] - public int DisplayOrder { get; set; } - } - public partial class AddRelatedProductModel : BaseNopModel - { - public AddRelatedProductModel() - { - AvailableCategories = new List(); - AvailableManufacturers = new List(); - AvailableStores = new List(); - AvailableVendors = new List(); - AvailableProductTypes = new List(); - } - - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchProductName")] - [AllowHtml] - public string SearchProductName { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchCategory")] - public int SearchCategoryId { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchManufacturer")] - public int SearchManufacturerId { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchStore")] - public int SearchStoreId { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchVendor")] - public int SearchVendorId { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchProductType")] - public int SearchProductTypeId { get; set; } - - public IList AvailableCategories { get; set; } - public IList AvailableManufacturers { get; set; } - public IList AvailableStores { get; set; } - public IList AvailableVendors { get; set; } - public IList AvailableProductTypes { get; set; } - - public int ProductId { get; set; } - - public int[] SelectedProductIds { get; set; } - - //vendor - public bool IsLoggedInAsVendor { get; set; } - } - - public partial class AssociatedProductModel : BaseNopEntityModel - { - [NopResourceDisplayName("Admin.Catalog.Products.AssociatedProducts.Fields.Product")] - public string ProductName { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.AssociatedProducts.Fields.DisplayOrder")] - public int DisplayOrder { get; set; } - } - public partial class AddAssociatedProductModel : BaseNopModel - { - public AddAssociatedProductModel() - { - AvailableCategories = new List(); - AvailableManufacturers = new List(); - AvailableStores = new List(); - AvailableVendors = new List(); - AvailableProductTypes = new List(); - } - - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchProductName")] - [AllowHtml] - public string SearchProductName { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchCategory")] - public int SearchCategoryId { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchManufacturer")] - public int SearchManufacturerId { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchStore")] - public int SearchStoreId { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchVendor")] - public int SearchVendorId { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchProductType")] - public int SearchProductTypeId { get; set; } - - public IList AvailableCategories { get; set; } - public IList AvailableManufacturers { get; set; } - public IList AvailableStores { get; set; } - public IList AvailableVendors { get; set; } - public IList AvailableProductTypes { get; set; } - - public int ProductId { get; set; } - - public int[] SelectedProductIds { get; set; } - - //vendor - public bool IsLoggedInAsVendor { get; set; } - } - - public partial class CrossSellProductModel : BaseNopEntityModel - { - public int ProductId2 { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.CrossSells.Fields.Product")] - public string Product2Name { get; set; } - } - public partial class AddCrossSellProductModel : BaseNopModel - { - public AddCrossSellProductModel() - { - AvailableCategories = new List(); - AvailableManufacturers = new List(); - AvailableStores = new List(); - AvailableVendors = new List(); - AvailableProductTypes = new List(); - } - - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchProductName")] - [AllowHtml] - public string SearchProductName { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchCategory")] - public int SearchCategoryId { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchManufacturer")] - public int SearchManufacturerId { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchStore")] - public int SearchStoreId { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchVendor")] - public int SearchVendorId { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchProductType")] - public int SearchProductTypeId { get; set; } - - public IList AvailableCategories { get; set; } - public IList AvailableManufacturers { get; set; } - public IList AvailableStores { get; set; } - public IList AvailableVendors { get; set; } - public IList AvailableProductTypes { get; set; } - - public int ProductId { get; set; } - - public int[] SelectedProductIds { get; set; } - - //vendor - public bool IsLoggedInAsVendor { get; set; } - } - - public partial class TierPriceModel : BaseNopEntityModel - { - public TierPriceModel() - { - AvailableStores = new List(); - AvailableCustomerRoles = new List(); - } - - public int ProductId { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.TierPrices.Fields.CustomerRole")] - public int CustomerRoleId { get; set; } - public IList AvailableCustomerRoles { get; set; } - public string CustomerRole { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.TierPrices.Fields.Store")] - public int StoreId { get; set; } - public IList AvailableStores { get; set; } - public string Store { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.TierPrices.Fields.Quantity")] - public int Quantity { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.TierPrices.Fields.Price")] - public decimal Price { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.TierPrices.Fields.StartDateTimeUtc")] - [UIHint("DateTimeNullable")] - public DateTime? StartDateTimeUtc { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.TierPrices.Fields.EndDateTimeUtc")] - [UIHint("DateTimeNullable")] - public DateTime? EndDateTimeUtc { get; set; } - } - - public partial class ProductWarehouseInventoryModel : BaseNopModel - { - [NopResourceDisplayName("Admin.Catalog.Products.ProductWarehouseInventory.Fields.Warehouse")] - public int WarehouseId { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.ProductWarehouseInventory.Fields.Warehouse")] - public string WarehouseName { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.ProductWarehouseInventory.Fields.WarehouseUsed")] - public bool WarehouseUsed { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.ProductWarehouseInventory.Fields.StockQuantity")] - public int StockQuantity { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.ProductWarehouseInventory.Fields.ReservedQuantity")] - public int ReservedQuantity { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.ProductWarehouseInventory.Fields.PlannedQuantity")] - public int PlannedQuantity { get; set; } - } - - - public partial class ProductAttributeMappingModel : BaseNopEntityModel - { - public int ProductId { get; set; } - - public int ProductAttributeId { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Fields.Attribute")] - public string ProductAttribute { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Fields.TextPrompt")] - [AllowHtml] - public string TextPrompt { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Fields.IsRequired")] - public bool IsRequired { get; set; } - - public int AttributeControlTypeId { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Fields.AttributeControlType")] - public string AttributeControlType { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Fields.DisplayOrder")] - public int DisplayOrder { get; set; } - - public bool ShouldHaveValues { get; set; } - public int TotalValues { get; set; } - - //validation fields - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.ValidationRules")] - public bool ValidationRulesAllowed { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.ValidationRules.MinLength")] - [UIHint("Int32Nullable")] - public int? ValidationMinLength { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.ValidationRules.MaxLength")] - [UIHint("Int32Nullable")] - public int? ValidationMaxLength { get; set; } - [AllowHtml] - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.ValidationRules.FileAllowedExtensions")] - public string ValidationFileAllowedExtensions { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.ValidationRules.FileMaximumSize")] - [UIHint("Int32Nullable")] - public int? ValidationFileMaximumSize { get; set; } - [AllowHtml] - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.ValidationRules.DefaultValue")] - public string DefaultValue { get; set; } - public string ValidationRulesString { get; set; } - - //condition - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Condition")] - public bool ConditionAllowed { get; set; } - public string ConditionString { get; set; } - } - public partial class ProductAttributeValueListModel : BaseNopModel - { - public int ProductId { get; set; } - - public string ProductName { get; set; } - - public int ProductAttributeMappingId { get; set; } - - public string ProductAttributeName { get; set; } - } - [Validator(typeof(ProductAttributeValueModelValidator))] - public partial class ProductAttributeValueModel : BaseNopEntityModel, ILocalizedModel - { - public ProductAttributeValueModel() - { - ProductPictureModels = new List(); - Locales = new List(); - } - - public int ProductAttributeMappingId { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.AttributeValueType")] - public int AttributeValueTypeId { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.AttributeValueType")] - public string AttributeValueTypeName { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.AssociatedProduct")] - public int AssociatedProductId { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.AssociatedProduct")] - public string AssociatedProductName { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.Name")] - [AllowHtml] - public string Name { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.ColorSquaresRgb")] - [AllowHtml] - public string ColorSquaresRgb { get; set; } - public bool DisplayColorSquaresRgb { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.ImageSquaresPicture")] - [UIHint("Picture")] - public int ImageSquaresPictureId { get; set; } - public bool DisplayImageSquaresPicture { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.PriceAdjustment")] - public decimal PriceAdjustment { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.PriceAdjustment")] - //used only on the values list page - public string PriceAdjustmentStr { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.WeightAdjustment")] - public decimal WeightAdjustment { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.WeightAdjustment")] - //used only on the values list page - public string WeightAdjustmentStr { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.Cost")] - public decimal Cost { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.CustomerEntersQty")] - public bool CustomerEntersQty { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.Quantity")] - public int Quantity { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.IsPreSelected")] - public bool IsPreSelected { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.DisplayOrder")] - public int DisplayOrder { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.Picture")] - public int PictureId { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.Picture")] - public string PictureThumbnailUrl { get; set; } - - public IList ProductPictureModels { get; set; } - public IList Locales { get; set; } - - #region Nested classes - - public partial class AssociateProductToAttributeValueModel : BaseNopModel - { - public AssociateProductToAttributeValueModel() - { - AvailableCategories = new List(); - AvailableManufacturers = new List(); - AvailableStores = new List(); - AvailableVendors = new List(); - AvailableProductTypes = new List(); - } - - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchProductName")] - [AllowHtml] - public string SearchProductName { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchCategory")] - public int SearchCategoryId { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchManufacturer")] - public int SearchManufacturerId { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchStore")] - public int SearchStoreId { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchVendor")] - public int SearchVendorId { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchProductType")] - public int SearchProductTypeId { get; set; } - - public IList AvailableCategories { get; set; } - public IList AvailableManufacturers { get; set; } - public IList AvailableStores { get; set; } - public IList AvailableVendors { get; set; } - public IList AvailableProductTypes { get; set; } - - //vendor - public bool IsLoggedInAsVendor { get; set; } - - - public int AssociatedToProductId { get; set; } - } - #endregion - } - public partial class ProductAttributeValueLocalizedModel : ILocalizedModelLocal - { - public int LanguageId { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.Name")] - [AllowHtml] - public string Name { get; set; } - } - public partial class ProductAttributeCombinationModel : BaseNopEntityModel - { - public int ProductId { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.AttributeCombinations.Fields.Attributes")] - [AllowHtml] - public string AttributesXml { get; set; } - - [AllowHtml] - public string Warnings { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.AttributeCombinations.Fields.StockQuantity")] - public int StockQuantity { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.AttributeCombinations.Fields.AllowOutOfStockOrders")] - public bool AllowOutOfStockOrders { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.AttributeCombinations.Fields.Sku")] - public string Sku { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.AttributeCombinations.Fields.ManufacturerPartNumber")] - public string ManufacturerPartNumber { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.AttributeCombinations.Fields.Gtin")] - public string Gtin { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.AttributeCombinations.Fields.OverriddenPrice")] - [UIHint("DecimalNullable")] - public decimal? OverriddenPrice { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.AttributeCombinations.Fields.NotifyAdminForQuantityBelow")] - public int NotifyAdminForQuantityBelow { get; set; } - - } - - #region Stock quantity history - - public partial class StockQuantityHistoryModel : BaseNopEntityModel - { - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchWarehouse")] - public int SearchWarehouseId { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.StockQuantityHistory.Fields.Warehouse")] - [AllowHtml] - public string WarehouseName { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.StockQuantityHistory.Fields.Combination")] - [AllowHtml] - public string AttributeCombination { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.StockQuantityHistory.Fields.QuantityAdjustment")] - public int QuantityAdjustment { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.StockQuantityHistory.Fields.StockQuantity")] - public int StockQuantity { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.StockQuantityHistory.Fields.Message")] - [AllowHtml] - public string Message { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.StockQuantityHistory.Fields.CreatedOn")] - [UIHint("DecimalNullable")] - public DateTime CreatedOn { get; set; } - } - - #endregion - - #endregion - } - - public partial class ProductLocalizedModel : ILocalizedModelLocal - { - public int LanguageId { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.Name")] - [AllowHtml] - public string Name { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.ShortDescription")] - [AllowHtml] - public string ShortDescription { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.FullDescription")] - [AllowHtml] - public string FullDescription { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.MetaKeywords")] - [AllowHtml] - public string MetaKeywords { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.MetaDescription")] - [AllowHtml] - public string MetaDescription { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.MetaTitle")] - [AllowHtml] - public string MetaTitle { get; set; } - - [NopResourceDisplayName("Admin.Catalog.Products.Fields.SeName")] - [AllowHtml] - public string SeName { get; set; } - } +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Web.Mvc; +using FluentValidation.Attributes; +using Nop.Admin.Models.Settings; +using Nop.Admin.Validators.Catalog; +using Nop.Web.Framework; +using Nop.Web.Framework.Localization; +using Nop.Web.Framework.Mvc; + +namespace Nop.Admin.Models.Catalog +{ + [Validator(typeof(ProductValidator))] + public partial class ProductModel : BaseNopEntityModel, ILocalizedModel + { + public ProductModel() + { + Locales = new List(); + ProductPictureModels = new List(); + CopyProductModel = new CopyProductModel(); + AddPictureModel = new ProductPictureModel(); + AddSpecificationAttributeModel = new AddProductSpecificationAttributeModel(); + ProductWarehouseInventoryModels = new List(); + ProductEditorSettingsModel = new ProductEditorSettingsModel(); + StockQuantityHistory = new StockQuantityHistoryModel(); + + AvailableBasepriceUnits = new List(); + AvailableBasepriceBaseUnits = new List(); + AvailableProductTemplates = new List(); + AvailableTaxCategories = new List(); + AvailableDeliveryDates = new List(); + AvailableProductAvailabilityRanges = new List(); + AvailableWarehouses = new List(); + AvailableProductAttributes = new List(); + ProductsTypesSupportedByProductTemplates = new Dictionary>(); + + AvailableVendors = new List(); + + SelectedStoreIds = new List(); + AvailableStores = new List(); + + SelectedManufacturerIds = new List(); + AvailableManufacturers = new List(); + + SelectedCategoryIds = new List(); + AvailableCategories = new List(); + + SelectedCustomerRoleIds = new List(); + AvailableCustomerRoles = new List(); + + SelectedDiscountIds = new List(); + AvailableDiscounts = new List(); + } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.ID")] + public override int Id { get; set; } + + //picture thumbnail + [NopResourceDisplayName("Admin.Catalog.Products.Fields.PictureThumbnailUrl")] + public string PictureThumbnailUrl { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.ProductType")] + public int ProductTypeId { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.Fields.ProductType")] + public string ProductTypeName { get; set; } + + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.AssociatedToProductName")] + public int AssociatedToProductId { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.Fields.AssociatedToProductName")] + public string AssociatedToProductName { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.VisibleIndividually")] + public bool VisibleIndividually { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.ProductTemplate")] + public int ProductTemplateId { get; set; } + public IList AvailableProductTemplates { get; set; } + // + public Dictionary> ProductsTypesSupportedByProductTemplates { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.Name")] + [AllowHtml] + public string Name { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.ShortDescription")] + [AllowHtml] + public string ShortDescription { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.FullDescription")] + [AllowHtml] + public string FullDescription { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.AdminComment")] + [AllowHtml] + public string AdminComment { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.ShowOnHomePage")] + public bool ShowOnHomePage { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.MetaKeywords")] + [AllowHtml] + public string MetaKeywords { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.MetaDescription")] + [AllowHtml] + public string MetaDescription { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.MetaTitle")] + [AllowHtml] + public string MetaTitle { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.SeName")] + [AllowHtml] + public string SeName { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.AllowCustomerReviews")] + public bool AllowCustomerReviews { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.ProductTags")] + public string ProductTags { get; set; } + + + + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.Sku")] + [AllowHtml] + public string Sku { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.ManufacturerPartNumber")] + [AllowHtml] + public string ManufacturerPartNumber { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.GTIN")] + [AllowHtml] + public virtual string Gtin { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.IsGiftCard")] + public bool IsGiftCard { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.Fields.GiftCardType")] + public int GiftCardTypeId { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.Fields.OverriddenGiftCardAmount")] + [UIHint("DecimalNullable")] + public decimal? OverriddenGiftCardAmount { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.IsRewardPoints")] + public bool IsRewardPoints { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.Fields.OverriddenRPExchangeRate")] + [UIHint("DecimalNullable")] + public decimal? OverriddenRPExchangeRate { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.Fields.ExcludeFromRewardPoints")] + public bool ExcludeFromRewardPoints { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.RequireOtherProducts")] + public bool RequireOtherProducts { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.RequiredProductIds")] + public string RequiredProductIds { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.AutomaticallyAddRequiredProducts")] + public bool AutomaticallyAddRequiredProducts { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.IsDownload")] + public bool IsDownload { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.Download")] + [UIHint("Download")] + public int DownloadId { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.UnlimitedDownloads")] + public bool UnlimitedDownloads { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.MaxNumberOfDownloads")] + public int MaxNumberOfDownloads { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.DownloadExpirationDays")] + [UIHint("Int32Nullable")] + public int? DownloadExpirationDays { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.DownloadActivationType")] + public int DownloadActivationTypeId { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.HasSampleDownload")] + public bool HasSampleDownload { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.SampleDownload")] + [UIHint("Download")] + public int SampleDownloadId { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.HasUserAgreement")] + public bool HasUserAgreement { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.UserAgreementText")] + [AllowHtml] + public string UserAgreementText { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.IsRecurring")] + public bool IsRecurring { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.RecurringCycleLength")] + public int RecurringCycleLength { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.RecurringCyclePeriod")] + public int RecurringCyclePeriodId { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.RecurringTotalCycles")] + public int RecurringTotalCycles { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.IsRental")] + public bool IsRental { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.RentalPriceLength")] + public int RentalPriceLength { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.RentalPricePeriod")] + public int RentalPricePeriodId { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.IsShipEnabled")] + public bool IsShipEnabled { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.IsFreeShipping")] + public bool IsFreeShipping { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.ShipSeparately")] + public bool ShipSeparately { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.AdditionalShippingCharge")] + public decimal AdditionalShippingCharge { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.DeliveryDate")] + public int DeliveryDateId { get; set; } + public IList AvailableDeliveryDates { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.IsTaxExempt")] + public bool IsTaxExempt { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.TaxCategory")] + public int TaxCategoryId { get; set; } + public IList AvailableTaxCategories { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.IsTelecommunicationsOrBroadcastingOrElectronicServices")] + public bool IsTelecommunicationsOrBroadcastingOrElectronicServices { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.ManageInventoryMethod")] + public int ManageInventoryMethodId { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.ProductAvailabilityRange")] + public int ProductAvailabilityRangeId { get; set; } + public IList AvailableProductAvailabilityRanges { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.UseMultipleWarehouses")] + public bool UseMultipleWarehouses { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.Warehouse")] + public int WarehouseId { get; set; } + public IList AvailableWarehouses { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.StockQuantity")] + public int StockQuantity { get; set; } + public int LastStockQuantity { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.Fields.StockQuantity")] + public string StockQuantityStr { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.DisplayStockAvailability")] + public bool DisplayStockAvailability { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.DisplayStockQuantity")] + public bool DisplayStockQuantity { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.MinStockQuantity")] + public int MinStockQuantity { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.LowStockActivity")] + public int LowStockActivityId { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.NotifyAdminForQuantityBelow")] + public int NotifyAdminForQuantityBelow { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.BackorderMode")] + public int BackorderModeId { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.AllowBackInStockSubscriptions")] + public bool AllowBackInStockSubscriptions { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.OrderMinimumQuantity")] + public int OrderMinimumQuantity { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.OrderMaximumQuantity")] + public int OrderMaximumQuantity { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.AllowedQuantities")] + public string AllowedQuantities { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.AllowAddingOnlyExistingAttributeCombinations")] + public bool AllowAddingOnlyExistingAttributeCombinations { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.NotReturnable")] + public bool NotReturnable { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.DisableBuyButton")] + public bool DisableBuyButton { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.DisableWishlistButton")] + public bool DisableWishlistButton { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.AvailableForPreOrder")] + public bool AvailableForPreOrder { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.PreOrderAvailabilityStartDateTimeUtc")] + [UIHint("DateTimeNullable")] + public DateTime? PreOrderAvailabilityStartDateTimeUtc { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.CallForPrice")] + public bool CallForPrice { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.Price")] + public decimal Price { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.OldPrice")] + public decimal OldPrice { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.ProductCost")] + public decimal ProductCost { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.CustomerEntersPrice")] + public bool CustomerEntersPrice { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.MinimumCustomerEnteredPrice")] + public decimal MinimumCustomerEnteredPrice { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.MaximumCustomerEnteredPrice")] + public decimal MaximumCustomerEnteredPrice { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.BasepriceEnabled")] + public bool BasepriceEnabled { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.Fields.BasepriceAmount")] + public decimal BasepriceAmount { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.Fields.BasepriceUnit")] + public int BasepriceUnitId { get; set; } + public IList AvailableBasepriceUnits { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.Fields.BasepriceBaseAmount")] + public decimal BasepriceBaseAmount { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.Fields.BasepriceBaseUnit")] + public int BasepriceBaseUnitId { get; set; } + public IList AvailableBasepriceBaseUnits { get; set; } + + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.MarkAsNew")] + public bool MarkAsNew { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.Fields.MarkAsNewStartDateTimeUtc")] + [UIHint("DateTimeNullable")] + public DateTime? MarkAsNewStartDateTimeUtc { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.Fields.MarkAsNewEndDateTimeUtc")] + [UIHint("DateTimeNullable")] + public DateTime? MarkAsNewEndDateTimeUtc { get; set; } + + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.Weight")] + public decimal Weight { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.Length")] + public decimal Length { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.Width")] + public decimal Width { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.Height")] + public decimal Height { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.AvailableStartDateTime")] + [UIHint("DateTimeNullable")] + public DateTime? AvailableStartDateTimeUtc { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.AvailableEndDateTime")] + [UIHint("DateTimeNullable")] + public DateTime? AvailableEndDateTimeUtc { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.DisplayOrder")] + public int DisplayOrder { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.Published")] + public bool Published { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.CreatedOn")] + public DateTime? CreatedOn { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.Fields.UpdatedOn")] + public DateTime? UpdatedOn { get; set; } + + + public string PrimaryStoreCurrencyCode { get; set; } + public string BaseDimensionIn { get; set; } + public string BaseWeightIn { get; set; } + + public IList Locales { get; set; } + + + + //ACL (customer roles) + [NopResourceDisplayName("Admin.Catalog.Products.Fields.AclCustomerRoles")] + [UIHint("MultiSelect")] + public IList SelectedCustomerRoleIds { get; set; } + public IList AvailableCustomerRoles { get; set; } + + //store mapping + [NopResourceDisplayName("Admin.Catalog.Products.Fields.LimitedToStores")] + [UIHint("MultiSelect")] + public IList SelectedStoreIds { get; set; } + public IList AvailableStores { get; set; } + + //categories + [NopResourceDisplayName("Admin.Catalog.Products.Fields.Categories")] + [UIHint("MultiSelect")] + public IList SelectedCategoryIds { get; set; } + public IList AvailableCategories { get; set; } + + //manufacturers + [NopResourceDisplayName("Admin.Catalog.Products.Fields.Manufacturers")] + [UIHint("MultiSelect")] + public IList SelectedManufacturerIds { get; set; } + public IList AvailableManufacturers { get; set; } + + //vendors + [NopResourceDisplayName("Admin.Catalog.Products.Fields.Vendor")] + public int VendorId { get; set; } + public IList AvailableVendors { get; set; } + + //discounts + [NopResourceDisplayName("Admin.Catalog.Products.Fields.Discounts")] + [UIHint("MultiSelect")] + public IList SelectedDiscountIds { get; set; } + public IList AvailableDiscounts { get; set; } + + //vendor + public bool IsLoggedInAsVendor { get; set; } + + //product attributes + public IList AvailableProductAttributes { get; set; } + + //pictures + public ProductPictureModel AddPictureModel { get; set; } + public IList ProductPictureModels { get; set; } + + //add specification attribute model + public AddProductSpecificationAttributeModel AddSpecificationAttributeModel { get; set; } + + //multiple warehouses + [NopResourceDisplayName("Admin.Catalog.Products.ProductWarehouseInventory")] + public IList ProductWarehouseInventoryModels { get; set; } + + //copy product + public CopyProductModel CopyProductModel { get; set; } + + //editor settings + public ProductEditorSettingsModel ProductEditorSettingsModel { get; set; } + + //stock quantity history + public StockQuantityHistoryModel StockQuantityHistory { get; set; } + + #region Nested classes + + public partial class AddRequiredProductModel : BaseNopModel + { + public AddRequiredProductModel() + { + AvailableCategories = new List(); + AvailableManufacturers = new List(); + AvailableStores = new List(); + AvailableVendors = new List(); + AvailableProductTypes = new List(); + } + + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchProductName")] + [AllowHtml] + public string SearchProductName { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchCategory")] + public int SearchCategoryId { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchManufacturer")] + public int SearchManufacturerId { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchStore")] + public int SearchStoreId { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchVendor")] + public int SearchVendorId { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchProductType")] + public int SearchProductTypeId { get; set; } + + public IList AvailableCategories { get; set; } + public IList AvailableManufacturers { get; set; } + public IList AvailableStores { get; set; } + public IList AvailableVendors { get; set; } + public IList AvailableProductTypes { get; set; } + + //vendor + public bool IsLoggedInAsVendor { get; set; } + } + + public partial class AddProductSpecificationAttributeModel : BaseNopModel + { + public AddProductSpecificationAttributeModel() + { + AvailableAttributes = new List(); + AvailableOptions = new List(); + } + + [NopResourceDisplayName("Admin.Catalog.Products.SpecificationAttributes.Fields.SpecificationAttribute")] + public int SpecificationAttributeId { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.SpecificationAttributes.Fields.AttributeType")] + public int AttributeTypeId { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.SpecificationAttributes.Fields.SpecificationAttributeOption")] + public int SpecificationAttributeOptionId { get; set; } + + [AllowHtml] + [NopResourceDisplayName("Admin.Catalog.Products.SpecificationAttributes.Fields.CustomValue")] + public string CustomValue { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.SpecificationAttributes.Fields.AllowFiltering")] + public bool AllowFiltering { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.SpecificationAttributes.Fields.ShowOnProductPage")] + public bool ShowOnProductPage { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.SpecificationAttributes.Fields.DisplayOrder")] + public int DisplayOrder { get; set; } + + public IList AvailableAttributes { get; set; } + public IList AvailableOptions { get; set; } + } + + public partial class ProductPictureModel : BaseNopEntityModel + { + public int ProductId { get; set; } + + [UIHint("Picture")] + [NopResourceDisplayName("Admin.Catalog.Products.Pictures.Fields.Picture")] + public int PictureId { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Pictures.Fields.Picture")] + public string PictureUrl { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Pictures.Fields.DisplayOrder")] + public int DisplayOrder { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Pictures.Fields.OverrideAltAttribute")] + [AllowHtml] + public string OverrideAltAttribute { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Pictures.Fields.OverrideTitleAttribute")] + [AllowHtml] + public string OverrideTitleAttribute { get; set; } + } + + public partial class RelatedProductModel : BaseNopEntityModel + { + public int ProductId2 { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.RelatedProducts.Fields.Product")] + public string Product2Name { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.RelatedProducts.Fields.DisplayOrder")] + public int DisplayOrder { get; set; } + } + public partial class AddRelatedProductModel : BaseNopModel + { + public AddRelatedProductModel() + { + AvailableCategories = new List(); + AvailableManufacturers = new List(); + AvailableStores = new List(); + AvailableVendors = new List(); + AvailableProductTypes = new List(); + } + + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchProductName")] + [AllowHtml] + public string SearchProductName { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchCategory")] + public int SearchCategoryId { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchManufacturer")] + public int SearchManufacturerId { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchStore")] + public int SearchStoreId { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchVendor")] + public int SearchVendorId { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchProductType")] + public int SearchProductTypeId { get; set; } + + public IList AvailableCategories { get; set; } + public IList AvailableManufacturers { get; set; } + public IList AvailableStores { get; set; } + public IList AvailableVendors { get; set; } + public IList AvailableProductTypes { get; set; } + + public int ProductId { get; set; } + + public int[] SelectedProductIds { get; set; } + + //vendor + public bool IsLoggedInAsVendor { get; set; } + } + + public partial class AssociatedProductModel : BaseNopEntityModel + { + [NopResourceDisplayName("Admin.Catalog.Products.AssociatedProducts.Fields.Product")] + public string ProductName { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.AssociatedProducts.Fields.DisplayOrder")] + public int DisplayOrder { get; set; } + } + public partial class AddAssociatedProductModel : BaseNopModel + { + public AddAssociatedProductModel() + { + AvailableCategories = new List(); + AvailableManufacturers = new List(); + AvailableStores = new List(); + AvailableVendors = new List(); + AvailableProductTypes = new List(); + } + + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchProductName")] + [AllowHtml] + public string SearchProductName { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchCategory")] + public int SearchCategoryId { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchManufacturer")] + public int SearchManufacturerId { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchStore")] + public int SearchStoreId { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchVendor")] + public int SearchVendorId { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchProductType")] + public int SearchProductTypeId { get; set; } + + public IList AvailableCategories { get; set; } + public IList AvailableManufacturers { get; set; } + public IList AvailableStores { get; set; } + public IList AvailableVendors { get; set; } + public IList AvailableProductTypes { get; set; } + + public int ProductId { get; set; } + + public int[] SelectedProductIds { get; set; } + + //vendor + public bool IsLoggedInAsVendor { get; set; } + } + + public partial class CrossSellProductModel : BaseNopEntityModel + { + public int ProductId2 { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.CrossSells.Fields.Product")] + public string Product2Name { get; set; } + } + public partial class AddCrossSellProductModel : BaseNopModel + { + public AddCrossSellProductModel() + { + AvailableCategories = new List(); + AvailableManufacturers = new List(); + AvailableStores = new List(); + AvailableVendors = new List(); + AvailableProductTypes = new List(); + } + + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchProductName")] + [AllowHtml] + public string SearchProductName { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchCategory")] + public int SearchCategoryId { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchManufacturer")] + public int SearchManufacturerId { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchStore")] + public int SearchStoreId { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchVendor")] + public int SearchVendorId { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchProductType")] + public int SearchProductTypeId { get; set; } + + public IList AvailableCategories { get; set; } + public IList AvailableManufacturers { get; set; } + public IList AvailableStores { get; set; } + public IList AvailableVendors { get; set; } + public IList AvailableProductTypes { get; set; } + + public int ProductId { get; set; } + + public int[] SelectedProductIds { get; set; } + + //vendor + public bool IsLoggedInAsVendor { get; set; } + } + + public partial class TierPriceModel : BaseNopEntityModel + { + public TierPriceModel() + { + AvailableStores = new List(); + AvailableCustomerRoles = new List(); + } + + public int ProductId { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.TierPrices.Fields.CustomerRole")] + public int CustomerRoleId { get; set; } + public IList AvailableCustomerRoles { get; set; } + public string CustomerRole { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.TierPrices.Fields.Store")] + public int StoreId { get; set; } + public IList AvailableStores { get; set; } + public string Store { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.TierPrices.Fields.Quantity")] + public int Quantity { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.TierPrices.Fields.Price")] + public decimal Price { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.TierPrices.Fields.StartDateTimeUtc")] + [UIHint("DateTimeNullable")] + public DateTime? StartDateTimeUtc { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.TierPrices.Fields.EndDateTimeUtc")] + [UIHint("DateTimeNullable")] + public DateTime? EndDateTimeUtc { get; set; } + } + + public partial class ProductWarehouseInventoryModel : BaseNopModel + { + [NopResourceDisplayName("Admin.Catalog.Products.ProductWarehouseInventory.Fields.Warehouse")] + public int WarehouseId { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.ProductWarehouseInventory.Fields.Warehouse")] + public string WarehouseName { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.ProductWarehouseInventory.Fields.WarehouseUsed")] + public bool WarehouseUsed { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.ProductWarehouseInventory.Fields.StockQuantity")] + public int StockQuantity { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.ProductWarehouseInventory.Fields.ReservedQuantity")] + public int ReservedQuantity { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.ProductWarehouseInventory.Fields.PlannedQuantity")] + public int PlannedQuantity { get; set; } + } + + + public partial class ProductAttributeMappingModel : BaseNopEntityModel + { + public int ProductId { get; set; } + + public int ProductAttributeId { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Fields.Attribute")] + public string ProductAttribute { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Fields.TextPrompt")] + [AllowHtml] + public string TextPrompt { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Fields.IsRequired")] + public bool IsRequired { get; set; } + + public int AttributeControlTypeId { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Fields.AttributeControlType")] + public string AttributeControlType { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Fields.DisplayOrder")] + public int DisplayOrder { get; set; } + + public bool ShouldHaveValues { get; set; } + public int TotalValues { get; set; } + + //validation fields + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.ValidationRules")] + public bool ValidationRulesAllowed { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.ValidationRules.MinLength")] + [UIHint("Int32Nullable")] + public int? ValidationMinLength { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.ValidationRules.MaxLength")] + [UIHint("Int32Nullable")] + public int? ValidationMaxLength { get; set; } + [AllowHtml] + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.ValidationRules.FileAllowedExtensions")] + public string ValidationFileAllowedExtensions { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.ValidationRules.FileMaximumSize")] + [UIHint("Int32Nullable")] + public int? ValidationFileMaximumSize { get; set; } + [AllowHtml] + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.ValidationRules.DefaultValue")] + public string DefaultValue { get; set; } + public string ValidationRulesString { get; set; } + + //condition + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Condition")] + public bool ConditionAllowed { get; set; } + public string ConditionString { get; set; } + } + public partial class ProductAttributeValueListModel : BaseNopModel + { + public int ProductId { get; set; } + + public string ProductName { get; set; } + + public int ProductAttributeMappingId { get; set; } + + public string ProductAttributeName { get; set; } + } + [Validator(typeof(ProductAttributeValueModelValidator))] + public partial class ProductAttributeValueModel : BaseNopEntityModel, ILocalizedModel + { + public ProductAttributeValueModel() + { + ProductPictureModels = new List(); + Locales = new List(); + } + + public int ProductAttributeMappingId { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.AttributeValueType")] + public int AttributeValueTypeId { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.AttributeValueType")] + public string AttributeValueTypeName { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.AssociatedProduct")] + public int AssociatedProductId { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.AssociatedProduct")] + public string AssociatedProductName { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.Name")] + [AllowHtml] + public string Name { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.ColorSquaresRgb")] + [AllowHtml] + public string ColorSquaresRgb { get; set; } + public bool DisplayColorSquaresRgb { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.ImageSquaresPicture")] + [UIHint("Picture")] + public int ImageSquaresPictureId { get; set; } + public bool DisplayImageSquaresPicture { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.PriceAdjustment")] + public decimal PriceAdjustment { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.PriceAdjustment")] + //used only on the values list page + public string PriceAdjustmentStr { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.WeightAdjustment")] + public decimal WeightAdjustment { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.WeightAdjustment")] + //used only on the values list page + public string WeightAdjustmentStr { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.Cost")] + public decimal Cost { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.CustomerEntersQty")] + public bool CustomerEntersQty { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.Quantity")] + public int Quantity { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.IsPreSelected")] + public bool IsPreSelected { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.DisplayOrder")] + public int DisplayOrder { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.Picture")] + public int PictureId { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.Picture")] + public string PictureThumbnailUrl { get; set; } + + public IList ProductPictureModels { get; set; } + public IList Locales { get; set; } + + #region Nested classes + + public partial class AssociateProductToAttributeValueModel : BaseNopModel + { + public AssociateProductToAttributeValueModel() + { + AvailableCategories = new List(); + AvailableManufacturers = new List(); + AvailableStores = new List(); + AvailableVendors = new List(); + AvailableProductTypes = new List(); + } + + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchProductName")] + [AllowHtml] + public string SearchProductName { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchCategory")] + public int SearchCategoryId { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchManufacturer")] + public int SearchManufacturerId { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchStore")] + public int SearchStoreId { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchVendor")] + public int SearchVendorId { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchProductType")] + public int SearchProductTypeId { get; set; } + + public IList AvailableCategories { get; set; } + public IList AvailableManufacturers { get; set; } + public IList AvailableStores { get; set; } + public IList AvailableVendors { get; set; } + public IList AvailableProductTypes { get; set; } + + //vendor + public bool IsLoggedInAsVendor { get; set; } + + + public int AssociatedToProductId { get; set; } + } + #endregion + } + public partial class ProductAttributeValueLocalizedModel : ILocalizedModelLocal + { + public int LanguageId { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.Attributes.Values.Fields.Name")] + [AllowHtml] + public string Name { get; set; } + } + public partial class ProductAttributeCombinationModel : BaseNopEntityModel + { + public int ProductId { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.AttributeCombinations.Fields.Attributes")] + [AllowHtml] + public string AttributesXml { get; set; } + + [AllowHtml] + public string Warnings { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.AttributeCombinations.Fields.StockQuantity")] + public int StockQuantity { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.AttributeCombinations.Fields.AllowOutOfStockOrders")] + public bool AllowOutOfStockOrders { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.AttributeCombinations.Fields.Sku")] + public string Sku { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.AttributeCombinations.Fields.ManufacturerPartNumber")] + public string ManufacturerPartNumber { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.AttributeCombinations.Fields.Gtin")] + public string Gtin { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.AttributeCombinations.Fields.OverriddenPrice")] + [UIHint("DecimalNullable")] + public decimal? OverriddenPrice { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.ProductAttributes.AttributeCombinations.Fields.NotifyAdminForQuantityBelow")] + public int NotifyAdminForQuantityBelow { get; set; } + + } + + #region Stock quantity history + + public partial class StockQuantityHistoryModel : BaseNopEntityModel + { + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchWarehouse")] + public int SearchWarehouseId { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.StockQuantityHistory.Fields.Warehouse")] + [AllowHtml] + public string WarehouseName { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.StockQuantityHistory.Fields.Combination")] + [AllowHtml] + public string AttributeCombination { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.StockQuantityHistory.Fields.QuantityAdjustment")] + public int QuantityAdjustment { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.StockQuantityHistory.Fields.StockQuantity")] + public int StockQuantity { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.StockQuantityHistory.Fields.Message")] + [AllowHtml] + public string Message { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.StockQuantityHistory.Fields.CreatedOn")] + [UIHint("DecimalNullable")] + public DateTime CreatedOn { get; set; } + } + + #endregion + + #endregion + } + + public partial class ProductLocalizedModel : ILocalizedModelLocal + { + public int LanguageId { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.Name")] + [AllowHtml] + public string Name { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.ShortDescription")] + [AllowHtml] + public string ShortDescription { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.FullDescription")] + [AllowHtml] + public string FullDescription { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.MetaKeywords")] + [AllowHtml] + public string MetaKeywords { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.MetaDescription")] + [AllowHtml] + public string MetaDescription { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.MetaTitle")] + [AllowHtml] + public string MetaTitle { get; set; } + + [NopResourceDisplayName("Admin.Catalog.Products.Fields.SeName")] + [AllowHtml] + public string SeName { get; set; } + } } \ No newline at end of file diff --git a/src/Presentation/Nop.Web/Administration/Models/Common/AddressModel.cs b/src/Presentation/Nop.Web/Administration/Models/Common/AddressModel.cs index 01e0af73f3c..d9eac9e4626 100644 --- a/src/Presentation/Nop.Web/Administration/Models/Common/AddressModel.cs +++ b/src/Presentation/Nop.Web/Administration/Models/Common/AddressModel.cs @@ -1,146 +1,147 @@ -using System.Collections.Generic; -using System.Web.Mvc; -using FluentValidation.Attributes; -using Nop.Admin.Validators.Common; -using Nop.Core.Domain.Catalog; -using Nop.Web.Framework; -using Nop.Web.Framework.Mvc; - -namespace Nop.Admin.Models.Common -{ - [Validator(typeof(AddressValidator))] - public partial class AddressModel : BaseNopEntityModel - { - public AddressModel() - { - AvailableCountries = new List(); - AvailableStates = new List(); - CustomAddressAttributes = new List(); - } - - [NopResourceDisplayName("Admin.Address.Fields.FirstName")] - [AllowHtml] - public string FirstName { get; set; } - - [NopResourceDisplayName("Admin.Address.Fields.LastName")] - [AllowHtml] - public string LastName { get; set; } - - [NopResourceDisplayName("Admin.Address.Fields.Email")] - [AllowHtml] - public string Email { get; set; } - - [NopResourceDisplayName("Admin.Address.Fields.Company")] - [AllowHtml] - public string Company { get; set; } - - [NopResourceDisplayName("Admin.Address.Fields.Country")] - public int? CountryId { get; set; } - - [NopResourceDisplayName("Admin.Address.Fields.Country")] - [AllowHtml] - public string CountryName { get; set; } - - [NopResourceDisplayName("Admin.Address.Fields.StateProvince")] - public int? StateProvinceId { get; set; } - - [NopResourceDisplayName("Admin.Address.Fields.StateProvince")] - [AllowHtml] - public string StateProvinceName { get; set; } - - [NopResourceDisplayName("Admin.Address.Fields.City")] - [AllowHtml] - public string City { get; set; } - - [NopResourceDisplayName("Admin.Address.Fields.Address1")] - [AllowHtml] - public string Address1 { get; set; } - - [NopResourceDisplayName("Admin.Address.Fields.Address2")] - [AllowHtml] - public string Address2 { get; set; } - - [NopResourceDisplayName("Admin.Address.Fields.ZipPostalCode")] - [AllowHtml] - public string ZipPostalCode { get; set; } - - [NopResourceDisplayName("Admin.Address.Fields.PhoneNumber")] - [AllowHtml] - public string PhoneNumber { get; set; } - - [NopResourceDisplayName("Admin.Address.Fields.FaxNumber")] - [AllowHtml] - public string FaxNumber { get; set; } - - //address in HTML format (usually used in grids) - [NopResourceDisplayName("Admin.Address")] - public string AddressHtml { get; set; } - - //formatted custom address attributes - public string FormattedCustomAddressAttributes { get; set; } - public IList CustomAddressAttributes { get; set; } - - - public IList AvailableCountries { get; set; } - public IList AvailableStates { get; set; } - - - - public bool FirstNameEnabled { get; set; } - public bool FirstNameRequired { get; set; } - public bool LastNameEnabled { get; set; } - public bool LastNameRequired { get; set; } - public bool EmailEnabled { get; set; } - public bool EmailRequired { get; set; } - public bool CompanyEnabled { get; set; } - public bool CompanyRequired { get; set; } - public bool CountryEnabled { get; set; } - public bool CountryRequired { get; set; } - public bool StateProvinceEnabled { get; set; } - public bool CityEnabled { get; set; } - public bool CityRequired { get; set; } - public bool StreetAddressEnabled { get; set; } - public bool StreetAddressRequired { get; set; } - public bool StreetAddress2Enabled { get; set; } - public bool StreetAddress2Required { get; set; } - public bool ZipPostalCodeEnabled { get; set; } - public bool ZipPostalCodeRequired { get; set; } - public bool PhoneEnabled { get; set; } - public bool PhoneRequired { get; set; } - public bool FaxEnabled { get; set; } - public bool FaxRequired { get; set; } - - - #region Nested classes - - public partial class AddressAttributeModel : BaseNopEntityModel - { - public AddressAttributeModel() - { - Values = new List(); - } - - public string Name { get; set; } - - public bool IsRequired { get; set; } - - /// - /// Selected value for textboxes - /// - public string DefaultValue { get; set; } - - public AttributeControlType AttributeControlType { get; set; } - - public IList Values { get; set; } - } - - public partial class AddressAttributeValueModel : BaseNopEntityModel - { - public string Name { get; set; } - - public bool IsPreSelected { get; set; } - } - - #endregion - } +using System.Collections.Generic; +using System.Web.Mvc; +using FluentValidation.Attributes; +using Nop.Admin.Validators.Common; +using Nop.Core.Domain.Catalog; +using Nop.Web.Framework; +using Nop.Web.Framework.Mvc; + +namespace Nop.Admin.Models.Common +{ + [Validator(typeof(AddressValidator))] + public partial class AddressModel : BaseNopEntityModel + { + public AddressModel() + { + AvailableCountries = new List(); + AvailableStates = new List(); + CustomAddressAttributes = new List(); + } + + [NopResourceDisplayName("Admin.Address.Fields.FirstName")] + [AllowHtml] + public string FirstName { get; set; } + + [NopResourceDisplayName("Admin.Address.Fields.LastName")] + [AllowHtml] + public string LastName { get; set; } + + [NopResourceDisplayName("Admin.Address.Fields.Email")] + [AllowHtml] + public string Email { get; set; } + + [NopResourceDisplayName("Admin.Address.Fields.Company")] + [AllowHtml] + public string Company { get; set; } + + [NopResourceDisplayName("Admin.Address.Fields.Country")] + public int? CountryId { get; set; } + + [NopResourceDisplayName("Admin.Address.Fields.Country")] + [AllowHtml] + public string CountryName { get; set; } + + [NopResourceDisplayName("Admin.Address.Fields.StateProvince")] + public int? StateProvinceId { get; set; } + + [NopResourceDisplayName("Admin.Address.Fields.StateProvince")] + [AllowHtml] + public string StateProvinceName { get; set; } + + [NopResourceDisplayName("Admin.Address.Fields.City")] + [AllowHtml] + public string City { get; set; } + + [NopResourceDisplayName("Admin.Address.Fields.Address1")] + [AllowHtml] + public string Address1 { get; set; } + + [NopResourceDisplayName("Admin.Address.Fields.Address2")] + [AllowHtml] + public string Address2 { get; set; } + + [NopResourceDisplayName("Admin.Address.Fields.ZipPostalCode")] + [AllowHtml] + public string ZipPostalCode { get; set; } + + [NopResourceDisplayName("Admin.Address.Fields.PhoneNumber")] + [AllowHtml] + public string PhoneNumber { get; set; } + + [NopResourceDisplayName("Admin.Address.Fields.FaxNumber")] + [AllowHtml] + public string FaxNumber { get; set; } + + //address in HTML format (usually used in grids) + [NopResourceDisplayName("Admin.Address")] + public string AddressHtml { get; set; } + + //formatted custom address attributes + public string FormattedCustomAddressAttributes { get; set; } + public IList CustomAddressAttributes { get; set; } + + + public IList AvailableCountries { get; set; } + public IList AvailableStates { get; set; } + + + + public bool FirstNameEnabled { get; set; } + public bool FirstNameRequired { get; set; } + public bool LastNameEnabled { get; set; } + public bool LastNameRequired { get; set; } + public bool EmailEnabled { get; set; } + public bool EmailRequired { get; set; } + public bool CompanyEnabled { get; set; } + public bool CompanyRequired { get; set; } + public bool CountryEnabled { get; set; } + public bool CountryRequired { get; set; } + public bool StateProvinceEnabled { get; set; } + public bool StateProvinceRequired { get; set; } + public bool CityEnabled { get; set; } + public bool CityRequired { get; set; } + public bool StreetAddressEnabled { get; set; } + public bool StreetAddressRequired { get; set; } + public bool StreetAddress2Enabled { get; set; } + public bool StreetAddress2Required { get; set; } + public bool ZipPostalCodeEnabled { get; set; } + public bool ZipPostalCodeRequired { get; set; } + public bool PhoneEnabled { get; set; } + public bool PhoneRequired { get; set; } + public bool FaxEnabled { get; set; } + public bool FaxRequired { get; set; } + + + #region Nested classes + + public partial class AddressAttributeModel : BaseNopEntityModel + { + public AddressAttributeModel() + { + Values = new List(); + } + + public string Name { get; set; } + + public bool IsRequired { get; set; } + + /// + /// Selected value for textboxes + /// + public string DefaultValue { get; set; } + + public AttributeControlType AttributeControlType { get; set; } + + public IList Values { get; set; } + } + + public partial class AddressAttributeValueModel : BaseNopEntityModel + { + public string Name { get; set; } + + public bool IsPreSelected { get; set; } + } + + #endregion + } } \ No newline at end of file diff --git a/src/Presentation/Nop.Web/Administration/Models/Customers/CustomerModel.cs b/src/Presentation/Nop.Web/Administration/Models/Customers/CustomerModel.cs index b3da5f13c64..fdb42c79b74 100644 --- a/src/Presentation/Nop.Web/Administration/Models/Customers/CustomerModel.cs +++ b/src/Presentation/Nop.Web/Administration/Models/Customers/CustomerModel.cs @@ -1,375 +1,382 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Web.Mvc; -using FluentValidation.Attributes; -using Nop.Admin.Validators.Customers; -using Nop.Core.Domain.Catalog; -using Nop.Web.Framework; -using Nop.Web.Framework.Mvc; - -namespace Nop.Admin.Models.Customers -{ - [Validator(typeof(CustomerValidator))] - public partial class CustomerModel : BaseNopEntityModel - { - public CustomerModel() - { - this.AvailableTimeZones = new List(); - this.SendEmail = new SendEmailModel() { SendImmediately = true }; - this.SendPm = new SendPmModel(); - - this.SelectedCustomerRoleIds= new List(); - this.AvailableCustomerRoles = new List(); - - this.AssociatedExternalAuthRecords = new List(); - this.AvailableCountries = new List(); - this.AvailableStates = new List(); - this.AvailableVendors = new List(); - this.CustomerAttributes = new List(); - this.AvailableNewsletterSubscriptionStores = new List(); - this.RewardPointsAvailableStores = new List(); - } - - public bool UsernamesEnabled { get; set; } - - [NopResourceDisplayName("Admin.Customers.Customers.Fields.Username")] - [AllowHtml] - public string Username { get; set; } - - [NopResourceDisplayName("Admin.Customers.Customers.Fields.Email")] - [AllowHtml] - public string Email { get; set; } - - [NopResourceDisplayName("Admin.Customers.Customers.Fields.Password")] - [AllowHtml] - [DataType(DataType.Password)] - [NoTrim] - public string Password { get; set; } - - [NopResourceDisplayName("Admin.Customers.Customers.Fields.Vendor")] - public int VendorId { get; set; } - public IList AvailableVendors { get; set; } - - //form fields & properties - public bool GenderEnabled { get; set; } - [NopResourceDisplayName("Admin.Customers.Customers.Fields.Gender")] - public string Gender { get; set; } - - [NopResourceDisplayName("Admin.Customers.Customers.Fields.FirstName")] - [AllowHtml] - public string FirstName { get; set; } - [NopResourceDisplayName("Admin.Customers.Customers.Fields.LastName")] - [AllowHtml] - public string LastName { get; set; } - [NopResourceDisplayName("Admin.Customers.Customers.Fields.FullName")] - public string FullName { get; set; } - - public bool DateOfBirthEnabled { get; set; } - [UIHint("DateNullable")] - [NopResourceDisplayName("Admin.Customers.Customers.Fields.DateOfBirth")] - public DateTime? DateOfBirth { get; set; } - - public bool CompanyEnabled { get; set; } - [NopResourceDisplayName("Admin.Customers.Customers.Fields.Company")] - [AllowHtml] - public string Company { get; set; } - - public bool StreetAddressEnabled { get; set; } - [NopResourceDisplayName("Admin.Customers.Customers.Fields.StreetAddress")] - [AllowHtml] - public string StreetAddress { get; set; } - - public bool StreetAddress2Enabled { get; set; } - [NopResourceDisplayName("Admin.Customers.Customers.Fields.StreetAddress2")] - [AllowHtml] - public string StreetAddress2 { get; set; } - - public bool ZipPostalCodeEnabled { get; set; } - [NopResourceDisplayName("Admin.Customers.Customers.Fields.ZipPostalCode")] - [AllowHtml] - public string ZipPostalCode { get; set; } - - public bool CityEnabled { get; set; } - [NopResourceDisplayName("Admin.Customers.Customers.Fields.City")] - [AllowHtml] - public string City { get; set; } - - public bool CountryEnabled { get; set; } - [NopResourceDisplayName("Admin.Customers.Customers.Fields.Country")] - public int CountryId { get; set; } - public IList AvailableCountries { get; set; } - - public bool StateProvinceEnabled { get; set; } - [NopResourceDisplayName("Admin.Customers.Customers.Fields.StateProvince")] - public int StateProvinceId { get; set; } - public IList AvailableStates { get; set; } - - public bool PhoneEnabled { get; set; } - [NopResourceDisplayName("Admin.Customers.Customers.Fields.Phone")] - [AllowHtml] - public string Phone { get; set; } - - public bool FaxEnabled { get; set; } - [NopResourceDisplayName("Admin.Customers.Customers.Fields.Fax")] - [AllowHtml] - public string Fax { get; set; } - - public List CustomerAttributes { get; set; } - - [NopResourceDisplayName("Admin.Customers.Customers.Fields.RegisteredInStore")] - public string RegisteredInStore { get; set; } - - - - [NopResourceDisplayName("Admin.Customers.Customers.Fields.AdminComment")] - [AllowHtml] - public string AdminComment { get; set; } - - [NopResourceDisplayName("Admin.Customers.Customers.Fields.IsTaxExempt")] - public bool IsTaxExempt { get; set; } - - [NopResourceDisplayName("Admin.Customers.Customers.Fields.Active")] - public bool Active { get; set; } - - [NopResourceDisplayName("Admin.Customers.Customers.Fields.Affiliate")] - public int AffiliateId { get; set; } - [NopResourceDisplayName("Admin.Customers.Customers.Fields.Affiliate")] - public string AffiliateName { get; set; } - - - - - //time zone - [NopResourceDisplayName("Admin.Customers.Customers.Fields.TimeZoneId")] - [AllowHtml] - public string TimeZoneId { get; set; } - - public bool AllowCustomersToSetTimeZone { get; set; } - - public IList AvailableTimeZones { get; set; } - - - - - - //EU VAT - [NopResourceDisplayName("Admin.Customers.Customers.Fields.VatNumber")] - [AllowHtml] - public string VatNumber { get; set; } - - public string VatNumberStatusNote { get; set; } - - public bool DisplayVatNumber { get; set; } - - - - - - //registration date - [NopResourceDisplayName("Admin.Customers.Customers.Fields.CreatedOn")] - public DateTime CreatedOn { get; set; } - [NopResourceDisplayName("Admin.Customers.Customers.Fields.LastActivityDate")] - public DateTime LastActivityDate { get; set; } - - //IP adderss - [NopResourceDisplayName("Admin.Customers.Customers.Fields.IPAddress")] - public string LastIpAddress { get; set; } - - - [NopResourceDisplayName("Admin.Customers.Customers.Fields.LastVisitedPage")] - public string LastVisitedPage { get; set; } - - - //customer roles - [NopResourceDisplayName("Admin.Customers.Customers.Fields.CustomerRoles")] - public string CustomerRoleNames { get; set; } - public List AvailableCustomerRoles { get; set; } - [NopResourceDisplayName("Admin.Customers.Customers.Fields.CustomerRoles")] - [UIHint("MultiSelect")] - public IList SelectedCustomerRoleIds { get; set; } - - - //newsletter subscriptions (per store) - [NopResourceDisplayName("Admin.Customers.Customers.Fields.Newsletter")] - public List AvailableNewsletterSubscriptionStores { get; set; } - [NopResourceDisplayName("Admin.Customers.Customers.Fields.Newsletter")] - public int[] SelectedNewsletterSubscriptionStoreIds { get; set; } - - - - //reward points history - public bool DisplayRewardPointsHistory { get; set; } - [NopResourceDisplayName("Admin.Customers.Customers.RewardPoints.Fields.AddRewardPointsValue")] - public int AddRewardPointsValue { get; set; } - [NopResourceDisplayName("Admin.Customers.Customers.RewardPoints.Fields.AddRewardPointsMessage")] - [AllowHtml] - public string AddRewardPointsMessage { get; set; } - [NopResourceDisplayName("Admin.Customers.Customers.RewardPoints.Fields.AddRewardPointsStore")] - public int AddRewardPointsStoreId { get; set; } - [NopResourceDisplayName("Admin.Customers.Customers.RewardPoints.Fields.AddRewardPointsStore")] - public IList RewardPointsAvailableStores { get; set; } - - - - //send email model - public SendEmailModel SendEmail { get; set; } - //send PM model - public SendPmModel SendPm { get; set; } - //send the welcome message - public bool AllowSendingOfWelcomeMessage { get; set; } - //re-send the activation message - public bool AllowReSendingOfActivationMessage { get; set; } - - [NopResourceDisplayName("Admin.Customers.Customers.AssociatedExternalAuth")] - public IList AssociatedExternalAuthRecords { get; set; } - - - #region Nested classes - - public partial class StoreModel : BaseNopEntityModel - { - public string Name { get; set; } - } - - public partial class AssociatedExternalAuthModel : BaseNopEntityModel - { - [NopResourceDisplayName("Admin.Customers.Customers.AssociatedExternalAuth.Fields.Email")] - public string Email { get; set; } - - [NopResourceDisplayName("Admin.Customers.Customers.AssociatedExternalAuth.Fields.ExternalIdentifier")] - public string ExternalIdentifier { get; set; } - - [NopResourceDisplayName("Admin.Customers.Customers.AssociatedExternalAuth.Fields.AuthMethodName")] - public string AuthMethodName { get; set; } - } - - public partial class RewardPointsHistoryModel : BaseNopEntityModel - { - [NopResourceDisplayName("Admin.Customers.Customers.RewardPoints.Fields.Store")] - public string StoreName { get; set; } - - [NopResourceDisplayName("Admin.Customers.Customers.RewardPoints.Fields.Points")] - public int Points { get; set; } - - [NopResourceDisplayName("Admin.Customers.Customers.RewardPoints.Fields.PointsBalance")] - public string PointsBalance { get; set; } - - [NopResourceDisplayName("Admin.Customers.Customers.RewardPoints.Fields.Message")] - [AllowHtml] - public string Message { get; set; } - - [NopResourceDisplayName("Admin.Customers.Customers.RewardPoints.Fields.Date")] - public DateTime CreatedOn { get; set; } - } - - public partial class SendEmailModel : BaseNopModel - { - [NopResourceDisplayName("Admin.Customers.Customers.SendEmail.Subject")] - [AllowHtml] - public string Subject { get; set; } - - [NopResourceDisplayName("Admin.Customers.Customers.SendEmail.Body")] - [AllowHtml] - public string Body { get; set; } - - [NopResourceDisplayName("Admin.Customers.Customers.SendEmail.SendImmediately")] - public bool SendImmediately { get; set; } - - [NopResourceDisplayName("Admin.Customers.Customers.SendEmail.DontSendBeforeDate")] - [UIHint("DateTimeNullable")] - public DateTime? DontSendBeforeDate { get; set; } - } - - public partial class SendPmModel : BaseNopModel - { - [NopResourceDisplayName("Admin.Customers.Customers.SendPM.Subject")] - public string Subject { get; set; } - - [NopResourceDisplayName("Admin.Customers.Customers.SendPM.Message")] - public string Message { get; set; } - } - - public partial class OrderModel : BaseNopEntityModel - { - public override int Id { get; set; } - [NopResourceDisplayName("Admin.Customers.Customers.Orders.CustomOrderNumber")] - public string CustomOrderNumber { get; set; } - - [NopResourceDisplayName("Admin.Customers.Customers.Orders.OrderStatus")] - public string OrderStatus { get; set; } - [NopResourceDisplayName("Admin.Customers.Customers.Orders.OrderStatus")] - public int OrderStatusId { get; set; } - - [NopResourceDisplayName("Admin.Customers.Customers.Orders.PaymentStatus")] - public string PaymentStatus { get; set; } - - [NopResourceDisplayName("Admin.Customers.Customers.Orders.ShippingStatus")] - public string ShippingStatus { get; set; } - - [NopResourceDisplayName("Admin.Customers.Customers.Orders.OrderTotal")] - public string OrderTotal { get; set; } - - [NopResourceDisplayName("Admin.Customers.Customers.Orders.Store")] - public string StoreName { get; set; } - - [NopResourceDisplayName("Admin.Customers.Customers.Orders.CreatedOn")] - public DateTime CreatedOn { get; set; } - } - - public partial class ActivityLogModel : BaseNopEntityModel - { - [NopResourceDisplayName("Admin.Customers.Customers.ActivityLog.ActivityLogType")] - public string ActivityLogTypeName { get; set; } - [NopResourceDisplayName("Admin.Customers.Customers.ActivityLog.Comment")] - public string Comment { get; set; } - [NopResourceDisplayName("Admin.Customers.Customers.ActivityLog.CreatedOn")] - public DateTime CreatedOn { get; set; } - [NopResourceDisplayName("Admin.Customers.Customers.ActivityLog.IpAddress")] - public string IpAddress { get; set; } - } - - public partial class BackInStockSubscriptionModel : BaseNopEntityModel - { - [NopResourceDisplayName("Admin.Customers.Customers.BackInStockSubscriptions.Store")] - public string StoreName { get; set; } - [NopResourceDisplayName("Admin.Customers.Customers.BackInStockSubscriptions.Product")] - public int ProductId { get; set; } - [NopResourceDisplayName("Admin.Customers.Customers.BackInStockSubscriptions.Product")] - public string ProductName { get; set; } - [NopResourceDisplayName("Admin.Customers.Customers.BackInStockSubscriptions.CreatedOn")] - public DateTime CreatedOn { get; set; } - } - - public partial class CustomerAttributeModel : BaseNopEntityModel - { - public CustomerAttributeModel() - { - Values = new List(); - } - - public string Name { get; set; } - - public bool IsRequired { get; set; } - - /// - /// Default value for textboxes - /// - public string DefaultValue { get; set; } - - public AttributeControlType AttributeControlType { get; set; } - - public IList Values { get; set; } - - } - - public partial class CustomerAttributeValueModel : BaseNopEntityModel - { - public string Name { get; set; } - - public bool IsPreSelected { get; set; } - } - - #endregion - } +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Web.Mvc; +using FluentValidation.Attributes; +using Nop.Admin.Validators.Customers; +using Nop.Core.Domain.Catalog; +using Nop.Web.Framework; +using Nop.Web.Framework.Mvc; + +namespace Nop.Admin.Models.Customers +{ + [Validator(typeof(CustomerValidator))] + public partial class CustomerModel : BaseNopEntityModel + { + public CustomerModel() + { + this.AvailableTimeZones = new List(); + this.SendEmail = new SendEmailModel() { SendImmediately = true }; + this.SendPm = new SendPmModel(); + + this.SelectedCustomerRoleIds= new List(); + this.AvailableCustomerRoles = new List(); + + this.AssociatedExternalAuthRecords = new List(); + this.AvailableCountries = new List(); + this.AvailableStates = new List(); + this.AvailableVendors = new List(); + this.CustomerAttributes = new List(); + this.AvailableNewsletterSubscriptionStores = new List(); + this.RewardPointsAvailableStores = new List(); + } + + public bool UsernamesEnabled { get; set; } + + [NopResourceDisplayName("Admin.Customers.Customers.Fields.Username")] + [AllowHtml] + public string Username { get; set; } + + [NopResourceDisplayName("Admin.Customers.Customers.Fields.Email")] + [AllowHtml] + public string Email { get; set; } + + [NopResourceDisplayName("Admin.Customers.Customers.Fields.Password")] + [AllowHtml] + [DataType(DataType.Password)] + [NoTrim] + public string Password { get; set; } + + [NopResourceDisplayName("Admin.Customers.Customers.Fields.Vendor")] + public int VendorId { get; set; } + public IList AvailableVendors { get; set; } + + //form fields & properties + public bool GenderEnabled { get; set; } + [NopResourceDisplayName("Admin.Customers.Customers.Fields.Gender")] + public string Gender { get; set; } + + [NopResourceDisplayName("Admin.Customers.Customers.Fields.FirstName")] + [AllowHtml] + public string FirstName { get; set; } + [NopResourceDisplayName("Admin.Customers.Customers.Fields.LastName")] + [AllowHtml] + public string LastName { get; set; } + [NopResourceDisplayName("Admin.Customers.Customers.Fields.FullName")] + public string FullName { get; set; } + + public bool DateOfBirthEnabled { get; set; } + [UIHint("DateNullable")] + [NopResourceDisplayName("Admin.Customers.Customers.Fields.DateOfBirth")] + public DateTime? DateOfBirth { get; set; } + + public bool CompanyEnabled { get; set; } + [NopResourceDisplayName("Admin.Customers.Customers.Fields.Company")] + [AllowHtml] + public string Company { get; set; } + + public bool StreetAddressEnabled { get; set; } + [NopResourceDisplayName("Admin.Customers.Customers.Fields.StreetAddress")] + [AllowHtml] + public string StreetAddress { get; set; } + + public bool StreetAddress2Enabled { get; set; } + [NopResourceDisplayName("Admin.Customers.Customers.Fields.StreetAddress2")] + [AllowHtml] + public string StreetAddress2 { get; set; } + + public bool ZipPostalCodeEnabled { get; set; } + [NopResourceDisplayName("Admin.Customers.Customers.Fields.ZipPostalCode")] + [AllowHtml] + public string ZipPostalCode { get; set; } + + public bool CityEnabled { get; set; } + [NopResourceDisplayName("Admin.Customers.Customers.Fields.City")] + [AllowHtml] + public string City { get; set; } + + public bool CountryEnabled { get; set; } + [NopResourceDisplayName("Admin.Customers.Customers.Fields.Country")] + public int CountryId { get; set; } + public IList AvailableCountries { get; set; } + + public bool StateProvinceEnabled { get; set; } + [NopResourceDisplayName("Admin.Customers.Customers.Fields.StateProvince")] + public int StateProvinceId { get; set; } + public IList AvailableStates { get; set; } + + public bool PhoneEnabled { get; set; } + [NopResourceDisplayName("Admin.Customers.Customers.Fields.Phone")] + [AllowHtml] + public string Phone { get; set; } + + public bool FaxEnabled { get; set; } + [NopResourceDisplayName("Admin.Customers.Customers.Fields.Fax")] + [AllowHtml] + public string Fax { get; set; } + + public List CustomerAttributes { get; set; } + + [NopResourceDisplayName("Admin.Customers.Customers.Fields.RegisteredInStore")] + public string RegisteredInStore { get; set; } + + + + [NopResourceDisplayName("Admin.Customers.Customers.Fields.AdminComment")] + [AllowHtml] + public string AdminComment { get; set; } + + [NopResourceDisplayName("Admin.Customers.Customers.Fields.IsTaxExempt")] + public bool IsTaxExempt { get; set; } + + [NopResourceDisplayName("Admin.Customers.Customers.Fields.Active")] + public bool Active { get; set; } + + [NopResourceDisplayName("Admin.Customers.Customers.Fields.Affiliate")] + public int AffiliateId { get; set; } + [NopResourceDisplayName("Admin.Customers.Customers.Fields.Affiliate")] + public string AffiliateName { get; set; } + + + + + //time zone + [NopResourceDisplayName("Admin.Customers.Customers.Fields.TimeZoneId")] + [AllowHtml] + public string TimeZoneId { get; set; } + + public bool AllowCustomersToSetTimeZone { get; set; } + + public IList AvailableTimeZones { get; set; } + + + + + + //EU VAT + [NopResourceDisplayName("Admin.Customers.Customers.Fields.VatNumber")] + [AllowHtml] + public string VatNumber { get; set; } + + public string VatNumberStatusNote { get; set; } + + public bool DisplayVatNumber { get; set; } + + + + + + //registration date + [NopResourceDisplayName("Admin.Customers.Customers.Fields.CreatedOn")] + public DateTime CreatedOn { get; set; } + [NopResourceDisplayName("Admin.Customers.Customers.Fields.LastActivityDate")] + public DateTime LastActivityDate { get; set; } + + //IP adderss + [NopResourceDisplayName("Admin.Customers.Customers.Fields.IPAddress")] + public string LastIpAddress { get; set; } + + + [NopResourceDisplayName("Admin.Customers.Customers.Fields.LastVisitedPage")] + public string LastVisitedPage { get; set; } + + + //customer roles + [NopResourceDisplayName("Admin.Customers.Customers.Fields.CustomerRoles")] + public string CustomerRoleNames { get; set; } + public List AvailableCustomerRoles { get; set; } + [NopResourceDisplayName("Admin.Customers.Customers.Fields.CustomerRoles")] + [UIHint("MultiSelect")] + public IList SelectedCustomerRoleIds { get; set; } + + + //newsletter subscriptions (per store) + [NopResourceDisplayName("Admin.Customers.Customers.Fields.Newsletter")] + public List AvailableNewsletterSubscriptionStores { get; set; } + [NopResourceDisplayName("Admin.Customers.Customers.Fields.Newsletter")] + public int[] SelectedNewsletterSubscriptionStoreIds { get; set; } + + + + //reward points history + public bool DisplayRewardPointsHistory { get; set; } + [NopResourceDisplayName("Admin.Customers.Customers.RewardPoints.Fields.AddRewardPointsValue")] + public int AddRewardPointsValue { get; set; } + [NopResourceDisplayName("Admin.Customers.Customers.RewardPoints.Fields.AddRewardPointsValuePurchased")] + public int AddRewardPointsValuePurchased { get; set; } + [NopResourceDisplayName("Admin.Customers.Customers.RewardPoints.Fields.AddRewardPointsMessage")] + [AllowHtml] + public string AddRewardPointsMessage { get; set; } + [NopResourceDisplayName("Admin.Customers.Customers.RewardPoints.Fields.AddRewardPointsStore")] + public int AddRewardPointsStoreId { get; set; } + [NopResourceDisplayName("Admin.Customers.Customers.RewardPoints.Fields.AddRewardPointsStore")] + public IList RewardPointsAvailableStores { get; set; } + + + + + //send email model + public SendEmailModel SendEmail { get; set; } + //send PM model + public SendPmModel SendPm { get; set; } + //send the welcome message + public bool AllowSendingOfWelcomeMessage { get; set; } + //re-send the activation message + public bool AllowReSendingOfActivationMessage { get; set; } + + [NopResourceDisplayName("Admin.Customers.Customers.AssociatedExternalAuth")] + public IList AssociatedExternalAuthRecords { get; set; } + + + #region Nested classes + + public partial class StoreModel : BaseNopEntityModel + { + public string Name { get; set; } + } + + public partial class AssociatedExternalAuthModel : BaseNopEntityModel + { + [NopResourceDisplayName("Admin.Customers.Customers.AssociatedExternalAuth.Fields.Email")] + public string Email { get; set; } + + [NopResourceDisplayName("Admin.Customers.Customers.AssociatedExternalAuth.Fields.ExternalIdentifier")] + public string ExternalIdentifier { get; set; } + + [NopResourceDisplayName("Admin.Customers.Customers.AssociatedExternalAuth.Fields.AuthMethodName")] + public string AuthMethodName { get; set; } + } + + public partial class RewardPointsHistoryModel : BaseNopEntityModel + { + [NopResourceDisplayName("Admin.Customers.Customers.RewardPoints.Fields.Store")] + public string StoreName { get; set; } + + [NopResourceDisplayName("Admin.Customers.Customers.RewardPoints.Fields.Points")] + public int Points { get; set; } + + [NopResourceDisplayName("Admin.Customers.Customers.RewardPoints.Fields.PointsBalance")] + public string PointsBalance { get; set; } + [NopResourceDisplayName("Admin.Customers.Customers.RewardPoints.Fields.PointsPurchased")] + public int PointsPurchased { get; set; } + [NopResourceDisplayName("Admin.Customers.Customers.RewardPoints.Fields.PointsBalancePurchased")] + public string PointsBalancePurchased { get; set; } + + [NopResourceDisplayName("Admin.Customers.Customers.RewardPoints.Fields.Message")] + [AllowHtml] + public string Message { get; set; } + + [NopResourceDisplayName("Admin.Customers.Customers.RewardPoints.Fields.Date")] + public DateTime CreatedOn { get; set; } + } + + public partial class SendEmailModel : BaseNopModel + { + [NopResourceDisplayName("Admin.Customers.Customers.SendEmail.Subject")] + [AllowHtml] + public string Subject { get; set; } + + [NopResourceDisplayName("Admin.Customers.Customers.SendEmail.Body")] + [AllowHtml] + public string Body { get; set; } + + [NopResourceDisplayName("Admin.Customers.Customers.SendEmail.SendImmediately")] + public bool SendImmediately { get; set; } + + [NopResourceDisplayName("Admin.Customers.Customers.SendEmail.DontSendBeforeDate")] + [UIHint("DateTimeNullable")] + public DateTime? DontSendBeforeDate { get; set; } + } + + public partial class SendPmModel : BaseNopModel + { + [NopResourceDisplayName("Admin.Customers.Customers.SendPM.Subject")] + public string Subject { get; set; } + + [NopResourceDisplayName("Admin.Customers.Customers.SendPM.Message")] + public string Message { get; set; } + } + + public partial class OrderModel : BaseNopEntityModel + { + public override int Id { get; set; } + [NopResourceDisplayName("Admin.Customers.Customers.Orders.CustomOrderNumber")] + public string CustomOrderNumber { get; set; } + + [NopResourceDisplayName("Admin.Customers.Customers.Orders.OrderStatus")] + public string OrderStatus { get; set; } + [NopResourceDisplayName("Admin.Customers.Customers.Orders.OrderStatus")] + public int OrderStatusId { get; set; } + + [NopResourceDisplayName("Admin.Customers.Customers.Orders.PaymentStatus")] + public string PaymentStatus { get; set; } + + [NopResourceDisplayName("Admin.Customers.Customers.Orders.ShippingStatus")] + public string ShippingStatus { get; set; } + + [NopResourceDisplayName("Admin.Customers.Customers.Orders.OrderTotal")] + public string OrderTotal { get; set; } + + [NopResourceDisplayName("Admin.Customers.Customers.Orders.Store")] + public string StoreName { get; set; } + + [NopResourceDisplayName("Admin.Customers.Customers.Orders.CreatedOn")] + public DateTime CreatedOn { get; set; } + } + + public partial class ActivityLogModel : BaseNopEntityModel + { + [NopResourceDisplayName("Admin.Customers.Customers.ActivityLog.ActivityLogType")] + public string ActivityLogTypeName { get; set; } + [NopResourceDisplayName("Admin.Customers.Customers.ActivityLog.Comment")] + public string Comment { get; set; } + [NopResourceDisplayName("Admin.Customers.Customers.ActivityLog.CreatedOn")] + public DateTime CreatedOn { get; set; } + [NopResourceDisplayName("Admin.Customers.Customers.ActivityLog.IpAddress")] + public string IpAddress { get; set; } + } + + public partial class BackInStockSubscriptionModel : BaseNopEntityModel + { + [NopResourceDisplayName("Admin.Customers.Customers.BackInStockSubscriptions.Store")] + public string StoreName { get; set; } + [NopResourceDisplayName("Admin.Customers.Customers.BackInStockSubscriptions.Product")] + public int ProductId { get; set; } + [NopResourceDisplayName("Admin.Customers.Customers.BackInStockSubscriptions.Product")] + public string ProductName { get; set; } + [NopResourceDisplayName("Admin.Customers.Customers.BackInStockSubscriptions.CreatedOn")] + public DateTime CreatedOn { get; set; } + } + + public partial class CustomerAttributeModel : BaseNopEntityModel + { + public CustomerAttributeModel() + { + Values = new List(); + } + + public string Name { get; set; } + + public bool IsRequired { get; set; } + + /// + /// Default value for textboxes + /// + public string DefaultValue { get; set; } + + public AttributeControlType AttributeControlType { get; set; } + + public IList Values { get; set; } + + } + + public partial class CustomerAttributeValueModel : BaseNopEntityModel + { + public string Name { get; set; } + + public bool IsPreSelected { get; set; } + } + + #endregion + } } \ No newline at end of file diff --git a/src/Presentation/Nop.Web/Administration/Models/Orders/OrderModel.cs b/src/Presentation/Nop.Web/Administration/Models/Orders/OrderModel.cs index 9c6a493b48e..c31ed99abd7 100644 --- a/src/Presentation/Nop.Web/Administration/Models/Orders/OrderModel.cs +++ b/src/Presentation/Nop.Web/Administration/Models/Orders/OrderModel.cs @@ -1,530 +1,553 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Web.Mvc; -using Nop.Admin.Models.Common; -using Nop.Core.Domain.Catalog; -using Nop.Core.Domain.Tax; -using Nop.Web.Framework; -using Nop.Web.Framework.Mvc; - -namespace Nop.Admin.Models.Orders -{ - public partial class OrderModel : BaseNopEntityModel - { - public OrderModel() - { - CustomValues = new Dictionary(); - TaxRates = new List(); - GiftCards = new List(); - Items = new List(); - UsedDiscounts = new List(); - Warnings = new List(); - } - - public bool IsLoggedInAsVendor { get; set; } - - //identifiers - public override int Id { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.OrderGuid")] - public Guid OrderGuid { get; set; } - - [NopResourceDisplayName("Admin.Orders.Fields.InvoiceId")] - public string InvoiceId { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.InvoiceDateUtc")] - [UIHint("DateTimeNullable")] - public DateTime? InvoiceDateUtc { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.CustomOrderNumber")] - public string CustomOrderNumber { get; set; } - - //store - [NopResourceDisplayName("Admin.Orders.Fields.Store")] - public string StoreName { get; set; } - - //customer info - [NopResourceDisplayName("Admin.Orders.Fields.Customer")] - public int CustomerId { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.Customer")] - public string CustomerInfo { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.CustomerEmail")] - public string CustomerEmail { get; set; } - public string CustomerFullName { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.CustomerIP")] - public string CustomerIp { get; set; } - - [NopResourceDisplayName("Admin.Orders.Fields.CustomValues")] - public Dictionary CustomValues { get; set; } - - [NopResourceDisplayName("Admin.Orders.Fields.Affiliate")] - public int AffiliateId { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.Affiliate")] - public string AffiliateName { get; set; } - - //Used discounts - [NopResourceDisplayName("Admin.Orders.Fields.UsedDiscounts")] - public IList UsedDiscounts { get; set; } - - //totals - public bool AllowCustomersToSelectTaxDisplayType { get; set; } - public TaxDisplayType TaxDisplayType { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.OrderSubtotalInclTax")] - public string OrderSubtotalInclTax { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.OrderSubtotalExclTax")] - public string OrderSubtotalExclTax { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.OrderSubTotalDiscountInclTax")] - public string OrderSubTotalDiscountInclTax { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.OrderSubTotalDiscountExclTax")] - public string OrderSubTotalDiscountExclTax { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.OrderShippingInclTax")] - public string OrderShippingInclTax { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.OrderShippingExclTax")] - public string OrderShippingExclTax { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.PaymentMethodAdditionalFeeInclTax")] - public string PaymentMethodAdditionalFeeInclTax { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.PaymentMethodAdditionalFeeExclTax")] - public string PaymentMethodAdditionalFeeExclTax { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.Tax")] - public string Tax { get; set; } - public IList TaxRates { get; set; } - public bool DisplayTax { get; set; } - public bool DisplayTaxRates { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.OrderTotalDiscount")] - public string OrderTotalDiscount { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.RedeemedRewardPoints")] - public int RedeemedRewardPoints { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.RedeemedRewardPoints")] - public string RedeemedRewardPointsAmount { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.OrderTotal")] - public string OrderTotal { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.RefundedAmount")] - public string RefundedAmount { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.Profit")] - public string Profit { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.OrderAmount")] - public string OrderAmount { get; set; } //MF 09.12.16 - [NopResourceDisplayName("Admin.Orders.Fields.OrderAmountIncl")] - public string OrderAmountIncl { get; set; } //MF 09.12.16 - - //edit totals - [NopResourceDisplayName("Admin.Orders.Fields.Edit.OrderSubtotal")] - public decimal OrderSubtotalInclTaxValue { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.Edit.OrderSubtotal")] - public decimal OrderSubtotalExclTaxValue { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.Edit.OrderSubTotalDiscount")] - public decimal OrderSubTotalDiscountInclTaxValue { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.Edit.OrderSubTotalDiscount")] - public decimal OrderSubTotalDiscountExclTaxValue { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.Edit.OrderShipping")] - public decimal OrderShippingInclTaxValue { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.Edit.OrderShipping")] - public decimal OrderShippingExclTaxValue { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.Edit.PaymentMethodAdditionalFee")] - public decimal PaymentMethodAdditionalFeeInclTaxValue { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.Edit.PaymentMethodAdditionalFee")] - public decimal PaymentMethodAdditionalFeeExclTaxValue { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.Edit.Tax")] - public decimal TaxValue { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.Edit.TaxRates")] - public string TaxRatesValue { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.Edit.OrderTotalDiscount")] - public decimal OrderTotalDiscountValue { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.Edit.OrderTotal")] - public decimal OrderTotalValue { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.Edit.OrderAmount")] - public decimal OrderAmountValue { get; set; } //MF 09.12.16 - [NopResourceDisplayName("Admin.Orders.Fields.Edit.OrderAmountIncl")] - public decimal OrderAmountInclValue { get; set; } //MF 09.12.16 - - //associated recurring payment id - [NopResourceDisplayName("Admin.Orders.Fields.RecurringPayment")] - public int RecurringPaymentId { get; set; } - - //order status - [NopResourceDisplayName("Admin.Orders.Fields.OrderStatus")] - public string OrderStatus { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.OrderStatus")] - public int OrderStatusId { get; set; } - - //payment info - [NopResourceDisplayName("Admin.Orders.Fields.PaymentStatus")] - public string PaymentStatus { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.PaymentStatus")] - public int PaymentStatusId { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.PaymentMethod")] - public string PaymentMethod { get; set; } - - //credit card info - public bool AllowStoringCreditCardNumber { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.CardType")] - [AllowHtml] - public string CardType { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.CardName")] - [AllowHtml] - public string CardName { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.CardNumber")] - [AllowHtml] - public string CardNumber { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.CardCVV2")] - [AllowHtml] - public string CardCvv2 { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.CardExpirationMonth")] - [AllowHtml] - public string CardExpirationMonth { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.CardExpirationYear")] - [AllowHtml] - public string CardExpirationYear { get; set; } - - //misc payment info - [NopResourceDisplayName("Admin.Orders.Fields.AuthorizationTransactionID")] - public string AuthorizationTransactionId { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.CaptureTransactionID")] - public string CaptureTransactionId { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.SubscriptionTransactionID")] - public string SubscriptionTransactionId { get; set; } - - //shipping info - public bool IsShippable { get; set; } - public bool PickUpInStore { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.PickupAddress")] - public AddressModel PickupAddress { get; set; } - public string PickupAddressGoogleMapsUrl { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.ShippingStatus")] - public string ShippingStatus { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.ShippingStatus")] - public int ShippingStatusId { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.ShippingAddress")] - public AddressModel ShippingAddress { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.ShippingMethod")] - public string ShippingMethod { get; set; } - public string ShippingAddressGoogleMapsUrl { get; set; } - public bool CanAddNewShipments { get; set; } - - //billing info - [NopResourceDisplayName("Admin.Orders.Fields.BillingAddress")] - public AddressModel BillingAddress { get; set; } - [NopResourceDisplayName("Admin.Orders.Fields.VatNumber")] - public string VatNumber { get; set; } - - //gift cards - public IList GiftCards { get; set; } - - //items - public bool HasDownloadableProducts { get; set; } - public IList Items { get; set; } - - //creation date - [NopResourceDisplayName("Admin.Orders.Fields.CreatedOn")] - public DateTime CreatedOn { get; set; } - - //checkout attributes - public string CheckoutAttributeInfo { get; set; } - - - //order notes - [NopResourceDisplayName("Admin.Orders.OrderNotes.Fields.DisplayToCustomer")] - public bool AddOrderNoteDisplayToCustomer { get; set; } - [NopResourceDisplayName("Admin.Orders.OrderNotes.Fields.Note")] - [AllowHtml] - public string AddOrderNoteMessage { get; set; } - public bool AddOrderNoteHasDownload { get; set; } - [NopResourceDisplayName("Admin.Orders.OrderNotes.Fields.Download")] - [UIHint("Download")] - public int AddOrderNoteDownloadId { get; set; } - - //refund info - [NopResourceDisplayName("Admin.Orders.Fields.PartialRefund.AmountToRefund")] - public decimal AmountToRefund { get; set; } - public decimal MaxAmountToRefund { get; set; } - public string PrimaryStoreCurrencyCode { get; set; } - - //workflow info - public bool CanCancelOrder { get; set; } - public bool CanCapture { get; set; } - public bool CanMarkOrderAsPaid { get; set; } - public bool CanRefund { get; set; } - public bool CanRefundOffline { get; set; } - public bool CanPartiallyRefund { get; set; } - public bool CanPartiallyRefundOffline { get; set; } - public bool CanVoid { get; set; } - public bool CanVoidOffline { get; set; } - - //warnings - public List Warnings { get; set; } - - #region Nested Classes - - public partial class OrderItemModel : BaseNopEntityModel - { - public OrderItemModel() - { - PurchasedGiftCardIds = new List(); - ReturnRequests = new List(); - } - public int ProductId { get; set; } - public string ProductName { get; set; } - public string VendorName { get; set; } - public string Sku { get; set; } - - public string PictureThumbnailUrl { get; set; } - - public string UnitPriceInclTax { get; set; } - public string UnitPriceExclTax { get; set; } - public decimal UnitPriceInclTaxValue { get; set; } - public decimal UnitPriceExclTaxValue { get; set; } - - public int Quantity { get; set; } - - public string DiscountInclTax { get; set; } - public string DiscountExclTax { get; set; } - public decimal DiscountInclTaxValue { get; set; } - public decimal DiscountExclTaxValue { get; set; } - - public string SubTotalInclTax { get; set; } - public string SubTotalExclTax { get; set; } - public decimal SubTotalInclTaxValue { get; set; } - public decimal SubTotalExclTaxValue { get; set; } - - public string AttributeInfo { get; set; } - public string RecurringInfo { get; set; } - public string RentalInfo { get; set; } - public IList ReturnRequests { get; set; } - public IList PurchasedGiftCardIds { get; set; } - - public bool IsDownload { get; set; } - public int DownloadCount { get; set; } - public DownloadActivationType DownloadActivationType { get; set; } - public bool IsDownloadActivated { get; set; } - public Guid LicenseDownloadGuid { get; set; } - public decimal VatRate { get; set; } - - #region Nested Classes - - public partial class ReturnRequestBriefModel : BaseNopEntityModel - { - public string CustomNumber { get; set; } - } - - #endregion - } - - public partial class TaxRate : BaseNopModel - { - [NopResourceDisplayName("Order.TaxRateLine.VatRate")] - public string Rate { get; set; } - [NopResourceDisplayName("Order.TaxRateLine.Amount")] - public string Amount { get; set; } // includes subtotal, shipping and payment fee - [NopResourceDisplayName("Order.TaxRateLine.DiscountAmount")] - public string DiscountAmount { get; set; } - [NopResourceDisplayName("Order.TaxRateLine.BaseAmount")] - public string BaseAmount { get; set; } - [NopResourceDisplayName("Order.TaxRateLine.VatAmount")] - public string VatAmount { get; set; } - } - - public partial class GiftCard : BaseNopModel - { - [NopResourceDisplayName("Admin.Orders.Fields.GiftCardInfo")] - public string CouponCode { get; set; } - public string Amount { get; set; } - } - - public partial class OrderNote : BaseNopEntityModel - { - public int OrderId { get; set; } - [NopResourceDisplayName("Admin.Orders.OrderNotes.Fields.DisplayToCustomer")] - public bool DisplayToCustomer { get; set; } - [NopResourceDisplayName("Admin.Orders.OrderNotes.Fields.Note")] - public string Note { get; set; } - [NopResourceDisplayName("Admin.Orders.OrderNotes.Fields.Download")] - public int DownloadId { get; set; } - [NopResourceDisplayName("Admin.Orders.OrderNotes.Fields.Download")] - public Guid DownloadGuid { get; set; } - [NopResourceDisplayName("Admin.Orders.OrderNotes.Fields.CreatedOn")] - public DateTime CreatedOn { get; set; } - } - - public partial class UploadLicenseModel : BaseNopModel - { - public int OrderId { get; set; } - - public int OrderItemId { get; set; } - - [UIHint("Download")] - public int LicenseDownloadId { get; set; } - - } - - public partial class AddOrderProductModel : BaseNopModel - { - public AddOrderProductModel() - { - AvailableCategories = new List(); - AvailableManufacturers = new List(); - AvailableProductTypes = new List(); - } - - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchProductName")] - [AllowHtml] - public string SearchProductName { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchCategory")] - public int SearchCategoryId { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchManufacturer")] - public int SearchManufacturerId { get; set; } - [NopResourceDisplayName("Admin.Catalog.Products.List.SearchProductType")] - public int SearchProductTypeId { get; set; } - - public IList AvailableCategories { get; set; } - public IList AvailableManufacturers { get; set; } - public IList AvailableProductTypes { get; set; } - - public int OrderId { get; set; } - - #region Nested classes - - public partial class ProductModel : BaseNopEntityModel - { - [NopResourceDisplayName("Admin.Orders.Products.AddNew.Name")] - [AllowHtml] - public string Name { get; set; } - - [NopResourceDisplayName("Admin.Orders.Products.AddNew.SKU")] - [AllowHtml] - public string Sku { get; set; } - } - - public partial class ProductDetailsModel : BaseNopModel - { - public ProductDetailsModel() - { - ProductAttributes = new List(); - GiftCard = new GiftCardModel(); - Warnings = new List(); - } - - public int ProductId { get; set; } - - public int OrderId { get; set; } - - public ProductType ProductType { get; set; } - - public string Name { get; set; } - - [NopResourceDisplayName("Admin.Orders.Products.AddNew.UnitPriceInclTax")] - public decimal UnitPriceInclTax { get; set; } - [NopResourceDisplayName("Admin.Orders.Products.AddNew.UnitPriceExclTax")] - public decimal UnitPriceExclTax { get; set; } - - [NopResourceDisplayName("Admin.Orders.Products.AddNew.Quantity")] - public int Quantity { get; set; } - - [NopResourceDisplayName("Admin.Orders.Products.AddNew.SubTotalInclTax")] - public decimal SubTotalInclTax { get; set; } - [NopResourceDisplayName("Admin.Orders.Products.AddNew.SubTotalExclTax")] - public decimal SubTotalExclTax { get; set; } - [NopResourceDisplayName("ShoppingCart.VatRate")] - public decimal VatRate { get; set; } - - //product attributes - public IList ProductAttributes { get; set; } - //gift card info - public GiftCardModel GiftCard { get; set; } - //rental - public bool IsRental { get; set; } - - public List Warnings { get; set; } - - /// - /// A value indicating whether this attribute depends on some other attribute - /// - public bool HasCondition { get; set; } - - public bool AutoUpdateOrderTotals { get; set; } - } - - public partial class ProductAttributeModel : BaseNopEntityModel - { - public ProductAttributeModel() - { - Values = new List(); - } - - public int ProductAttributeId { get; set; } - - public string Name { get; set; } - - public string TextPrompt { get; set; } - - public bool IsRequired { get; set; } - - public bool HasCondition { get; set; } - - /// - /// Allowed file extensions for customer uploaded files - /// - public IList AllowedFileExtensions { get; set; } - - public AttributeControlType AttributeControlType { get; set; } - - public IList Values { get; set; } - } - - public partial class ProductAttributeValueModel : BaseNopEntityModel - { - public string Name { get; set; } - - public bool IsPreSelected { get; set; } - - public string PriceAdjustment { get; set; } - - public decimal PriceAdjustmentValue { get; set; } - - public bool CustomerEntersQty { get; set; } - - public int Quantity { get; set; } - } - - - public partial class GiftCardModel : BaseNopModel - { - public bool IsGiftCard { get; set; } - - [NopResourceDisplayName("Admin.GiftCards.Fields.RecipientName")] - [AllowHtml] - public string RecipientName { get; set; } - [NopResourceDisplayName("Admin.GiftCards.Fields.RecipientEmail")] - [AllowHtml] - public string RecipientEmail { get; set; } - [NopResourceDisplayName("Admin.GiftCards.Fields.SenderName")] - [AllowHtml] - public string SenderName { get; set; } - [NopResourceDisplayName("Admin.GiftCards.Fields.SenderEmail")] - [AllowHtml] - public string SenderEmail { get; set; } - [NopResourceDisplayName("Admin.GiftCards.Fields.Message")] - [AllowHtml] - public string Message { get; set; } - - public GiftCardType GiftCardType { get; set; } - } - #endregion - } - - public partial class UsedDiscountModel:BaseNopModel - { - public int DiscountId { get; set; } - public string DiscountName { get; set; } - } - - #endregion - } - - - public partial class OrderAggreratorModel : BaseNopModel - { - //aggergator properties - public string aggregatorprofit { get; set; } - public string aggregatorshipping { get; set; } - public string aggregatortax { get; set; } - public string aggregatortotal { get; set; } - } +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Web.Mvc; +using Nop.Admin.Models.Common; +using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Tax; +using Nop.Web.Framework; +using Nop.Web.Framework.Mvc; + +namespace Nop.Admin.Models.Orders +{ + public partial class OrderModel : BaseNopEntityModel + { + public OrderModel() + { + CustomValues = new Dictionary(); + TaxRates = new List(); + GiftCards = new List(); + Items = new List(); + UsedDiscounts = new List(); + Warnings = new List(); + } + + public bool IsLoggedInAsVendor { get; set; } + + //identifiers + public override int Id { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.OrderGuid")] + public Guid OrderGuid { get; set; } + + [NopResourceDisplayName("Admin.Orders.Fields.InvoiceId")] + public string InvoiceId { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.InvoiceDateUtc")] + [UIHint("DateTimeNullable")] + public DateTime? InvoiceDateUtc { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.CustomOrderNumber")] + public string CustomOrderNumber { get; set; } + + //store + [NopResourceDisplayName("Admin.Orders.Fields.Store")] + public string StoreName { get; set; } + + //customer info + [NopResourceDisplayName("Admin.Orders.Fields.Customer")] + public int CustomerId { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.Customer")] + public string CustomerInfo { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.CustomerEmail")] + public string CustomerEmail { get; set; } + public string CustomerFullName { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.CustomerIP")] + public string CustomerIp { get; set; } + + [NopResourceDisplayName("Admin.Orders.Fields.CustomValues")] + public Dictionary CustomValues { get; set; } + + [NopResourceDisplayName("Admin.Orders.Fields.Affiliate")] + public int AffiliateId { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.Affiliate")] + public string AffiliateName { get; set; } + + //Used discounts + [NopResourceDisplayName("Admin.Orders.Fields.UsedDiscounts")] + public IList UsedDiscounts { get; set; } + + //totals + public bool AllowCustomersToSelectTaxDisplayType { get; set; } + public TaxDisplayType TaxDisplayType { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.OrderSubtotalInclTax")] + public string OrderSubtotalInclTax { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.OrderSubtotalExclTax")] + public string OrderSubtotalExclTax { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.OrderSubTotalDiscountInclTax")] + public string OrderSubTotalDiscountInclTax { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.OrderSubTotalDiscountExclTax")] + public string OrderSubTotalDiscountExclTax { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.OrderShippingInclTax")] + public string OrderShippingInclTax { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.OrderShippingExclTax")] + public string OrderShippingExclTax { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.OrderShippingNonTaxable")] + public string OrderShippingNonTaxable { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.PaymentMethodAdditionalFeeInclTax")] + public string PaymentMethodAdditionalFeeInclTax { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.PaymentMethodAdditionalFeeExclTax")] + public string PaymentMethodAdditionalFeeExclTax { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.PaymentMethodAdditionalFeeNonTaxable")] + public string PaymentMethodAdditionalFeeNonTaxable { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.Tax")] + public string Tax { get; set; } + public IList TaxRates { get; set; } + public bool DisplayTax { get; set; } + public bool DisplayTaxRates { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.OrderTotalDiscount")] + public string OrderTotalDiscount { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.RedeemedRewardPoints")] + public int RedeemedRewardPoints { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.RedeemedRewardPointsAmount")] + public string RedeemedRewardPointsAmount { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.RedeemedRewardPointsPurchased")] + public int RedeemedRewardPointsPurchased { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.RedeemedRewardPointsAmountPurchased")] + public string RedeemedRewardPointsAmountPurchased { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.OrderTotal")] + public string OrderTotal { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.RefundedAmount")] + public string RefundedAmount { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.Profit")] + public string Profit { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.OrderAmount")] + public string OrderAmount { get; set; } //MF 09.12.16 + [NopResourceDisplayName("Admin.Orders.Fields.OrderAmountIncl")] + public string OrderAmountIncl { get; set; } //MF 09.12.16 + [NopResourceDisplayName("Admin.Orders.Fields.OrderTotalDiscountIncl")] + public string OrderTotalDiscountIncl { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.EarnedRewardPointsBaseAmountIncl")] + public string EarnedRewardPointsBaseAmountIncl { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.EarnedRewardPointsBaseAmountExcl")] + public string EarnedRewardPointsBaseAmountExcl { get; set; } + + //edit totals + [NopResourceDisplayName("Admin.Orders.Fields.Edit.OrderSubtotal")] + public decimal OrderSubtotalInclTaxValue { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.Edit.OrderSubtotal")] + public decimal OrderSubtotalExclTaxValue { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.Edit.OrderSubTotalDiscount")] + public decimal OrderSubTotalDiscountInclTaxValue { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.Edit.OrderSubTotalDiscount")] + public decimal OrderSubTotalDiscountExclTaxValue { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.Edit.OrderShipping")] + public decimal OrderShippingInclTaxValue { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.Edit.OrderShipping")] + public decimal OrderShippingExclTaxValue { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.Edit.OrderShipping")] + public decimal OrderShippingNonTaxableValue { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.Edit.PaymentMethodAdditionalFee")] + public decimal PaymentMethodAdditionalFeeInclTaxValue { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.Edit.PaymentMethodAdditionalFee")] + public decimal PaymentMethodAdditionalFeeExclTaxValue { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.Edit.PaymentMethodAdditionalFee")] + public decimal PaymentMethodAdditionalFeeNonTaxableValue { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.Edit.Tax")] + public decimal TaxValue { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.Edit.TaxRates")] + public string TaxRatesValue { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.Edit.OrderTotalDiscount")] + public decimal OrderTotalDiscountValue { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.Edit.OrderTotal")] + public decimal OrderTotalValue { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.Edit.OrderAmount")] + public decimal OrderAmountValue { get; set; } //MF 09.12.16 + [NopResourceDisplayName("Admin.Orders.Fields.Edit.OrderAmountIncl")] + public decimal OrderAmountInclValue { get; set; } //MF 09.12.16 + [NopResourceDisplayName("Admin.Orders.Fields.Edit.OrderDiscountIncl")] + public decimal OrderTotalDiscountInclValue { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.Edit.EarnedRewardPointsBaseAmountIncl")] + public decimal EarnedRewardPointsBaseAmountInclValue { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.Edit.EarnedRewardPointsBaseAmountExcl")] + public decimal EarnedRewardPointsBaseAmountExclValue { get; set; } + //associated recurring payment id + [NopResourceDisplayName("Admin.Orders.Fields.RecurringPayment")] + public int RecurringPaymentId { get; set; } + + //order status + [NopResourceDisplayName("Admin.Orders.Fields.OrderStatus")] + public string OrderStatus { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.OrderStatus")] + public int OrderStatusId { get; set; } + + //payment info + [NopResourceDisplayName("Admin.Orders.Fields.PaymentStatus")] + public string PaymentStatus { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.PaymentStatus")] + public int PaymentStatusId { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.PaymentMethod")] + public string PaymentMethod { get; set; } + + //credit card info + public bool AllowStoringCreditCardNumber { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.CardType")] + [AllowHtml] + public string CardType { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.CardName")] + [AllowHtml] + public string CardName { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.CardNumber")] + [AllowHtml] + public string CardNumber { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.CardCVV2")] + [AllowHtml] + public string CardCvv2 { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.CardExpirationMonth")] + [AllowHtml] + public string CardExpirationMonth { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.CardExpirationYear")] + [AllowHtml] + public string CardExpirationYear { get; set; } + + //misc payment info + [NopResourceDisplayName("Admin.Orders.Fields.AuthorizationTransactionID")] + public string AuthorizationTransactionId { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.CaptureTransactionID")] + public string CaptureTransactionId { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.SubscriptionTransactionID")] + public string SubscriptionTransactionId { get; set; } + + //shipping info + public bool IsShippable { get; set; } + public bool PickUpInStore { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.PickupAddress")] + public AddressModel PickupAddress { get; set; } + public string PickupAddressGoogleMapsUrl { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.ShippingStatus")] + public string ShippingStatus { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.ShippingStatus")] + public int ShippingStatusId { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.ShippingAddress")] + public AddressModel ShippingAddress { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.ShippingMethod")] + public string ShippingMethod { get; set; } + public string ShippingAddressGoogleMapsUrl { get; set; } + public bool CanAddNewShipments { get; set; } + + //billing info + [NopResourceDisplayName("Admin.Orders.Fields.BillingAddress")] + public AddressModel BillingAddress { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.VatNumber")] + public string VatNumber { get; set; } + + //gift cards + public IList GiftCards { get; set; } + + //items + public bool HasDownloadableProducts { get; set; } + public IList Items { get; set; } + + //creation date + [NopResourceDisplayName("Admin.Orders.Fields.CreatedOn")] + public DateTime CreatedOn { get; set; } + + //checkout attributes + public string CheckoutAttributeInfo { get; set; } + + + //order notes + [NopResourceDisplayName("Admin.Orders.OrderNotes.Fields.DisplayToCustomer")] + public bool AddOrderNoteDisplayToCustomer { get; set; } + [NopResourceDisplayName("Admin.Orders.OrderNotes.Fields.Note")] + [AllowHtml] + public string AddOrderNoteMessage { get; set; } + public bool AddOrderNoteHasDownload { get; set; } + [NopResourceDisplayName("Admin.Orders.OrderNotes.Fields.Download")] + [UIHint("Download")] + public int AddOrderNoteDownloadId { get; set; } + + //refund info + [NopResourceDisplayName("Admin.Orders.Fields.PartialRefund.AmountToRefund")] + public decimal AmountToRefund { get; set; } + public decimal MaxAmountToRefund { get; set; } + public string PrimaryStoreCurrencyCode { get; set; } + + //workflow info + public bool CanCancelOrder { get; set; } + public bool CanCapture { get; set; } + public bool CanMarkOrderAsPaid { get; set; } + public bool CanRefund { get; set; } + public bool CanRefundOffline { get; set; } + public bool CanPartiallyRefund { get; set; } + public bool CanPartiallyRefundOffline { get; set; } + public bool CanVoid { get; set; } + public bool CanVoidOffline { get; set; } + + //warnings + public List Warnings { get; set; } + + #region Nested Classes + + public partial class OrderItemModel : BaseNopEntityModel + { + public OrderItemModel() + { + PurchasedGiftCardIds = new List(); + ReturnRequests = new List(); + } + public int ProductId { get; set; } + public string ProductName { get; set; } + public string VendorName { get; set; } + public string Sku { get; set; } + + public string PictureThumbnailUrl { get; set; } + + public string UnitPriceInclTax { get; set; } + public string UnitPriceExclTax { get; set; } + public decimal UnitPriceInclTaxValue { get; set; } + public decimal UnitPriceExclTaxValue { get; set; } + + public int Quantity { get; set; } + + public string DiscountInclTax { get; set; } + public string DiscountExclTax { get; set; } + public decimal DiscountInclTaxValue { get; set; } + public decimal DiscountExclTaxValue { get; set; } + + public string SubTotalInclTax { get; set; } + public string SubTotalExclTax { get; set; } + public decimal SubTotalInclTaxValue { get; set; } + public decimal SubTotalExclTaxValue { get; set; } + + public string AttributeInfo { get; set; } + public string RecurringInfo { get; set; } + public string RentalInfo { get; set; } + public IList ReturnRequests { get; set; } + public IList PurchasedGiftCardIds { get; set; } + + public bool IsDownload { get; set; } + public int DownloadCount { get; set; } + public DownloadActivationType DownloadActivationType { get; set; } + public bool IsDownloadActivated { get; set; } + public Guid LicenseDownloadGuid { get; set; } + public decimal TaxRate { get; set; } + + #region Nested Classes + + public partial class ReturnRequestBriefModel : BaseNopEntityModel + { + public string CustomNumber { get; set; } + } + + #endregion + } + + public partial class TaxRate : BaseNopModel + { + [NopResourceDisplayName("Order.TaxRateLine.TaxRate")] + public string Rate { get; set; } + [NopResourceDisplayName("Order.TaxRateLine.Amount")] + public string Amount { get; set; } // includes subtotal, shipping and payment fee + [NopResourceDisplayName("Order.TaxRateLine.DiscountAmount")] + public string DiscountAmount { get; set; } + [NopResourceDisplayName("Order.TaxRateLine.BaseAmount")] + public string BaseAmount { get; set; } + [NopResourceDisplayName("Order.TaxRateLine.TaxAmount")] + public string TaxAmount { get; set; } + } + + public partial class GiftCard : BaseNopModel + { + [NopResourceDisplayName("Admin.Orders.Fields.GiftCardInfo")] + public string CouponCode { get; set; } + public string Amount { get; set; } + } + + public partial class OrderNote : BaseNopEntityModel + { + public int OrderId { get; set; } + [NopResourceDisplayName("Admin.Orders.OrderNotes.Fields.DisplayToCustomer")] + public bool DisplayToCustomer { get; set; } + [NopResourceDisplayName("Admin.Orders.OrderNotes.Fields.Note")] + public string Note { get; set; } + [NopResourceDisplayName("Admin.Orders.OrderNotes.Fields.Download")] + public int DownloadId { get; set; } + [NopResourceDisplayName("Admin.Orders.OrderNotes.Fields.Download")] + public Guid DownloadGuid { get; set; } + [NopResourceDisplayName("Admin.Orders.OrderNotes.Fields.CreatedOn")] + public DateTime CreatedOn { get; set; } + } + + public partial class UploadLicenseModel : BaseNopModel + { + public int OrderId { get; set; } + + public int OrderItemId { get; set; } + + [UIHint("Download")] + public int LicenseDownloadId { get; set; } + + } + + public partial class AddOrderProductModel : BaseNopModel + { + public AddOrderProductModel() + { + AvailableCategories = new List(); + AvailableManufacturers = new List(); + AvailableProductTypes = new List(); + } + + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchProductName")] + [AllowHtml] + public string SearchProductName { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchCategory")] + public int SearchCategoryId { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchManufacturer")] + public int SearchManufacturerId { get; set; } + [NopResourceDisplayName("Admin.Catalog.Products.List.SearchProductType")] + public int SearchProductTypeId { get; set; } + + public IList AvailableCategories { get; set; } + public IList AvailableManufacturers { get; set; } + public IList AvailableProductTypes { get; set; } + + public int OrderId { get; set; } + + #region Nested classes + + public partial class ProductModel : BaseNopEntityModel + { + [NopResourceDisplayName("Admin.Orders.Products.AddNew.Name")] + [AllowHtml] + public string Name { get; set; } + + [NopResourceDisplayName("Admin.Orders.Products.AddNew.SKU")] + [AllowHtml] + public string Sku { get; set; } + } + + public partial class ProductDetailsModel : BaseNopModel + { + public ProductDetailsModel() + { + ProductAttributes = new List(); + GiftCard = new GiftCardModel(); + Warnings = new List(); + } + + public int ProductId { get; set; } + + public int OrderId { get; set; } + + public ProductType ProductType { get; set; } + + public string Name { get; set; } + + [NopResourceDisplayName("Admin.Orders.Products.AddNew.UnitPriceInclTax")] + public decimal UnitPriceInclTax { get; set; } + [NopResourceDisplayName("Admin.Orders.Products.AddNew.UnitPriceExclTax")] + public decimal UnitPriceExclTax { get; set; } + + [NopResourceDisplayName("Admin.Orders.Products.AddNew.Quantity")] + public int Quantity { get; set; } + + [NopResourceDisplayName("Admin.Orders.Products.AddNew.SubTotalInclTax")] + public decimal SubTotalInclTax { get; set; } + [NopResourceDisplayName("Admin.Orders.Products.AddNew.SubTotalExclTax")] + public decimal SubTotalExclTax { get; set; } + [NopResourceDisplayName("ShoppingCart.TaxRate")] + public decimal TaxRate { get; set; } + + //product attributes + public IList ProductAttributes { get; set; } + //gift card info + public GiftCardModel GiftCard { get; set; } + //rental + public bool IsRental { get; set; } + + public List Warnings { get; set; } + + /// + /// A value indicating whether this attribute depends on some other attribute + /// + public bool HasCondition { get; set; } + + public bool AutoUpdateOrderTotals { get; set; } + } + + public partial class ProductAttributeModel : BaseNopEntityModel + { + public ProductAttributeModel() + { + Values = new List(); + } + + public int ProductAttributeId { get; set; } + + public string Name { get; set; } + + public string TextPrompt { get; set; } + + public bool IsRequired { get; set; } + + public bool HasCondition { get; set; } + + /// + /// Allowed file extensions for customer uploaded files + /// + public IList AllowedFileExtensions { get; set; } + + public AttributeControlType AttributeControlType { get; set; } + + public IList Values { get; set; } + } + + public partial class ProductAttributeValueModel : BaseNopEntityModel + { + public string Name { get; set; } + + public bool IsPreSelected { get; set; } + + public string PriceAdjustment { get; set; } + + public decimal PriceAdjustmentValue { get; set; } + + public bool CustomerEntersQty { get; set; } + + public int Quantity { get; set; } + } + + + public partial class GiftCardModel : BaseNopModel + { + public bool IsGiftCard { get; set; } + + [NopResourceDisplayName("Admin.GiftCards.Fields.RecipientName")] + [AllowHtml] + public string RecipientName { get; set; } + [NopResourceDisplayName("Admin.GiftCards.Fields.RecipientEmail")] + [AllowHtml] + public string RecipientEmail { get; set; } + [NopResourceDisplayName("Admin.GiftCards.Fields.SenderName")] + [AllowHtml] + public string SenderName { get; set; } + [NopResourceDisplayName("Admin.GiftCards.Fields.SenderEmail")] + [AllowHtml] + public string SenderEmail { get; set; } + [NopResourceDisplayName("Admin.GiftCards.Fields.Message")] + [AllowHtml] + public string Message { get; set; } + + public GiftCardType GiftCardType { get; set; } + } + #endregion + } + + public partial class UsedDiscountModel:BaseNopModel + { + public int DiscountId { get; set; } + public string DiscountName { get; set; } + } + + #endregion + } + + + public partial class OrderAggreratorModel : BaseNopModel + { + //aggergator properties + public string aggregatorprofit { get; set; } + public string aggregatorshipping { get; set; } + public string aggregatortax { get; set; } + public string aggregatortotal { get; set; } + } } \ No newline at end of file diff --git a/src/Presentation/Nop.Web/Administration/Models/Settings/ProductEditorSettingsModel.cs b/src/Presentation/Nop.Web/Administration/Models/Settings/ProductEditorSettingsModel.cs index 5130fc45a12..64bffbac077 100644 --- a/src/Presentation/Nop.Web/Administration/Models/Settings/ProductEditorSettingsModel.cs +++ b/src/Presentation/Nop.Web/Administration/Models/Settings/ProductEditorSettingsModel.cs @@ -82,6 +82,8 @@ public partial class ProductEditorSettingsModel : BaseNopModel [NopResourceDisplayName("Admin.Configuration.Settings.ProductEditor.IsGiftCard")] public bool IsGiftCard { get; set; } + [NopResourceDisplayName("Admin.Configuration.Settings.ProductEditor.IsRewardPoints")] + public bool IsRewardPoints { get; set; } [NopResourceDisplayName("Admin.Configuration.Settings.ProductEditor.DownloadableProduct")] public bool DownloadableProduct { get; set; } diff --git a/src/Presentation/Nop.Web/Administration/Models/Settings/RewardPointsSettingsModel.cs b/src/Presentation/Nop.Web/Administration/Models/Settings/RewardPointsSettingsModel.cs index a7ffc576f75..72060d66acd 100644 --- a/src/Presentation/Nop.Web/Administration/Models/Settings/RewardPointsSettingsModel.cs +++ b/src/Presentation/Nop.Web/Administration/Models/Settings/RewardPointsSettingsModel.cs @@ -1,53 +1,73 @@ -using Nop.Web.Framework; -using Nop.Web.Framework.Mvc; - -namespace Nop.Admin.Models.Settings -{ - public partial class RewardPointsSettingsModel : BaseNopModel - { - public int ActiveStoreScopeConfiguration { get; set; } - - - [NopResourceDisplayName("Admin.Configuration.Settings.RewardPoints.Enabled")] - public bool Enabled { get; set; } - public bool Enabled_OverrideForStore { get; set; } - - [NopResourceDisplayName("Admin.Configuration.Settings.RewardPoints.ExchangeRate")] - public decimal ExchangeRate { get; set; } - public bool ExchangeRate_OverrideForStore { get; set; } - - [NopResourceDisplayName("Admin.Configuration.Settings.RewardPoints.MinimumRewardPointsToUse")] - public int MinimumRewardPointsToUse { get; set; } - public bool MinimumRewardPointsToUse_OverrideForStore { get; set; } - - [NopResourceDisplayName("Admin.Configuration.Settings.RewardPoints.PointsForRegistration")] - public int PointsForRegistration { get; set; } - public bool PointsForRegistration_OverrideForStore { get; set; } - - [NopResourceDisplayName("Admin.Configuration.Settings.RewardPoints.PointsForPurchases_Amount")] - public decimal PointsForPurchases_Amount { get; set; } - public int PointsForPurchases_Points { get; set; } - public bool PointsForPurchases_OverrideForStore { get; set; } - - [NopResourceDisplayName("Admin.Configuration.Settings.RewardPoints.ActivatePointsImmediately")] - public bool ActivatePointsImmediately { get; set; } - - [NopResourceDisplayName("Admin.Configuration.Settings.RewardPoints.ActivationDelay")] - public int ActivationDelay { get; set; } - public bool ActivationDelay_OverrideForStore { get; set; } - public int ActivationDelayPeriodId { get; set; } - - [NopResourceDisplayName("Admin.Configuration.Settings.RewardPoints.DisplayHowMuchWillBeEarned")] - public bool DisplayHowMuchWillBeEarned { get; set; } - public bool DisplayHowMuchWillBeEarned_OverrideForStore { get; set; } - - [NopResourceDisplayName("Admin.Configuration.Settings.RewardPoints.PointsAccumulatedForAllStores")] - public bool PointsAccumulatedForAllStores { get; set; } - - [NopResourceDisplayName("Admin.Configuration.Settings.RewardPoints.PageSize")] - public int PageSize { get; set; } - public bool PageSize_OverrideForStore { get; set; } - - public string PrimaryStoreCurrencyCode { get; set; } - } +using Nop.Web.Framework; +using Nop.Web.Framework.Mvc; + +namespace Nop.Admin.Models.Settings +{ + public partial class RewardPointsSettingsModel : BaseNopModel + { + public int ActiveStoreScopeConfiguration { get; set; } + + + [NopResourceDisplayName("Admin.Configuration.Settings.RewardPoints.Enabled")] + public bool Enabled { get; set; } + public bool Enabled_OverrideForStore { get; set; } + + [NopResourceDisplayName("Admin.Configuration.Settings.RewardPoints.ExchangeRate")] + public decimal ExchangeRate { get; set; } + public bool ExchangeRate_OverrideForStore { get; set; } + + [NopResourceDisplayName("Admin.Configuration.Settings.RewardPoints.MinimumRewardPointsToUse")] + public int MinimumRewardPointsToUse { get; set; } + public bool MinimumRewardPointsToUse_OverrideForStore { get; set; } + + [NopResourceDisplayName("Admin.Configuration.Settings.RewardPoints.PointsForRegistration")] + public int PointsForRegistration { get; set; } + public bool PointsForRegistration_OverrideForStore { get; set; } + + [NopResourceDisplayName("Admin.Configuration.Settings.RewardPoints.PointsForPurchases_Amount")] + public decimal PointsForPurchases_Amount { get; set; } + public int PointsForPurchases_Points { get; set; } + public bool PointsForPurchases_OverrideForStore { get; set; } + + [NopResourceDisplayName("Admin.Configuration.Settings.RewardPoints.ActivatePointsImmediately")] + public bool ActivatePointsImmediately { get; set; } + + [NopResourceDisplayName("Admin.Configuration.Settings.RewardPoints.ActivationDelay")] + public int ActivationDelay { get; set; } + public bool ActivationDelay_OverrideForStore { get; set; } + public int ActivationDelayPeriodId { get; set; } + + [NopResourceDisplayName("Admin.Configuration.Settings.RewardPoints.DisplayHowMuchWillBeEarned")] + public bool DisplayHowMuchWillBeEarned { get; set; } + public bool DisplayHowMuchWillBeEarned_OverrideForStore { get; set; } + + [NopResourceDisplayName("Admin.Configuration.Settings.RewardPoints.PointsAccumulatedForAllStores")] + public bool PointsAccumulatedForAllStores { get; set; } + + [NopResourceDisplayName("Admin.Configuration.Settings.RewardPoints.PageSize")] + public int PageSize { get; set; } + public bool PageSize_OverrideForStore { get; set; } + + public string PrimaryStoreCurrencyCode { get; set; } + + [NopResourceDisplayName("Admin.Configuration.Settings.RewardPoints.EarnedRewardPointsAreTaxable")] + public bool EarnedRewardPointsAreTaxable { get; set; } + public bool EarnedRewardPointsAreTaxable_OverrideForStore { get; set; } + [NopResourceDisplayName("Admin.Configuration.Settings.RewardPoints.AwardPointsIncludeShipping")] + public bool AwardPointsIncludeShipping { get; set; } + public bool AwardPointsIncludeShipping_OverrideForStore { get; set; } + [NopResourceDisplayName("Admin.Configuration.Settings.RewardPoints.AwardPointsIncludePaymentMethodAdditionalFee")] + public bool AwardPointsIncludePaymentMethodAdditionalFee { get; set; } + public bool AwardPointsIncludePaymentMethodAdditionalFee_OverrideForStore { get; set; } + [NopResourceDisplayName("Admin.Configuration.Settings.RewardPoints.AwardPointsExcludeGiftCard")] + public bool AwardPointsExcludeGiftCard { get; set; } + public bool AwardPointsExcludeGiftCard_OverrideForStore { get; set; } + [NopResourceDisplayName("Admin.Configuration.Settings.RewardPoints.AwardPointsExcludePurchasedRewardPoints")] + public bool AwardPointsExcludePurchasedRewardPoints { get; set; } + public bool AwardPointsExcludePurchasedRewardPoints_OverrideForStore { get; set; } + [NopResourceDisplayName("Admin.Configuration.Settings.RewardPoints.EarnRewardPointsOnlyWhenUsingPurchasedRewardPoints")] + public bool EarnRewardPointsOnlyWhenUsingPurchasedRewardPoints { get; set; } + public bool EarnRewardPointsOnlyWhenUsingPurchasedRewardPoints_OverrideForStore { get; set; } + + } } \ No newline at end of file diff --git a/src/Presentation/Nop.Web/Administration/Views/Customer/_CreateOrUpdate.cshtml b/src/Presentation/Nop.Web/Administration/Views/Customer/_CreateOrUpdate.cshtml index 0b01572124c..423a1f204bf 100644 --- a/src/Presentation/Nop.Web/Administration/Views/Customer/_CreateOrUpdate.cshtml +++ b/src/Presentation/Nop.Web/Administration/Views/Customer/_CreateOrUpdate.cshtml @@ -25,7 +25,7 @@ @Html.RenderBootstrapTabHeader("tab-activitylog", @T("Admin.Customers.Customers.ActivityLog")) @Html.RenderBootstrapTabHeader("tab-impersonate", @T("Admin.Customers.Customers.Impersonate")) @Html.RenderBootstrapTabHeader("tab-backinstock", @T("Admin.Customers.Customers.BackInStockSubscriptions")) - } + } @if (Model.DisplayRewardPointsHistory) { @Html.RenderBootstrapTabHeader("tab-rewardpoints", @T("Admin.Customers.Customers.RewardPoints")) @@ -42,7 +42,7 @@ @Html.RenderBootstrapTabContent("tab-activitylog", @TabActivityLog()) @Html.RenderBootstrapTabContent("tab-impersonate", @TabImpersonate()) @Html.RenderBootstrapTabContent("tab-backinstock", @TabBackInStockSubscriptions()) - } + } @if (Model.DisplayRewardPointsHistory) { @Html.RenderBootstrapTabContent("tab-rewardpoints", @TabRewardPoints()) @@ -1153,12 +1153,36 @@ var stores = EngineContext.Current.Resolve().GetAllStores(); type: "POST", dataType: "json", data: addAntiForgeryToken + }, + update: { + url: "@Html.Raw(Url.Action("RewardPointsHistoryUpdate", "Customer"))", + type: "POST", + dataType: "json", + data: addAntiForgeryToken } }, schema: { data: "Data", total: "Total", - errors: "Errors" + errors: "Errors", + model: { + id: "Id", + fields: { + StoreName: { editable: false, type: "string" }, + Points: { editable: false, type: "number" }, + PointsBalance: { editable: false, type: "string" }, + PointsPurchased: { editable: false, type: "string" }, + PointsBalancePurchased: { editable: false, type: "string" }, + Message: { editable: true, type: "string" }, + CreatedOn: { editable: false, type: "date" }, + Id: { editable: false, type: "number" } + } + } + }, + requestEnd: function(e) { + if (e.type == "update") { + this.read(); + } }, error: function(e) { display_kendoui_grid_error(e); @@ -1191,6 +1215,12 @@ var stores = EngineContext.Current.Resolve().GetAllStores(); }, { field: "PointsBalance", title: "@T("Admin.Customers.Customers.RewardPoints.Fields.PointsBalance")" + }, { + field: "PointsPurchased", + title: "@T("Admin.Customers.Customers.RewardPoints.Fields.PointsPurchased")" + }, { + field: "PointsBalancePurchased", + title: "@T("Admin.Customers.Customers.RewardPoints.Fields.PointsBalancePurchased")" }, { field: "Message", title: "@T("Admin.Customers.Customers.RewardPoints.Fields.Message")" @@ -1198,8 +1228,19 @@ var stores = EngineContext.Current.Resolve().GetAllStores(); field: "CreatedOn", title: "@T("Admin.Promotions.Campaigns.Fields.CreatedOn")", width: 200, - type: "date", - format: "{0:G}" + format: "{0:G}"" + }, { + command: [ + { + name: "edit", + text: { + edit: "@T("Admin.Common.Edit")", + update: "@T("Admin.Common.Update")", + cancel: "@T("Admin.Common.Cancel")" + } + } + ], + width: 200 } ] }); @@ -1222,6 +1263,15 @@ var stores = EngineContext.Current.Resolve().GetAllStores(); @Html.ValidationMessageFor(model => model.AddRewardPointsValue) +
    +
    + @Html.NopLabelFor(model => model.AddRewardPointsValuePurchased) +
    +
    + @Html.NopEditorFor(model => model.AddRewardPointsValuePurchased) + @Html.ValidationMessageFor(model => model.AddRewardPointsValuePurchased) +
    +
    @Html.NopLabelFor(model => model.AddRewardPointsMessage) @@ -1253,12 +1303,13 @@ var stores = EngineContext.Current.Resolve().GetAllStores(); $(document).ready(function () { $('#addRewardPoints').click(function () { var rewardPointsValue = $("#@Html.FieldIdFor(model => model.AddRewardPointsValue)").val(); + var rewardPointsValuePurchased = $("#@Html.FieldIdFor(model => model.AddRewardPointsValuePurchased)").val(); var rewardPointsMessage = $("#@Html.FieldIdFor(model => model.AddRewardPointsMessage)").val(); var rewardPointsStoreId = $("#@Html.FieldIdFor(model => model.AddRewardPointsStoreId)").val(); $('#addRewardPoints').attr('disabled', true); var postData = { - addRewardPointsValue: rewardPointsValue, + addRewardPointsValue: [rewardPointsValue, rewardPointsValuePurchased], addRewardPointsMessage: rewardPointsMessage, storeId: rewardPointsStoreId, customerId: @Model.Id diff --git a/src/Presentation/Nop.Web/Administration/Views/Order/AddProductToOrderDetails.cshtml b/src/Presentation/Nop.Web/Administration/Views/Order/AddProductToOrderDetails.cshtml index db6b807f9f8..1ac98399b52 100644 --- a/src/Presentation/Nop.Web/Administration/Views/Order/AddProductToOrderDetails.cshtml +++ b/src/Presentation/Nop.Web/Administration/Views/Order/AddProductToOrderDetails.cshtml @@ -1,150 +1,150 @@ -@model OrderModel.AddOrderProductModel.ProductDetailsModel - -@using Nop.Core.Domain.Catalog - -@{ - //page title - ViewBag.Title = string.Format(T("Admin.Orders.Products.AddNew.Title2").Text, Model.Name, Model.OrderId); - //active menu item (system name) - Html.SetActiveMenuItemSystemName("Orders"); -} - -@*we add enctype = "multipart/form-data" because "File upload" attribute control type requires it*@ -@using (Html.BeginForm("AddProductToOrderDetails", "Order", - new RouteValueDictionary(new { orderId = Model.OrderId, productId = Model.ProductId }), - FormMethod.Post, - new Dictionary { { "enctype", "multipart/form-data" }, {"id", "product-details-form" } })) -{ - @Html.AntiForgeryToken() - @Html.ValidationSummary(false) -
    -

    - @string.Format(T("Admin.Orders.Products.AddNew.Title2").Text, Model.Name, Model.OrderId) - @Html.ActionLink(T("Admin.Orders.Products.AddNew.BackToList").Text, "AddProductToOrder", new { orderId = Model.OrderId }) -

    -
    -   -
    -
    - -
    - @if (Model.Warnings.Count > 0) - { -
    -
      - @foreach (var warning in Model.Warnings) - { -
    • @warning
    • - } -
    -
    - } - -
    -
    - @if (Model.ProductType == ProductType.SimpleProduct) - { -
    -
    -
    -
    - @Html.NopLabelFor(model => model.UnitPriceInclTax) -
    -
    - @Html.NopEditorFor(model => model.UnitPriceInclTax) -
    -
    -
    -
    - @Html.NopLabelFor(model => model.UnitPriceExclTax) -
    -
    - @Html.NopEditorFor(model => model.UnitPriceExclTax) -
    -
    -
    -
    - @Html.NopLabelFor(model => model.Quantity) -
    -
    - @Html.NopEditorFor(model => model.Quantity) -
    -
    -
    -
    - @Html.NopLabelFor(model => model.SubTotalInclTax) -
    -
    - @Html.NopEditorFor(model => model.SubTotalInclTax) -
    -
    -
    -
    - @Html.NopLabelFor(model => model.SubTotalExclTax) -
    -
    - @Html.NopEditorFor(model => model.SubTotalExclTax) -
    -
    -
    -
    - @Html.NopLabelFor(model => model.VatRate) -
    -
    - @Html.NopEditorFor(model => model.VatRate) -
    -
    -
    -
    - - @Html.Partial("_ProductAddAttributes", Model.ProductAttributes, new ViewDataDictionary() {new KeyValuePair("productId", Model.ProductId)}) - - var dataDictGiftCard = new ViewDataDictionary(); - dataDictGiftCard.TemplateInfo.HtmlFieldPrefix = "giftcard"; - @Html.Partial("_ProductAddGiftCardInfo", Model.GiftCard, dataDictGiftCard) - - @Html.Partial("_ProductAddRentalInfo", Model) - -
    -
    - @if (!Model.AutoUpdateOrderTotals) - { -
    -
    -

    - @T("Admin.Orders.Products.AddNew.UpdateTotals") -

    -
    -
    - } -
    -
    - -
    -
    -
    -
    - } - else if (Model.ProductType == ProductType.GroupedProduct) - { -
    -
    - @*TODO add support for grouped products to be added to an existing order*@ - Grouped products are not currently supported for adding to an existing order -
    -
    - } - else - { -
    -
    - This product type (unknown) is not currently supported for adding to an existing order -
    -
    - } -
    -
    -
    +@model OrderModel.AddOrderProductModel.ProductDetailsModel + +@using Nop.Core.Domain.Catalog + +@{ + //page title + ViewBag.Title = string.Format(T("Admin.Orders.Products.AddNew.Title2").Text, Model.Name, Model.OrderId); + //active menu item (system name) + Html.SetActiveMenuItemSystemName("Orders"); +} + +@*we add enctype = "multipart/form-data" because "File upload" attribute control type requires it*@ +@using (Html.BeginForm("AddProductToOrderDetails", "Order", + new RouteValueDictionary(new { orderId = Model.OrderId, productId = Model.ProductId }), + FormMethod.Post, + new Dictionary { { "enctype", "multipart/form-data" }, {"id", "product-details-form" } })) +{ + @Html.AntiForgeryToken() + @Html.ValidationSummary(false) +
    +

    + @string.Format(T("Admin.Orders.Products.AddNew.Title2").Text, Model.Name, Model.OrderId) + @Html.ActionLink(T("Admin.Orders.Products.AddNew.BackToList").Text, "AddProductToOrder", new { orderId = Model.OrderId }) +

    +
    +   +
    +
    + +
    + @if (Model.Warnings.Count > 0) + { +
    +
      + @foreach (var warning in Model.Warnings) + { +
    • @warning
    • + } +
    +
    + } + +
    +
    + @if (Model.ProductType == ProductType.SimpleProduct) + { +
    +
    +
    +
    + @Html.NopLabelFor(model => model.UnitPriceInclTax) +
    +
    + @Html.NopEditorFor(model => model.UnitPriceInclTax) +
    +
    +
    +
    + @Html.NopLabelFor(model => model.UnitPriceExclTax) +
    +
    + @Html.NopEditorFor(model => model.UnitPriceExclTax) +
    +
    +
    +
    + @Html.NopLabelFor(model => model.Quantity) +
    +
    + @Html.NopEditorFor(model => model.Quantity) +
    +
    +
    +
    + @Html.NopLabelFor(model => model.SubTotalInclTax) +
    +
    + @Html.NopEditorFor(model => model.SubTotalInclTax) +
    +
    +
    +
    + @Html.NopLabelFor(model => model.SubTotalExclTax) +
    +
    + @Html.NopEditorFor(model => model.SubTotalExclTax) +
    +
    +
    +
    + @Html.NopLabelFor(model => model.TaxRate) +
    +
    + @Html.NopEditorFor(model => model.TaxRate) +
    +
    +
    +
    + + @Html.Partial("_ProductAddAttributes", Model.ProductAttributes, new ViewDataDictionary() {new KeyValuePair("productId", Model.ProductId)}) + + var dataDictGiftCard = new ViewDataDictionary(); + dataDictGiftCard.TemplateInfo.HtmlFieldPrefix = "giftcard"; + @Html.Partial("_ProductAddGiftCardInfo", Model.GiftCard, dataDictGiftCard) + + @Html.Partial("_ProductAddRentalInfo", Model) + +
    +
    + @if (!Model.AutoUpdateOrderTotals) + { +
    +
    +

    + @T("Admin.Orders.Products.AddNew.UpdateTotals") +

    +
    +
    + } +
    +
    + +
    +
    +
    +
    + } + else if (Model.ProductType == ProductType.GroupedProduct) + { +
    +
    + @*TODO add support for grouped products to be added to an existing order*@ + Grouped products are not currently supported for adding to an existing order +
    +
    + } + else + { +
    +
    + This product type (unknown) is not currently supported for adding to an existing order +
    +
    + } +
    +
    +
    } \ No newline at end of file diff --git a/src/Presentation/Nop.Web/Administration/Views/Order/_OrderDetails.Info.cshtml b/src/Presentation/Nop.Web/Administration/Views/Order/_OrderDetails.Info.cshtml index 4e2e379c0d4..2a38becc4f7 100644 --- a/src/Presentation/Nop.Web/Administration/Views/Order/_OrderDetails.Info.cshtml +++ b/src/Presentation/Nop.Web/Administration/Views/Order/_OrderDetails.Info.cshtml @@ -1,927 +1,1030 @@ -@model OrderModel - -@using Nop.Core.Domain.Tax; -@using Nop.Core.Domain.Orders; -@using Nop.Services -@if (!Model.IsLoggedInAsVendor) -{ - -} -
    -
    -
    - @if (!Model.IsLoggedInAsVendor) - { -
    -
    - @Html.NopLabelFor(model => model.OrderStatus) -
    -
    -
    -
    - - @Html.NopDisplayFor(model => model.OrderStatus) - -
    -
    - @if (Model.CanCancelOrder) - { - - @Html.ActionConfirmation("cancelorder") - } - -
    - @Html.NopDropDownListFor(model => model.OrderStatusId, ((OrderStatus) Model.OrderStatusId).ToSelectList()) - - @Html.ActionConfirmation("btnSaveOrderStatus") - -
    - @T("Admin.Orders.Fields.OrderStatus.Change.ForAdvancedUsers") -
    -
    -
    -
    -
    -
    - } - else - { - //vendors should see whether or order is cancelled - if (Model.OrderStatusId == (int)OrderStatus.Cancelled) - { -
    -
    -   -
    -
    - - @T("Admin.Orders.Fields.OrderStatus.CancelledNotification") - -
    -
    - } - } -
    -
    - @Html.NopLabelFor(model => model.CustomOrderNumber) -
    -
    - @Html.NopDisplayFor(model => model.CustomOrderNumber) -
    -
    - @if (!Model.IsLoggedInAsVendor) - { -
    -
    - @Html.NopLabelFor(model => model.OrderGuid) -
    -
    - @Html.NopDisplayFor(model => model.OrderGuid) -
    -
    - } - @if (!Model.IsLoggedInAsVendor) - { -
    -
    - @Html.NopLabelFor(model => model.InvoiceId) -
    -
    -
    - @Html.NopDisplayFor(model => model.InvoiceId) -
    -
    - @Html.NopEditorFor(model => model.InvoiceId) -
    -
    -
    - } - @if (!Model.IsLoggedInAsVendor) - { -
    -
    - @Html.NopLabelFor(model => model.InvoiceDateUtc) -
    -
    -
    - @Html.NopDisplayFor(model => model.InvoiceDateUtc) -
    -
    - @Html.NopEditorFor(model => model.InvoiceDateUtc) -
    -
    -
    - } - @if (!Model.IsLoggedInAsVendor) - { -
    -
    - - - - @Html.ActionConfirmation("btnSaveIV") - - -
    -
    - } -
    -
    - @Html.NopLabelFor(model => model.StoreName) -
    -
    - @Html.NopDisplayFor(model => model.StoreName) -
    -
    -
    -
    - - @if (!Model.IsLoggedInAsVendor) - { -
    -
    -
    -
    - @Html.NopLabelFor(model => model.CustomerId) -
    -
    - @Html.ActionLink(Model.CustomerInfo, "Edit", "Customer", new { id = Model.CustomerId }, new { }) -
    -
    -
    -
    - @Html.NopLabelFor(model => model.CustomerIp) -
    -
    - @Html.NopDisplayFor(model => model.CustomerIp) -
    -
    - @if (Model.RecurringPaymentId > 0) - { -
    -
    - @Html.NopLabelFor(model => model.RecurringPaymentId) -
    - -
    - } - @if (!String.IsNullOrEmpty(Model.VatNumber)) - { -
    -
    - @Html.NopLabelFor(model => model.VatNumber) -
    -
    - @Html.NopDisplayFor(model => model.VatNumber) -
    -
    - } - @if (Model.AffiliateId > 0) - { -
    -
    - @Html.NopLabelFor(model => model.AffiliateId) -
    - -
    - } -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - @if (Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.IncludingTax) - { -
    -
    - @Html.DisplayFor(model => model.OrderSubtotalInclTax) @*@T("Admin.Orders.Fields.OrderSubtotalInclTax")*@ -
    -
    - } - @if (Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.ExcludingTax) - { -
    -
    - @Html.DisplayFor(model => model.OrderSubtotalExclTax) @*@T("Admin.Orders.Fields.OrderSubtotalExclTax")*@ -
    -
    - } -
    -
    -
    - @if (((Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.IncludingTax) && !String.IsNullOrEmpty(Model.OrderSubTotalDiscountInclTax)) || - ((Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.ExcludingTax) && !String.IsNullOrEmpty(Model.OrderSubTotalDiscountExclTax))) - { -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - @if ((Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.IncludingTax) && !String.IsNullOrEmpty(Model.OrderSubTotalDiscountInclTax)) - { -
    -
    - @Html.DisplayFor(model => model.OrderSubTotalDiscountInclTax) @*@T("Admin.Orders.Fields.OrderSubTotalDiscountInclTax")*@ -
    -
    - } - @if ((Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.ExcludingTax) && !String.IsNullOrEmpty(Model.OrderSubTotalDiscountExclTax)) - { -
    -
    - @Html.DisplayFor(model => model.OrderSubTotalDiscountExclTax) @*@T("Admin.Orders.Fields.OrderSubTotalDiscountExclTax")*@ -
    -
    - } -
    -
    -
    - } -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - @if (Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.IncludingTax) - { -
    -
    - @Html.DisplayFor(model => model.OrderShippingInclTax) @*@T("Admin.Orders.Fields.OrderShippingInclTax")*@ -
    -
    - } - @if (Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.ExcludingTax) - { -
    -
    - @Html.DisplayFor(model => model.OrderShippingExclTax) @*@T("Admin.Orders.Fields.OrderShippingExclTax")*@ -
    -
    - } -
    -
    -
    - @if (((Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.IncludingTax) && !String.IsNullOrEmpty(Model.PaymentMethodAdditionalFeeInclTax)) || - ((Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.ExcludingTax) && !String.IsNullOrEmpty(Model.PaymentMethodAdditionalFeeExclTax))) - { -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - @if (Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.IncludingTax) - { -
    -
    - @Html.DisplayFor(model => model.PaymentMethodAdditionalFeeInclTax) @*@T("Admin.Orders.Fields.PaymentMethodAdditionalFeeInclTax")*@ -
    -
    - } - @if (Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.ExcludingTax) - { -
    -
    - @Html.DisplayFor(model => model.PaymentMethodAdditionalFeeExclTax) @*@T("Admin.Orders.Fields.PaymentMethodAdditionalFeeExclTax")*@ -
    -
    - } -
    -
    -
    - } - @if (Model.DisplayTaxRates) - { - foreach (var tr in Model.TaxRates) - { -
    -
    - @Html.NopLabelFor(model => model.Tax) @tr.Rate% -
    -
    - @Html.NopDisplayFor(model => tr.VatAmount) -
    -
    - } - } - @if (Model.DisplayTax) - { -
    -
    - @Html.NopLabelFor(model => model.Tax) -
    -
    - @Html.NopDisplayFor(model => model.Tax) -
    -
    - } - @if (!String.IsNullOrEmpty(Model.OrderTotalDiscount)) - { -
    -
    - @Html.NopLabelFor(model => model.OrderTotalDiscount) -
    -
    - @Html.NopDisplayFor(model => model.OrderTotalDiscount) -
    -
    - } -
    -
    - @Html.NopLabelFor(model => model.OrderAmount) -
    -
    - @Html.NopDisplayFor(model => model.OrderAmount) -
    -
    -
    -
    - @Html.NopLabelFor(model => model.OrderAmountIncl) -
    -
    - @Html.NopDisplayFor(model => model.OrderAmountIncl) -
    -
    - @foreach (var gc in Model.GiftCards) - { -
    -
    - @Html.NopLabelFor(model => model.GiftCards[0].CouponCode) - (@(gc.CouponCode)) -
    -
    - @Html.NopDisplay(gc.Amount) -
    -
    - } - @if (Model.RedeemedRewardPoints > 0) - { -
    -
    - @Html.NopLabelFor(model => model.RedeemedRewardPoints) -
    -
    - @Html.NopDisplay(Model.RedeemedRewardPoints + @T("Admin.Orders.Fields.RedeemedRewardPoints.Points").Text + "/" + Model.RedeemedRewardPointsAmount) -
    -
    - } -
    -
    - @Html.NopLabelFor(model => model.OrderTotal) -
    -
    - @Html.NopDisplayFor(model => model.OrderTotal) -
    -
    - @if (!String.IsNullOrEmpty(Model.RefundedAmount)) - { -
    -
    - @Html.NopLabelFor(model => model.RefundedAmount) -
    -
    - @Html.NopDisplayFor(model => model.RefundedAmount) -
    -
    - } - @if (Model.UsedDiscounts.Count > 0) - { -
    -
    - @Html.NopLabelFor(model => model.UsedDiscounts) -
    -
    - @for (int i = 0; i < Model.UsedDiscounts.Count; i++) - { - var discount = Model.UsedDiscounts[i]; - @discount.DiscountName - if (i != Model.UsedDiscounts.Count - 1) - { - , - } - } -
    -
    - } -
    -
    - @Html.NopLabelFor(model => model.Profit) -
    -
    - @Html.NopDisplayFor(model => model.Profit) -
    -
    -
    -
    -
    - @Html.NopLabelFor(model => model.OrderSubtotalInclTaxValue) -
    -
    -
    - @T("Admin.Orders.Fields.Edit.InclTax") - @Html.NopEditorFor(model => model.OrderSubtotalInclTaxValue) -
    -
    - @T("Admin.Orders.Fields.Edit.ExclTax") - @Html.NopEditorFor(model => model.OrderSubtotalExclTaxValue) -
    -
    -
    -
    -
    - @Html.NopLabelFor(model => model.OrderSubTotalDiscountInclTaxValue) -
    -
    -
    - @T("Admin.Orders.Fields.Edit.InclTax") - @Html.NopEditorFor(model => model.OrderSubTotalDiscountInclTaxValue) -
    -
    - @T("Admin.Orders.Fields.Edit.ExclTax") - @Html.NopEditorFor(model => model.OrderSubTotalDiscountExclTaxValue) -
    -
    -
    -
    -
    - @Html.NopLabelFor(model => model.OrderShippingInclTaxValue) -
    -
    -
    - @T("Admin.Orders.Fields.Edit.InclTax") - @Html.NopEditorFor(model => model.OrderShippingInclTaxValue) -
    -
    - @T("Admin.Orders.Fields.Edit.ExclTax") - @Html.NopEditorFor(model => model.OrderShippingExclTaxValue) -
    -
    -
    -
    -
    - @Html.NopLabelFor(model => model.PaymentMethodAdditionalFeeInclTaxValue) -
    -
    -
    - @T("Admin.Orders.Fields.Edit.InclTax") - @Html.NopEditorFor(model => model.PaymentMethodAdditionalFeeInclTaxValue) -
    -
    - @T("Admin.Orders.Fields.Edit.ExclTax") - @Html.NopEditorFor(model => model.PaymentMethodAdditionalFeeExclTaxValue) -
    -
    -
    -
    -
    - @Html.NopLabelFor(model => model.TaxRatesValue) -
    -
    - @Html.NopEditorFor(model => model.TaxRatesValue) -
    -
    -
    -
    - @Html.NopLabelFor(model => model.TaxValue) -
    -
    - @Html.NopEditorFor(model => model.TaxValue) -
    -
    -
    -
    - @Html.NopLabelFor(model => model.OrderTotalDiscountValue) -
    -
    - @Html.NopEditorFor(model => model.OrderTotalDiscountValue) -
    -
    -
    -
    - @Html.NopLabelFor(model => model.OrderAmountValue) -
    -
    - @Html.NopEditorFor(model => model.OrderAmountValue) -
    -
    -
    -
    - @Html.NopLabelFor(model => model.OrderAmountInclValue) -
    -
    - @Html.NopEditorFor(model => model.OrderAmountInclValue) -
    -
    -
    -
    - @Html.NopLabelFor(model => model.OrderTotalValue) -
    -
    - @Html.NopEditorFor(model => model.OrderTotalValue) -
    -
    -
    -
    -
    - - - - @Html.ActionConfirmation("btnSaveOrderTotals") - - -
    -
    - @if (Model.AllowStoringCreditCardNumber) - { -
    -
    - @Html.NopLabelFor(model => model.CardType) -
    -
    -
    - @Html.NopDisplayFor(model => model.CardType) -
    - @Html.NopEditorFor(model => model.CardType) -
    -
    - } - @if (Model.AllowStoringCreditCardNumber) - { -
    -
    - @Html.NopLabelFor(model => model.CardName) -
    -
    -
    - @Html.NopDisplayFor(model => model.CardName) -
    - @Html.NopEditorFor(model => model.CardName) -
    -
    - } - @if (Model.AllowStoringCreditCardNumber || !String.IsNullOrEmpty(Model.CardNumber)) - { -
    -
    - @Html.NopLabelFor(model => model.CardNumber) -
    -
    -
    - @Html.NopDisplayFor(model => model.CardNumber) -
    - @Html.NopEditorFor(model => model.CardNumber) -
    -
    - } - @if (Model.AllowStoringCreditCardNumber) - { -
    -
    - @Html.NopLabelFor(model => model.CardCvv2) -
    -
    -
    - @Html.NopDisplayFor(model => model.CardCvv2) -
    - @Html.NopEditorFor(model => model.CardCvv2) -
    -
    - } - @if (Model.AllowStoringCreditCardNumber) - { -
    -
    - @Html.NopLabelFor(model => model.CardExpirationMonth) -
    -
    -
    - @Html.NopDisplayFor(model => model.CardExpirationMonth) -
    - @Html.NopEditorFor(model => model.CardExpirationMonth) -
    -
    - } - @if (Model.AllowStoringCreditCardNumber) - { -
    -
    - @Html.NopLabelFor(model => model.CardExpirationYear) -
    -
    -
    - @Html.NopDisplayFor(model => model.CardExpirationYear) -
    - @Html.NopEditorFor(model => model.CardExpirationYear) -
    -
    - } - @if (Model.AllowStoringCreditCardNumber) - { -
    -
    - - - - @Html.ActionConfirmation("btnSaveCC") - - -
    -
    - } - @if (!String.IsNullOrEmpty(Model.AuthorizationTransactionId)) - { -
    -
    - @Html.NopLabelFor(model => model.AuthorizationTransactionId) -
    -
    - @Html.NopDisplayFor(model => model.AuthorizationTransactionId) -
    -
    - } - @if (!String.IsNullOrEmpty(Model.CaptureTransactionId)) - { -
    -
    - @Html.NopLabelFor(model => model.CaptureTransactionId) -
    -
    - @Html.NopDisplayFor(model => model.CaptureTransactionId) -
    -
    - } - @if (!String.IsNullOrEmpty(Model.SubscriptionTransactionId)) - { -
    -
    - @Html.NopLabelFor(model => model.SubscriptionTransactionId) -
    -
    - @Html.NopDisplayFor(model => model.SubscriptionTransactionId) -
    -
    - } -
    -
    - @Html.NopLabelFor(model => model.PaymentMethod) -
    -
    - @Html.NopDisplayFor(model => model.PaymentMethod) -
    -
    -
    -
    - } -
    -
    -
    -
    - @Html.NopLabelFor(model => model.PaymentStatus) -
    -
    - @Html.NopDisplayFor(model => model.PaymentStatus) -
    -
    - @if (Model.CanCapture || Model.CanMarkOrderAsPaid || Model.CanRefund || Model.CanRefundOffline || - Model.CanPartiallyRefund || Model.CanPartiallyRefundOffline || Model.CanVoid || Model.CanVoidOffline) - { -
    -
    - @if (!Model.IsLoggedInAsVendor) - { - if (Model.CanCapture) - { - -   - @Html.ActionConfirmation("captureorder") - } - if (Model.CanMarkOrderAsPaid) - { - -   - @Html.ActionConfirmation("markorderaspaid") - } - if (Model.CanRefund) - { - -   - @Html.ActionConfirmation("refundorder") - } - if (Model.CanRefundOffline) - { - -   - @Html.ActionConfirmation("refundorderoffline") - } - if (Model.CanPartiallyRefund) - { - -   - } - if (Model.CanPartiallyRefundOffline) - { - -   - } - if (Model.CanVoid) - { - -   - @Html.ActionConfirmation("voidorder") - } - if (Model.CanVoidOffline) - { - -   - @Html.ActionConfirmation("voidorderoffline") - } - } -
    -
    - } - @if (Model.CustomValues != null && Model.CustomValues.Count > 0) - { -
    -
    - @Html.NopLabelFor(model => model.CustomValues) -
    -
    -
    - @foreach (var item in Model.CustomValues) - { -
      -
    • - @item.Key: @(item.Value != null ? item.Value.ToString() : "") -
    • -
    - } -
    -
    -
    - } -
    -
    - @Html.NopLabelFor(model => model.CreatedOn) -
    -
    - @Html.NopDisplayFor(model => model.CreatedOn) -
    -
    -
    -
    +@model OrderModel + +@using Nop.Core.Domain.Tax; +@using Nop.Core.Domain.Orders; +@using Nop.Services +@if (!Model.IsLoggedInAsVendor) +{ + +} +
    +
    +
    + @if (!Model.IsLoggedInAsVendor) + { +
    +
    + @Html.NopLabelFor(model => model.OrderStatus) +
    +
    +
    +
    + + @Html.NopDisplayFor(model => model.OrderStatus) + +
    +
    + @if (Model.CanCancelOrder) + { + + @Html.ActionConfirmation("cancelorder") + } + +
    + @Html.NopDropDownListFor(model => model.OrderStatusId, ((OrderStatus)Model.OrderStatusId).ToSelectList()) + + @Html.ActionConfirmation("btnSaveOrderStatus") + +
    + @T("Admin.Orders.Fields.OrderStatus.Change.ForAdvancedUsers") +
    +
    +
    +
    +
    +
    + } + else + { + //vendors should see whether or order is cancelled + if (Model.OrderStatusId == (int)OrderStatus.Cancelled) + { +
    +
    +   +
    +
    + + @T("Admin.Orders.Fields.OrderStatus.CancelledNotification") + +
    +
    + } + } +
    +
    + @Html.NopLabelFor(model => model.CustomOrderNumber) +
    +
    + @Html.NopDisplayFor(model => model.CustomOrderNumber) +
    +
    + @if (!Model.IsLoggedInAsVendor) + { +
    +
    + @Html.NopLabelFor(model => model.OrderGuid) +
    +
    + @Html.NopDisplayFor(model => model.OrderGuid) +
    +
    + } + @if (!Model.IsLoggedInAsVendor) + { +
    +
    + @Html.NopLabelFor(model => model.InvoiceId) +
    +
    +
    + @Html.NopDisplayFor(model => model.InvoiceId) +
    +
    + @Html.NopEditorFor(model => model.InvoiceId) +
    +
    +
    + } + @if (!Model.IsLoggedInAsVendor) + { +
    +
    + @Html.NopLabelFor(model => model.InvoiceDateUtc) +
    +
    +
    + @Html.NopDisplayFor(model => model.InvoiceDateUtc) +
    +
    + @Html.NopEditorFor(model => model.InvoiceDateUtc) +
    +
    +
    + } + @if (!Model.IsLoggedInAsVendor) + { +
    +
    + + + + @Html.ActionConfirmation("btnSaveIV") + + +
    +
    + } +
    +
    + @Html.NopLabelFor(model => model.StoreName) +
    +
    + @Html.NopDisplayFor(model => model.StoreName) +
    +
    +
    +
    + + @if (!Model.IsLoggedInAsVendor) + { +
    +
    +
    +
    + @Html.NopLabelFor(model => model.CustomerId) +
    +
    + @Html.ActionLink(Model.CustomerInfo, "Edit", "Customer", new { id = Model.CustomerId }, new { }) +
    +
    +
    +
    + @Html.NopLabelFor(model => model.CustomerIp) +
    +
    + @Html.NopDisplayFor(model => model.CustomerIp) +
    +
    + @if (Model.RecurringPaymentId > 0) + { +
    +
    + @Html.NopLabelFor(model => model.RecurringPaymentId) +
    + +
    + } + @if (!String.IsNullOrEmpty(Model.VatNumber)) + { +
    +
    + @Html.NopLabelFor(model => model.VatNumber) +
    +
    + @Html.NopDisplayFor(model => model.VatNumber) +
    +
    + } + @if (Model.AffiliateId > 0) + { +
    +
    + @Html.NopLabelFor(model => model.AffiliateId) +
    + +
    + } +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + @if (Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.IncludingTax) + { +
    +
    + @Html.DisplayFor(model => model.OrderSubtotalInclTax) @*@T("Admin.Orders.Fields.OrderSubtotalInclTax")*@ +
    +
    + } + @if (Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.ExcludingTax) + { +
    +
    + @Html.DisplayFor(model => model.OrderSubtotalExclTax) @*@T("Admin.Orders.Fields.OrderSubtotalExclTax")*@ +
    +
    + } +
    +
    +
    + @if (((Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.IncludingTax) && !String.IsNullOrEmpty(Model.OrderSubTotalDiscountInclTax)) || + ((Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.ExcludingTax) && !String.IsNullOrEmpty(Model.OrderSubTotalDiscountExclTax))) + { +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + @if ((Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.IncludingTax) && !String.IsNullOrEmpty(Model.OrderSubTotalDiscountInclTax)) + { +
    +
    + @Html.DisplayFor(model => model.OrderSubTotalDiscountInclTax) @*@T("Admin.Orders.Fields.OrderSubTotalDiscountInclTax")*@ +
    +
    + } + @if ((Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.ExcludingTax) && !String.IsNullOrEmpty(Model.OrderSubTotalDiscountExclTax)) + { +
    +
    + @Html.DisplayFor(model => model.OrderSubTotalDiscountExclTax) @*@T("Admin.Orders.Fields.OrderSubTotalDiscountExclTax")*@ +
    +
    + } +
    +
    +
    + } +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + @if (Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.IncludingTax) + { +
    +
    + @Html.DisplayFor(model => model.OrderShippingInclTax) @*@T("Admin.Orders.Fields.OrderShippingInclTax")*@ +
    +
    + } + @if (Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.ExcludingTax) + { +
    +
    + @Html.DisplayFor(model => model.OrderShippingExclTax) @*@T("Admin.Orders.Fields.OrderShippingExclTax")*@ +
    +
    + } + @if (!string.IsNullOrEmpty(Model.OrderShippingNonTaxable)) + { +
    +
    + @Html.DisplayFor(model => model.OrderShippingNonTaxable) @*@T("Admin.Orders.Fields.OrderShippingNonTaxable")*@ +
    +
    + } +
    +
    +
    + @if (((Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.IncludingTax) && !String.IsNullOrEmpty(Model.PaymentMethodAdditionalFeeInclTax)) || + ((Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.ExcludingTax) && !String.IsNullOrEmpty(Model.PaymentMethodAdditionalFeeExclTax)) || + (!String.IsNullOrEmpty(Model.PaymentMethodAdditionalFeeNonTaxable))) + { +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + @if (Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.IncludingTax) + { +
    +
    + @Html.DisplayFor(model => model.PaymentMethodAdditionalFeeInclTax) @*@T("Admin.Orders.Fields.PaymentMethodAdditionalFeeInclTax")*@ +
    +
    + } + @if (Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.ExcludingTax) + { +
    +
    + @Html.DisplayFor(model => model.PaymentMethodAdditionalFeeExclTax) @*@T("Admin.Orders.Fields.PaymentMethodAdditionalFeeExclTax")*@ +
    +
    + } + @if (!String.IsNullOrEmpty(Model.PaymentMethodAdditionalFeeNonTaxable)) + { +
    +
    + @Html.DisplayFor(model => model.PaymentMethodAdditionalFeeNonTaxable) @*@T("Admin.Orders.Fields.PaymentMethodAdditionalFeeNonTaxable")*@ +
    +
    + } +
    +
    +
    + } + @if (Model.DisplayTaxRates) + { + foreach (var tr in Model.TaxRates) + { +
    +
    + @Html.NopLabelFor(model => model.Tax) @tr.Rate% +
    +
    + @Html.NopDisplayFor(model => tr.TaxAmount) +
    +
    + } + } + @if (Model.DisplayTax) + { +
    +
    + @Html.NopLabelFor(model => model.Tax) +
    +
    + @Html.NopDisplayFor(model => model.Tax) +
    +
    + } + @if (!String.IsNullOrEmpty(Model.OrderTotalDiscount)) + { +
    + @if (Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.ExcludingTax) + { +
    + @Html.NopLabelFor(model => model.OrderTotalDiscount) +
    +
    + @Html.NopDisplayFor(model => model.OrderTotalDiscount) +
    + } + @if (Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.IncludingTax) + { +
    + @Html.NopLabelFor(model => model.OrderTotalDiscountIncl) +
    +
    + @Html.NopDisplayFor(model => model.OrderTotalDiscountIncl) +
    + } +
    + } + @if (!String.IsNullOrEmpty(Model.EarnedRewardPointsBaseAmountExcl)) + { +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + @if (Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.IncludingTax) + { +
    +
    + @Html.DisplayFor(model => model.EarnedRewardPointsBaseAmountIncl) @*@T("Admin.Orders.Fields.EarnedRewardPointsBaseAmountIncl")*@ +
    +
    + } + @if (Model.AllowCustomersToSelectTaxDisplayType || Model.TaxDisplayType == TaxDisplayType.ExcludingTax) + { +
    +
    + @Html.DisplayFor(model => model.EarnedRewardPointsBaseAmountExcl) @*@T("Admin.Orders.Fields.EarnedRewardPointsBaseAmountExcl")*@ +
    +
    + } +
    +
    +
    + } +
    +
    + @Html.NopLabelFor(model => model.OrderAmount) +
    +
    + @Html.NopDisplayFor(model => model.OrderAmount) +
    +
    +
    +
    + @Html.NopLabelFor(model => model.OrderAmountIncl) +
    +
    + @Html.NopDisplayFor(model => model.OrderAmountIncl) +
    +
    + @foreach (var gc in Model.GiftCards) + { +
    +
    + @Html.NopLabelFor(model => model.GiftCards[0].CouponCode) + (@(gc.CouponCode)) +
    +
    + @Html.NopDisplay(gc.Amount) +
    +
    + } + @if (Model.RedeemedRewardPoints > 0) + { +
    +
    + @Html.NopLabelFor(model => model.RedeemedRewardPoints) +
    +
    + @Html.NopDisplay(Model.RedeemedRewardPoints + " " + @T("Admin.Orders.Fields.RedeemedRewardPoints.Points").Text + "/" + Model.RedeemedRewardPointsAmount) +
    +
    + } + @if (Model.RedeemedRewardPointsPurchased > 0) + { +
    +
    + @Html.NopLabelFor(model => model.RedeemedRewardPointsPurchased) +
    +
    + @Html.NopDisplay(Model.RedeemedRewardPointsPurchased + " " + @T("Admin.Orders.Fields.RedeemedRewardPoints.Points").Text + "/" + Model.RedeemedRewardPointsAmountPurchased) +
    +
    + } +
    +
    + @Html.NopLabelFor(model => model.OrderTotal) +
    +
    + @Html.NopDisplayFor(model => model.OrderTotal) +
    +
    + @if (!String.IsNullOrEmpty(Model.RefundedAmount)) + { +
    +
    + @Html.NopLabelFor(model => model.RefundedAmount) +
    +
    + @Html.NopDisplayFor(model => model.RefundedAmount) +
    +
    + } + @if (Model.UsedDiscounts.Count > 0) + { +
    +
    + @Html.NopLabelFor(model => model.UsedDiscounts) +
    +
    + @for (int i = 0; i < Model.UsedDiscounts.Count; i++) + { + var discount = Model.UsedDiscounts[i]; + @discount.DiscountName + if (i != Model.UsedDiscounts.Count - 1) + { + , + } + } +
    +
    + } +
    +
    + @Html.NopLabelFor(model => model.Profit) +
    +
    + @Html.NopDisplayFor(model => model.Profit) +
    +
    +
    +
    +
    + @Html.NopLabelFor(model => model.OrderSubtotalInclTaxValue) +
    +
    +
    + @T("Admin.Orders.Fields.Edit.InclTax") + @Html.NopEditorFor(model => model.OrderSubtotalInclTaxValue) +
    +
    + @T("Admin.Orders.Fields.Edit.ExclTax") + @Html.NopEditorFor(model => model.OrderSubtotalExclTaxValue) +
    +
    +
    +
    +
    + @Html.NopLabelFor(model => model.OrderSubTotalDiscountInclTaxValue) +
    +
    +
    + @T("Admin.Orders.Fields.Edit.InclTax") + @Html.NopEditorFor(model => model.OrderSubTotalDiscountInclTaxValue) +
    +
    + @T("Admin.Orders.Fields.Edit.ExclTax") + @Html.NopEditorFor(model => model.OrderSubTotalDiscountExclTaxValue) +
    +
    +
    +
    +
    + @Html.NopLabelFor(model => model.OrderShippingInclTaxValue) +
    +
    +
    + @T("Admin.Orders.Fields.Edit.InclTax") + @Html.NopEditorFor(model => model.OrderShippingInclTaxValue) +
    +
    + @T("Admin.Orders.Fields.Edit.ExclTax") + @Html.NopEditorFor(model => model.OrderShippingExclTaxValue) +
    +
    + @T("Admin.Orders.Fields.Edit.NonTaxable") + @Html.NopEditorFor(model => model.OrderShippingNonTaxableValue) +
    +
    +
    +
    +
    + @Html.NopLabelFor(model => model.PaymentMethodAdditionalFeeInclTaxValue) +
    +
    +
    + @T("Admin.Orders.Fields.Edit.InclTax") + @Html.NopEditorFor(model => model.PaymentMethodAdditionalFeeInclTaxValue) +
    +
    + @T("Admin.Orders.Fields.Edit.ExclTax") + @Html.NopEditorFor(model => model.PaymentMethodAdditionalFeeExclTaxValue) +
    +
    + @T("Admin.Orders.Fields.Edit.NonTaxable") + @Html.NopEditorFor(model => model.PaymentMethodAdditionalFeeNonTaxableValue) +
    +
    +
    +
    +
    + @Html.NopLabelFor(model => model.TaxRatesValue) +
    +
    + @Html.NopEditorFor(model => model.TaxRatesValue) +
    +
    +
    +
    + @Html.NopLabelFor(model => model.TaxValue) +
    +
    + @Html.NopEditorFor(model => model.TaxValue) +
    +
    +
    +
    + @Html.NopLabelFor(model => model.OrderTotalDiscountValue) +
    +
    +
    + @T("Admin.Orders.Fields.Edit.InclTax") + @Html.NopEditorFor(model => model.OrderTotalDiscountInclValue) +
    +
    + @T("Admin.Orders.Fields.Edit.ExclTax") + @Html.NopEditorFor(model => model.OrderTotalDiscountValue) +
    +
    +
    +
    +
    + @Html.NopLabelFor(model => model.EarnedRewardPointsBaseAmountExclValue) +
    +
    +
    + @T("Admin.Orders.Fields.Edit.InclTax") + @Html.NopEditorFor(model => model.EarnedRewardPointsBaseAmountInclValue) +
    +
    + @T("Admin.Orders.Fields.Edit.ExclTax") + @Html.NopEditorFor(model => model.EarnedRewardPointsBaseAmountExclValue) +
    +
    +
    +
    +
    + @Html.NopLabelFor(model => model.OrderAmountValue) +
    +
    + @Html.NopEditorFor(model => model.OrderAmountValue) +
    +
    +
    +
    + @Html.NopLabelFor(model => model.OrderAmountInclValue) +
    +
    + @Html.NopEditorFor(model => model.OrderAmountInclValue) +
    +
    +
    +
    + @Html.NopLabelFor(model => model.OrderTotalValue) +
    +
    + @Html.NopEditorFor(model => model.OrderTotalValue) +
    +
    +
    +
    +
    + + + + @Html.ActionConfirmation("btnSaveOrderTotals") + + +
    +
    + @if (Model.AllowStoringCreditCardNumber) + { +
    +
    + @Html.NopLabelFor(model => model.CardType) +
    +
    +
    + @Html.NopDisplayFor(model => model.CardType) +
    + @Html.NopEditorFor(model => model.CardType) +
    +
    + } + @if (Model.AllowStoringCreditCardNumber) + { +
    +
    + @Html.NopLabelFor(model => model.CardName) +
    +
    +
    + @Html.NopDisplayFor(model => model.CardName) +
    + @Html.NopEditorFor(model => model.CardName) +
    +
    + } + @if (Model.AllowStoringCreditCardNumber || !String.IsNullOrEmpty(Model.CardNumber)) + { +
    +
    + @Html.NopLabelFor(model => model.CardNumber) +
    +
    +
    + @Html.NopDisplayFor(model => model.CardNumber) +
    + @Html.NopEditorFor(model => model.CardNumber) +
    +
    + } + @if (Model.AllowStoringCreditCardNumber) + { +
    +
    + @Html.NopLabelFor(model => model.CardCvv2) +
    +
    +
    + @Html.NopDisplayFor(model => model.CardCvv2) +
    + @Html.NopEditorFor(model => model.CardCvv2) +
    +
    + } + @if (Model.AllowStoringCreditCardNumber) + { +
    +
    + @Html.NopLabelFor(model => model.CardExpirationMonth) +
    +
    +
    + @Html.NopDisplayFor(model => model.CardExpirationMonth) +
    + @Html.NopEditorFor(model => model.CardExpirationMonth) +
    +
    + } + @if (Model.AllowStoringCreditCardNumber) + { +
    +
    + @Html.NopLabelFor(model => model.CardExpirationYear) +
    +
    +
    + @Html.NopDisplayFor(model => model.CardExpirationYear) +
    + @Html.NopEditorFor(model => model.CardExpirationYear) +
    +
    + } + @if (Model.AllowStoringCreditCardNumber) + { +
    +
    + + + + @Html.ActionConfirmation("btnSaveCC") + + +
    +
    + } + @if (!String.IsNullOrEmpty(Model.AuthorizationTransactionId)) + { +
    +
    + @Html.NopLabelFor(model => model.AuthorizationTransactionId) +
    +
    + @Html.NopDisplayFor(model => model.AuthorizationTransactionId) +
    +
    + } + @if (!String.IsNullOrEmpty(Model.CaptureTransactionId)) + { +
    +
    + @Html.NopLabelFor(model => model.CaptureTransactionId) +
    +
    + @Html.NopDisplayFor(model => model.CaptureTransactionId) +
    +
    + } + @if (!String.IsNullOrEmpty(Model.SubscriptionTransactionId)) + { +
    +
    + @Html.NopLabelFor(model => model.SubscriptionTransactionId) +
    +
    + @Html.NopDisplayFor(model => model.SubscriptionTransactionId) +
    +
    + } +
    +
    + @Html.NopLabelFor(model => model.PaymentMethod) +
    +
    + @Html.NopDisplayFor(model => model.PaymentMethod) +
    +
    +
    +
    + } +
    +
    +
    +
    + @Html.NopLabelFor(model => model.PaymentStatus) +
    +
    + @Html.NopDisplayFor(model => model.PaymentStatus) +
    +
    + @if (Model.CanCapture || Model.CanMarkOrderAsPaid || Model.CanRefund || Model.CanRefundOffline || + Model.CanPartiallyRefund || Model.CanPartiallyRefundOffline || Model.CanVoid || Model.CanVoidOffline) + { +
    +
    + @if (!Model.IsLoggedInAsVendor) + { + if (Model.CanCapture) + { + +   + @Html.ActionConfirmation("captureorder") + } + if (Model.CanMarkOrderAsPaid) + { + +   + @Html.ActionConfirmation("markorderaspaid") + } + if (Model.CanRefund) + { + +   + @Html.ActionConfirmation("refundorder") + } + if (Model.CanRefundOffline) + { + +   + @Html.ActionConfirmation("refundorderoffline") + } + if (Model.CanPartiallyRefund) + { + +   + } + if (Model.CanPartiallyRefundOffline) + { + +   + } + if (Model.CanVoid) + { + +   + @Html.ActionConfirmation("voidorder") + } + if (Model.CanVoidOffline) + { + +   + @Html.ActionConfirmation("voidorderoffline") + } + } +
    +
    + } + @if (Model.CustomValues != null && Model.CustomValues.Count > 0) + { +
    +
    + @Html.NopLabelFor(model => model.CustomValues) +
    +
    +
    + @foreach (var item in Model.CustomValues) + { +
      +
    • + @item.Key: @(item.Value != null ? item.Value.ToString() : "") +
    • +
    + } +
    +
    +
    + } +
    +
    + @Html.NopLabelFor(model => model.CreatedOn) +
    +
    + @Html.NopDisplayFor(model => model.CreatedOn) +
    +
    +
    +
    \ No newline at end of file diff --git a/src/Presentation/Nop.Web/Administration/Views/Order/_OrderDetails.Products.cshtml b/src/Presentation/Nop.Web/Administration/Views/Order/_OrderDetails.Products.cshtml index 83ca5a0ba76..dd0de1a5e97 100644 --- a/src/Presentation/Nop.Web/Administration/Views/Order/_OrderDetails.Products.cshtml +++ b/src/Presentation/Nop.Web/Administration/Views/Order/_OrderDetails.Products.cshtml @@ -1,409 +1,409 @@ -@model OrderModel -@using Nop.Core.Domain.Tax; -@using Nop.Core.Domain.Catalog; - -
    -
    -
    -
    -
    - @foreach (var item in Model.Items) - { - - - - } - - - - - @if (Model.HasDownloadableProducts) - { - - } - - - - - @if (!Model.IsLoggedInAsVendor) - { - - } - - - - - @if (Model.HasDownloadableProducts) - { - - } - - - - - - @if (!Model.IsLoggedInAsVendor) - { - - } - - - - @foreach (var item in Model.Items) - { - - - - - @if (Model.HasDownloadableProducts) - { - - } - - - - - - @if (!Model.IsLoggedInAsVendor) - { - - } - - } - -
    - @T("Admin.Orders.Products.Picture") - - @T("Admin.Orders.Products.ProductName") - - @T("Admin.Orders.Products.Download") - - @T("Admin.Orders.Products.Price") - - @T("Admin.Orders.Products.Quantity") - - @T("Admin.Orders.Products.Discount") - - @T("Admin.Orders.Products.Total") - - @T("Order.TaxRateLine.VatRate") - - @T("Admin.Common.Edit") -
    - - - @item.ProductName - @if (!String.IsNullOrEmpty(item.AttributeInfo)) - { -

    - @Html.Raw(item.AttributeInfo) -

    - } - @if (!String.IsNullOrEmpty(item.RecurringInfo)) - { -

    - @Html.Raw(item.RecurringInfo) -

    - } - @if (!String.IsNullOrEmpty(item.RentalInfo)) - { -

    - @Html.Raw(item.RentalInfo) -

    - } - @if (!String.IsNullOrEmpty(item.Sku)) - { -

    - @T("Admin.Orders.Products.SKU"): - @item.Sku -

    - } - @if (!String.IsNullOrEmpty(item.VendorName)) - { -

    - @T("Admin.Orders.Products.Vendor"): - @item.VendorName -

    - } - @if (item.ReturnRequests.Count > 0) - { -

    - @T("Admin.Orders.Products.ReturnRequests"): - @for (int i = 0; i < item.ReturnRequests.Count; i++) - { - @item.ReturnRequests[i].CustomNumber - if (i != item.ReturnRequests.Count - 1) - { - , - } - } -

    - } - @if (item.PurchasedGiftCardIds.Count > 0) - { -

    - @T("Admin.Orders.Products.GiftCards"): - @for (int i = 0; i < item.PurchasedGiftCardIds.Count; i++) - { - @item.PurchasedGiftCardIds[i] - if (i != item.PurchasedGiftCardIds.Count - 1) - { - , - } - } -

    - } -
    - @if (item.IsDownload) - { -
    - @string.Format(T("Admin.Orders.Products.Download.DownloadCount").Text, item.DownloadCount) - -
    -
    - if (item.DownloadActivationType == DownloadActivationType.Manually) - { -
    - -
    -
    - } -
    -
    - - @T("Admin.Orders.Products.License") - -
    - @if (item.LicenseDownloadGuid != Guid.Empty) - { - - } - -
    - } -
    - @if (Model.AllowCustomersToSelectTaxDisplayType) - { -
    @item.UnitPriceInclTax
    -
    @item.UnitPriceExclTax
    - } - else - { - switch (Model.TaxDisplayType) - { - case TaxDisplayType.ExcludingTax: - { - @item.UnitPriceExclTax - } - break; - case TaxDisplayType.IncludingTax: - { - @item.UnitPriceInclTax - } - break; - default: - break; - } - } -
    -
    -
    - @T("Admin.Orders.Products.Edit.InclTax") -
    -
    - -
    -
    -
    -
    - @T("Admin.Orders.Products.Edit.ExclTax") -
    -
    - -
    -
    -
    -
    -
    @item.Quantity
    -
    -
    -
    - -
    -
    -
    -
    - @if (Model.AllowCustomersToSelectTaxDisplayType) - { -
    @item.DiscountInclTax
    -
    @item.DiscountExclTax
    - } - else - { - switch (Model.TaxDisplayType) - { - case TaxDisplayType.ExcludingTax: - { - @item.DiscountExclTax - } - break; - case TaxDisplayType.IncludingTax: - { - @item.DiscountInclTax - } - break; - default: - break; - } - } -
    -
    -
    - @T("Admin.Orders.Products.Edit.InclTax") -
    -
    - -
    -
    -
    -
    - @T("Admin.Orders.Products.Edit.ExclTax") -
    -
    - -
    -
    -
    -
    - @if (Model.AllowCustomersToSelectTaxDisplayType) - { -
    @item.SubTotalInclTax
    -
    @item.SubTotalExclTax
    - } - else - { - switch (Model.TaxDisplayType) - { - case TaxDisplayType.ExcludingTax: - { - @item.SubTotalExclTax - } - break; - case TaxDisplayType.IncludingTax: - { - @item.SubTotalInclTax - } - break; - default: - break; - } - } -
    -
    -
    - @T("Admin.Orders.Products.Edit.InclTax") -
    -
    - -
    -
    -
    -
    - @T("Admin.Orders.Products.Edit.ExclTax") -
    -
    - -
    -
    -
    -
    -
    @item.VatRate.ToString("G29")
    -
    -
    -
    - -
    -
    -
    -
    - - - - @Html.ActionConfirmation("btnDeleteOrderItem" + item.Id) - - - @Html.ActionConfirmation("btnSaveOrderItem" + item.Id) - - -
    -
    -
    - @if (!String.IsNullOrEmpty(Model.CheckoutAttributeInfo) && !Model.IsLoggedInAsVendor) - { -
    -
    - @Html.Raw(Model.CheckoutAttributeInfo) -
    -
    - } - @if (!Model.IsLoggedInAsVendor) - { -
    -
    - -
    -
    - } -
    -
    -
    +@model OrderModel +@using Nop.Core.Domain.Tax; +@using Nop.Core.Domain.Catalog; + +
    +
    +
    +
    +
    + @foreach (var item in Model.Items) + { + + + + } + + + + + @if (Model.HasDownloadableProducts) + { + + } + + + + + @if (!Model.IsLoggedInAsVendor) + { + + } + + + + + @if (Model.HasDownloadableProducts) + { + + } + + + + + + @if (!Model.IsLoggedInAsVendor) + { + + } + + + + @foreach (var item in Model.Items) + { + + + + + @if (Model.HasDownloadableProducts) + { + + } + + + + + + @if (!Model.IsLoggedInAsVendor) + { + + } + + } + +
    + @T("Admin.Orders.Products.Picture") + + @T("Admin.Orders.Products.ProductName") + + @T("Admin.Orders.Products.Download") + + @T("Admin.Orders.Products.Price") + + @T("Admin.Orders.Products.Quantity") + + @T("Admin.Orders.Products.Discount") + + @T("Admin.Orders.Products.Total") + + @T("Order.TaxRateLine.TaxRate") + + @T("Admin.Common.Edit") +
    + + + @item.ProductName + @if (!String.IsNullOrEmpty(item.AttributeInfo)) + { +

    + @Html.Raw(item.AttributeInfo) +

    + } + @if (!String.IsNullOrEmpty(item.RecurringInfo)) + { +

    + @Html.Raw(item.RecurringInfo) +

    + } + @if (!String.IsNullOrEmpty(item.RentalInfo)) + { +

    + @Html.Raw(item.RentalInfo) +

    + } + @if (!String.IsNullOrEmpty(item.Sku)) + { +

    + @T("Admin.Orders.Products.SKU"): + @item.Sku +

    + } + @if (!String.IsNullOrEmpty(item.VendorName)) + { +

    + @T("Admin.Orders.Products.Vendor"): + @item.VendorName +

    + } + @if (item.ReturnRequests.Count > 0) + { +

    + @T("Admin.Orders.Products.ReturnRequests"): + @for (int i = 0; i < item.ReturnRequests.Count; i++) + { + @item.ReturnRequests[i].CustomNumber + if (i != item.ReturnRequests.Count - 1) + { + , + } + } +

    + } + @if (item.PurchasedGiftCardIds.Count > 0) + { +

    + @T("Admin.Orders.Products.GiftCards"): + @for (int i = 0; i < item.PurchasedGiftCardIds.Count; i++) + { + @item.PurchasedGiftCardIds[i] + if (i != item.PurchasedGiftCardIds.Count - 1) + { + , + } + } +

    + } +
    + @if (item.IsDownload) + { +
    + @string.Format(T("Admin.Orders.Products.Download.DownloadCount").Text, item.DownloadCount) + +
    +
    + if (item.DownloadActivationType == DownloadActivationType.Manually) + { +
    + +
    +
    + } +
    +
    + + @T("Admin.Orders.Products.License") + +
    + @if (item.LicenseDownloadGuid != Guid.Empty) + { + + } + +
    + } +
    + @if (Model.AllowCustomersToSelectTaxDisplayType) + { +
    @item.UnitPriceInclTax
    +
    @item.UnitPriceExclTax
    + } + else + { + switch (Model.TaxDisplayType) + { + case TaxDisplayType.ExcludingTax: + { + @item.UnitPriceExclTax + } + break; + case TaxDisplayType.IncludingTax: + { + @item.UnitPriceInclTax + } + break; + default: + break; + } + } +
    +
    +
    + @T("Admin.Orders.Products.Edit.InclTax") +
    +
    + +
    +
    +
    +
    + @T("Admin.Orders.Products.Edit.ExclTax") +
    +
    + +
    +
    +
    +
    +
    @item.Quantity
    +
    +
    +
    + +
    +
    +
    +
    + @if (Model.AllowCustomersToSelectTaxDisplayType) + { +
    @item.DiscountInclTax
    +
    @item.DiscountExclTax
    + } + else + { + switch (Model.TaxDisplayType) + { + case TaxDisplayType.ExcludingTax: + { + @item.DiscountExclTax + } + break; + case TaxDisplayType.IncludingTax: + { + @item.DiscountInclTax + } + break; + default: + break; + } + } +
    +
    +
    + @T("Admin.Orders.Products.Edit.InclTax") +
    +
    + +
    +
    +
    +
    + @T("Admin.Orders.Products.Edit.ExclTax") +
    +
    + +
    +
    +
    +
    + @if (Model.AllowCustomersToSelectTaxDisplayType) + { +
    @item.SubTotalInclTax
    +
    @item.SubTotalExclTax
    + } + else + { + switch (Model.TaxDisplayType) + { + case TaxDisplayType.ExcludingTax: + { + @item.SubTotalExclTax + } + break; + case TaxDisplayType.IncludingTax: + { + @item.SubTotalInclTax + } + break; + default: + break; + } + } +
    +
    +
    + @T("Admin.Orders.Products.Edit.InclTax") +
    +
    + +
    +
    +
    +
    + @T("Admin.Orders.Products.Edit.ExclTax") +
    +
    + +
    +
    +
    +
    +
    @item.TaxRate.ToString("G29")
    +
    +
    +
    + +
    +
    +
    +
    + + + + @Html.ActionConfirmation("btnDeleteOrderItem" + item.Id) + + + @Html.ActionConfirmation("btnSaveOrderItem" + item.Id) + + +
    +
    +
    + @if (!String.IsNullOrEmpty(Model.CheckoutAttributeInfo) && !Model.IsLoggedInAsVendor) + { +
    +
    + @Html.Raw(Model.CheckoutAttributeInfo) +
    +
    + } + @if (!Model.IsLoggedInAsVendor) + { +
    +
    + +
    +
    + } +
    +
    +
    diff --git a/src/Presentation/Nop.Web/Administration/Views/Product/_CreateOrUpdate.Info.cshtml b/src/Presentation/Nop.Web/Administration/Views/Product/_CreateOrUpdate.Info.cshtml index 05d6a661e7c..24e9eaa6076 100644 --- a/src/Presentation/Nop.Web/Administration/Views/Product/_CreateOrUpdate.Info.cshtml +++ b/src/Presentation/Nop.Web/Administration/Views/Product/_CreateOrUpdate.Info.cshtml @@ -25,6 +25,7 @@ $("#@Html.FieldIdFor(model => model.RequireOtherProducts)").click(toggleRequireOtherProducts); $("#@Html.FieldIdFor(model => model.AvailableForPreOrder)").click(togglePreOrder); $("#@Html.FieldIdFor(model => model.IsGiftCard)").click(toggleGiftCard); + $("#@Html.FieldIdFor(model => model.IsRewardPoints)").click(toggleRewardPoints); $("#@Html.FieldIdFor(model => model.CustomerEntersPrice)").click(toggleCustomerEntersPrice); $("#@Html.FieldIdFor(model => model.BasepriceEnabled)").click(toggleBasepriceEnabled); $("#@Html.FieldIdFor(model => model.IsDownload)").click(toggleDownloadableProduct); @@ -49,6 +50,7 @@ togglePreOrder(); toggleRequireOtherProducts(); toggleGiftCard(); + toggleRewardPoints(); toggleCustomerEntersPrice(); toggleBasepriceEnabled(); toggleDownloadableProduct(); @@ -79,6 +81,7 @@ $('#tier-prices').show(); $('#group-required-other-products').show(); $('#group-giftcard').show(); + $('#group-rewardpoints').show(); $('#group-downloads').show(); $('#group-recurring').show(); $('#group-rental').show(); @@ -90,6 +93,7 @@ $('#tier-prices').hide(); $('#group-required-other-products').hide(); $('#group-giftcard').hide(); + $('#group-rewardpoints').hide(); $('#group-downloads').hide(); $('#group-recurring').hide(); $('#group-rental').hide(); @@ -156,6 +160,14 @@ } } + function toggleRewardPoints() { + if ($('#@Html.FieldIdFor(model => model.IsRewardPoints)').is(':checked')) { + $('#pnlOverriddenRPExchangeRate').show(); + } else { + $('#pnlOverriddenRPExchangeRate').hide(); + } + } + function toggleCustomerEntersPrice() { if ($('#@Html.FieldIdFor(model => model.CustomerEntersPrice)').is(':checked')) { $('#pnlMinimumCustomerEnteredPrice').show(); @@ -760,6 +772,42 @@
    +
    +
    + @T("Admin.Catalog.Products.RewardPoints") +
    +
    +
    +
    + @Html.NopLabelFor(model => model.ExcludeFromRewardPoints) +
    +
    + @Html.NopEditorFor(model => model.ExcludeFromRewardPoints) + @Html.ValidationMessageFor(model => model.ExcludeFromRewardPoints) +
    +
    +
    +
    +
    +
    + @Html.NopLabelFor(model => model.IsRewardPoints) +
    +
    + @Html.NopEditorFor(model => model.IsRewardPoints) + @Html.ValidationMessageFor(model => model.IsRewardPoints) +
    +
    +
    +
    + @Html.NopLabelFor(model => model.OverriddenRPExchangeRate) +
    +
    + @Html.NopEditorFor(model => model.OverriddenRPExchangeRate) + @Html.ValidationMessageFor(model => model.OverriddenRPExchangeRate) +
    +
    +
    +
    @T("Admin.Catalog.Products.DownloadableProduct") diff --git a/src/Presentation/Nop.Web/Administration/Views/Product/_ProductEditorSettingsModal.cshtml b/src/Presentation/Nop.Web/Administration/Views/Product/_ProductEditorSettingsModal.cshtml index a84eda037f4..de062ded03f 100644 --- a/src/Presentation/Nop.Web/Administration/Views/Product/_ProductEditorSettingsModal.cshtml +++ b/src/Presentation/Nop.Web/Administration/Views/Product/_ProductEditorSettingsModal.cshtml @@ -228,6 +228,15 @@ @Html.ValidationMessageFor(model => model.ProductEditorSettingsModel.IsGiftCard)
    +
    +
    + @Html.NopLabelFor(model => model.ProductEditorSettingsModel.IsRewardPoints) +
    +
    + @Html.NopEditorFor(model => model.ProductEditorSettingsModel.IsRewardPoints) + @Html.ValidationMessageFor(model => model.ProductEditorSettingsModel.IsRewardPoints) +
    +
    @Html.NopLabelFor(model => model.ProductEditorSettingsModel.DownloadableProduct) diff --git a/src/Presentation/Nop.Web/Administration/Views/Setting/RewardPoints.cshtml b/src/Presentation/Nop.Web/Administration/Views/Setting/RewardPoints.cshtml index fb8699755fb..cf8115cc8f8 100644 --- a/src/Presentation/Nop.Web/Administration/Views/Setting/RewardPoints.cshtml +++ b/src/Presentation/Nop.Web/Administration/Views/Setting/RewardPoints.cshtml @@ -162,6 +162,66 @@ @Html.ValidationMessageFor(model => model.PageSize)
    +
    +
    + @Html.OverrideStoreCheckboxFor(model => model.EarnedRewardPointsAreTaxable_OverrideForStore, model => model.EarnedRewardPointsAreTaxable, Model.ActiveStoreScopeConfiguration) + @Html.NopLabelFor(model => model.EarnedRewardPointsAreTaxable) +
    +
    + @Html.NopEditorFor(model => model.EarnedRewardPointsAreTaxable) + @Html.ValidationMessageFor(model => model.EarnedRewardPointsAreTaxable) +
    +
    +
    +
    + @Html.OverrideStoreCheckboxFor(model => model.AwardPointsIncludeShipping_OverrideForStore, model => model.AwardPointsIncludeShipping, Model.ActiveStoreScopeConfiguration) + @Html.NopLabelFor(model => model.AwardPointsIncludeShipping) +
    +
    + @Html.NopEditorFor(model => model.AwardPointsIncludeShipping) + @Html.ValidationMessageFor(model => model.AwardPointsIncludeShipping) +
    +
    +
    +
    + @Html.OverrideStoreCheckboxFor(model => model.AwardPointsIncludePaymentMethodAdditionalFee_OverrideForStore, model => model.AwardPointsIncludePaymentMethodAdditionalFee, Model.ActiveStoreScopeConfiguration) + @Html.NopLabelFor(model => model.AwardPointsIncludePaymentMethodAdditionalFee) +
    +
    + @Html.NopEditorFor(model => model.AwardPointsIncludePaymentMethodAdditionalFee) + @Html.ValidationMessageFor(model => model.AwardPointsIncludePaymentMethodAdditionalFee) +
    +
    +
    +
    + @Html.OverrideStoreCheckboxFor(model => model.AwardPointsExcludeGiftCard_OverrideForStore, model => model.AwardPointsExcludeGiftCard, Model.ActiveStoreScopeConfiguration) + @Html.NopLabelFor(model => model.AwardPointsExcludeGiftCard) +
    +
    + @Html.NopEditorFor(model => model.AwardPointsExcludeGiftCard) + @Html.ValidationMessageFor(model => model.AwardPointsExcludeGiftCard) +
    +
    +
    +
    + @Html.OverrideStoreCheckboxFor(model => model.AwardPointsExcludePurchasedRewardPoints_OverrideForStore, model => model.AwardPointsExcludePurchasedRewardPoints, Model.ActiveStoreScopeConfiguration) + @Html.NopLabelFor(model => model.AwardPointsExcludePurchasedRewardPoints) +
    +
    + @Html.NopEditorFor(model => model.AwardPointsExcludePurchasedRewardPoints) + @Html.ValidationMessageFor(model => model.AwardPointsExcludePurchasedRewardPoints) +
    +
    +
    +
    + @Html.OverrideStoreCheckboxFor(model => model.EarnRewardPointsOnlyWhenUsingPurchasedRewardPoints_OverrideForStore, model => model.EarnRewardPointsOnlyWhenUsingPurchasedRewardPoints, Model.ActiveStoreScopeConfiguration) + @Html.NopLabelFor(model => model.EarnRewardPointsOnlyWhenUsingPurchasedRewardPoints) +
    +
    + @Html.NopEditorFor(model => model.EarnRewardPointsOnlyWhenUsingPurchasedRewardPoints) + @Html.ValidationMessageFor(model => model.EarnRewardPointsOnlyWhenUsingPurchasedRewardPoints) +
    +
    - -
    -
    - @Html.Action("StoreScopeConfiguration", "Setting") - @Html.Action("Mode", "Setting") - @Html.ValidationSummary(false) - - -
    -
    - - { - //custom tabs - var eventMessage = new AdminTabStripCreated(this.Html, "ordersettings-edit"); - EngineContext.Current.Resolve().Publish(eventMessage); - foreach (var eventBlock in eventMessage.BlocksToRender) - { - @eventBlock - } - } - - @*save selected tab name*@ - -} - -@helper TabOrderSettings() -{ - - -
    -
    -
    - @T("Admin.Configuration.Settings.Order.BlockTitle.Checkout") -
    -
    -
    -
    - @Html.OverrideStoreCheckboxFor(model => model.AnonymousCheckoutAllowed_OverrideForStore, model => model.AnonymousCheckoutAllowed, Model.ActiveStoreScopeConfiguration) - @Html.NopLabelFor(model => model.AnonymousCheckoutAllowed) -
    -
    - @Html.NopEditorFor(model => model.AnonymousCheckoutAllowed) - @Html.ValidationMessageFor(model => model.AnonymousCheckoutAllowed) -
    -
    -
    -
    - @Html.OverrideStoreCheckboxFor(model => model.OnePageCheckoutEnabled_OverrideForStore, model => model.OnePageCheckoutEnabled, Model.ActiveStoreScopeConfiguration) - @Html.NopLabelFor(model => model.OnePageCheckoutEnabled) -
    -
    - @Html.NopEditorFor(model => model.OnePageCheckoutEnabled) - @Html.ValidationMessageFor(model => model.OnePageCheckoutEnabled) -
    -
    -
    -
    - @Html.OverrideStoreCheckboxFor(model => model.OnePageCheckoutDisplayOrderTotalsOnPaymentInfoTab_OverrideForStore, model => model.OnePageCheckoutDisplayOrderTotalsOnPaymentInfoTab, Model.ActiveStoreScopeConfiguration) - @Html.NopLabelFor(model => model.OnePageCheckoutDisplayOrderTotalsOnPaymentInfoTab) -
    -
    - @Html.NopEditorFor(model => model.OnePageCheckoutDisplayOrderTotalsOnPaymentInfoTab) - @Html.ValidationMessageFor(model => model.OnePageCheckoutDisplayOrderTotalsOnPaymentInfoTab) -
    -
    -
    -
    - @Html.OverrideStoreCheckboxFor(model => model.TermsOfServiceOnShoppingCartPage_OverrideForStore, model => model.TermsOfServiceOnShoppingCartPage, Model.ActiveStoreScopeConfiguration) - @Html.NopLabelFor(model => model.TermsOfServiceOnShoppingCartPage) -
    -
    - @Html.NopEditorFor(model => model.TermsOfServiceOnShoppingCartPage) - @Html.ValidationMessageFor(model => model.TermsOfServiceOnShoppingCartPage) -
    -
    -
    -
    - @Html.OverrideStoreCheckboxFor(model => model.TermsOfServiceOnOrderConfirmPage_OverrideForStore, model => model.TermsOfServiceOnOrderConfirmPage, Model.ActiveStoreScopeConfiguration) - @Html.NopLabelFor(model => model.TermsOfServiceOnOrderConfirmPage) -
    -
    - @Html.NopEditorFor(model => model.TermsOfServiceOnOrderConfirmPage) - @Html.ValidationMessageFor(model => model.TermsOfServiceOnOrderConfirmPage) -
    -
    -
    -
    - @Html.OverrideStoreCheckboxFor(model => model.DisableBillingAddressCheckoutStep_OverrideForStore, model => model.DisableBillingAddressCheckoutStep, Model.ActiveStoreScopeConfiguration) - @Html.NopLabelFor(model => model.DisableBillingAddressCheckoutStep) -
    -
    - @Html.NopEditorFor(model => model.DisableBillingAddressCheckoutStep) - @Html.ValidationMessageFor(model => model.DisableBillingAddressCheckoutStep) -
    -
    -
    -
    - @Html.OverrideStoreCheckboxFor(model => model.DisableOrderCompletedPage_OverrideForStore, model => model.DisableOrderCompletedPage, Model.ActiveStoreScopeConfiguration) - @Html.NopLabelFor(model => model.DisableOrderCompletedPage) -
    -
    - @Html.NopEditorFor(model => model.DisableOrderCompletedPage) - @Html.ValidationMessageFor(model => model.DisableOrderCompletedPage) -
    -
    -
    -
    -
    -
    - @T("Admin.Configuration.Settings.Order.BlockTitle.OrderTotals") -
    -
    -
    -
    - @Html.OverrideStoreCheckboxFor(model => model.MinOrderSubtotalAmount_OverrideForStore, model => model.MinOrderSubtotalAmount, Model.ActiveStoreScopeConfiguration) - @Html.NopLabelFor(model => model.MinOrderSubtotalAmount) -
    -
    - @Html.NopEditorFor(model => model.MinOrderSubtotalAmount, Model.PrimaryStoreCurrencyCode) - @Html.ValidationMessageFor(model => model.MinOrderSubtotalAmount) -
    -
    -
    -
    - @Html.OverrideStoreCheckboxFor(model => model.MinOrderSubtotalAmountIncludingTax_OverrideForStore, model => model.MinOrderSubtotalAmountIncludingTax, Model.ActiveStoreScopeConfiguration) - @Html.NopLabelFor(model => model.MinOrderSubtotalAmountIncludingTax) -
    -
    - @Html.NopEditorFor(model => model.MinOrderSubtotalAmountIncludingTax) - @Html.ValidationMessageFor(model => model.MinOrderSubtotalAmountIncludingTax) -
    -
    -
    -
    - @Html.OverrideStoreCheckboxFor(model => model.MinOrderTotalAmount_OverrideForStore, model => model.MinOrderTotalAmount, Model.ActiveStoreScopeConfiguration) - @Html.NopLabelFor(model => model.MinOrderTotalAmount) -
    -
    - @Html.NopEditorFor(model => model.MinOrderTotalAmount, Model.PrimaryStoreCurrencyCode) - @Html.ValidationMessageFor(model => model.MinOrderTotalAmount) -
    -
    -
    -
    - @Html.OverrideStoreCheckboxFor(model => model.AutoUpdateOrderTotalsOnEditingOrder_OverrideForStore, model => model.AutoUpdateOrderTotalsOnEditingOrder, Model.ActiveStoreScopeConfiguration) - @Html.NopLabelFor(model => model.AutoUpdateOrderTotalsOnEditingOrder) -
    -
    - @Html.NopEditorFor(model => model.AutoUpdateOrderTotalsOnEditingOrder) - @Html.ValidationMessageFor(model => model.AutoUpdateOrderTotalsOnEditingOrder) -
    -
    -
    -
    -
    -
    - @T("Admin.Configuration.Settings.Order.BlockTitle.Common") -
    -
    - @if (Model.OrderIdent.HasValue) - { -
    -
    - @Html.NopLabelFor(model => model.OrderIdent) -
    -
    - @Html.NopEditorFor(model => model.OrderIdent) -
    -
    - } -
    -
    - @Html.OverrideStoreCheckboxFor(model => model.CustomOrderNumberMask_OverrideForStore, model => model.CustomOrderNumberMask, Model.ActiveStoreScopeConfiguration) - @Html.NopLabelFor(model => model.CustomOrderNumberMask) -
    -
    - @Html.NopEditorFor(model => model.CustomOrderNumberMask) - @Html.ValidationMessageFor(model => model.CustomOrderNumberMask) -
      -
    • - @T("Admin.Configuration.Settings.Order.CustomOrderNumberMask.Description.YYYY") -
    • -
    • - @T("Admin.Configuration.Settings.Order.CustomOrderNumberMask.Description.YY") -
    • -
    • - @T("Admin.Configuration.Settings.Order.CustomOrderNumberMask.Description.MM") -
    • -
    • - @T("Admin.Configuration.Settings.Order.CustomOrderNumberMask.Description.DD") -
    • -
    • - @T("Admin.Configuration.Settings.Order.CustomOrderNumberMask.Description.ID") -
    • -
    -
    -
    -
    -
    - @Html.OverrideStoreCheckboxFor(model => model.IsReOrderAllowed_OverrideForStore, model => model.IsReOrderAllowed, Model.ActiveStoreScopeConfiguration) - @Html.NopLabelFor(model => model.IsReOrderAllowed) -
    -
    - @Html.NopEditorFor(model => model.IsReOrderAllowed) - @Html.ValidationMessageFor(model => model.IsReOrderAllowed) -
    -
    -
    -
    - @Html.NopLabelFor(model => model.CompleteOrderWhenDelivered) -
    -
    - @Html.NopEditorFor(model => model.CompleteOrderWhenDelivered) - @Html.ValidationMessageFor(model => model.CompleteOrderWhenDelivered) -
    -
    -
    -
    - @Html.OverrideStoreCheckboxFor(model => model.ExportWithProducts_OverrideForStore, model => model.ExportWithProducts, Model.ActiveStoreScopeConfiguration) - @Html.NopLabelFor(model => model.ExportWithProducts) -
    -
    - @Html.NopEditorFor(model => model.ExportWithProducts) - @Html.ValidationMessageFor(model => model.ExportWithProducts) -
    -
    -
    -
    -
    -
    - @T("Admin.Configuration.Settings.Order.BlockTitle.PdfInvoice") -
    -
    -
    -
    - @Html.OverrideStoreCheckboxFor(model => model.AttachPdfInvoiceToOrderPlacedEmail_OverrideForStore, model => model.AttachPdfInvoiceToOrderPlacedEmail, Model.ActiveStoreScopeConfiguration) - @Html.NopLabelFor(model => model.AttachPdfInvoiceToOrderPlacedEmail) -
    -
    - @Html.NopEditorFor(model => model.AttachPdfInvoiceToOrderPlacedEmail) - @Html.ValidationMessageFor(model => model.AttachPdfInvoiceToOrderPlacedEmail) -
    -
    -
    -
    - @Html.OverrideStoreCheckboxFor(model => model.AttachPdfInvoiceToOrderPaidEmail_OverrideForStore, model => model.AttachPdfInvoiceToOrderPaidEmail, Model.ActiveStoreScopeConfiguration) - @Html.NopLabelFor(model => model.AttachPdfInvoiceToOrderPaidEmail) -
    -
    - @Html.NopEditorFor(model => model.AttachPdfInvoiceToOrderPaidEmail) - @Html.ValidationMessageFor(model => model.AttachPdfInvoiceToOrderPaidEmail) -
    -
    -
    -
    - @Html.OverrideStoreCheckboxFor(model => model.AttachPdfInvoiceToOrderCompletedEmail_OverrideForStore, model => model.AttachPdfInvoiceToOrderCompletedEmail, Model.ActiveStoreScopeConfiguration) - @Html.NopLabelFor(model => model.AttachPdfInvoiceToOrderCompletedEmail) -
    -
    - @Html.NopEditorFor(model => model.AttachPdfInvoiceToOrderCompletedEmail) - @Html.ValidationMessageFor(model => model.AttachPdfInvoiceToOrderCompletedEmail) -
    -
    -
    -
    -
    -
    - @T("Admin.Configuration.Settings.Order.BlockTitle.GiftCards") -
    -
    -
    -
    - @Html.NopLabelFor(model => model.ActivateGiftCardsAfterCompletingOrder) -
    -
    - @Html.NopEditorFor(model => model.ActivateGiftCardsAfterCompletingOrder) - @Html.ValidationMessageFor(model => model.ActivateGiftCardsAfterCompletingOrder) -
    -
    -
    -
    - @Html.NopLabelFor(model => model.DeactivateGiftCardsAfterCancellingOrder) -
    -
    - @Html.NopEditorFor(model => model.DeactivateGiftCardsAfterCancellingOrder) - @Html.ValidationMessageFor(model => model.DeactivateGiftCardsAfterCancellingOrder) -
    -
    -
    -
    - @Html.NopLabelFor(model => model.DeactivateGiftCardsAfterDeletingOrder) -
    -
    - @Html.NopEditorFor(model => model.DeactivateGiftCardsAfterDeletingOrder) - @Html.ValidationMessageFor(model => model.DeactivateGiftCardsAfterDeletingOrder) -
    -
    -
    -
    -
    -} - -@helper TabReturnRequestSettings() -{ -
    -
    -
    - @T("Admin.Configuration.Settings.Order.BlockTitle.Common") -
    -
    -
      -
    • - @T("Admin.Configuration.Settings.Order.ReturnRequestsDescription1") -
    • -
    • - @T("Admin.Configuration.Settings.Order.ReturnRequestsDescription2") -
    • -
    -
    -
    - @Html.OverrideStoreCheckboxFor(model => model.ReturnRequestsEnabled_OverrideForStore, model => model.ReturnRequestsEnabled, Model.ActiveStoreScopeConfiguration) - @Html.NopLabelFor(model => model.ReturnRequestsEnabled) -
    -
    - @Html.NopEditorFor(model => model.ReturnRequestsEnabled) - @Html.ValidationMessageFor(model => model.ReturnRequestsEnabled) -
    -
    -
    -
    - @Html.OverrideStoreCheckboxFor(model => model.ReturnRequestNumberMask_OverrideForStore, model => model.ReturnRequestNumberMask, Model.ActiveStoreScopeConfiguration) - @Html.NopLabelFor(model => model.ReturnRequestNumberMask) -
    -
    - @Html.NopEditorFor(model => model.ReturnRequestNumberMask) - @Html.ValidationMessageFor(model => model.ReturnRequestNumberMask) -
      -
    • - @T("Admin.Configuration.Settings.Order.ReturnRequestNumberMask.Description.ID") -
    • -
    • - @T("Admin.Configuration.Settings.Order.ReturnRequestNumberMask.Description.YYYY") -
    • -
    • - @T("Admin.Configuration.Settings.Order.ReturnRequestNumberMask.Description.YY") -
    • -
    • - @T("Admin.Configuration.Settings.Order.ReturnRequestNumberMask.Description.MM") -
    • -
    • - @T("Admin.Configuration.Settings.Order.ReturnRequestNumberMask.Description.DD") -
    • -
    -
    -
    -
    -
    - @Html.OverrideStoreCheckboxFor(model => model.NumberOfDaysReturnRequestAvailable_OverrideForStore, model => model.NumberOfDaysReturnRequestAvailable, Model.ActiveStoreScopeConfiguration) - @Html.NopLabelFor(model => model.NumberOfDaysReturnRequestAvailable) -
    -
    - @Html.NopEditorFor(model => model.NumberOfDaysReturnRequestAvailable) - @Html.ValidationMessageFor(model => model.NumberOfDaysReturnRequestAvailable) -
    -
    -
    -
    - @Html.OverrideStoreCheckboxFor(model => model.ReturnRequestsAllowFiles_OverrideForStore, model => model.ReturnRequestsAllowFiles, Model.ActiveStoreScopeConfiguration) - @Html.NopLabelFor(model => model.ReturnRequestsAllowFiles) -
    -
    - @Html.NopEditorFor(model => model.ReturnRequestsAllowFiles) - @Html.ValidationMessageFor(model => model.ReturnRequestsAllowFiles) -
    -
    -
    -
    - @Html.Partial("_ReturnRequestReasons") - @Html.Partial("_ReturnRequestActions") -
    +@model OrderSettingsModel + +@{ + //page title + ViewBag.Title = T("Admin.Configuration.Settings.Order").Text; + //active menu item (system name) + Html.SetActiveMenuItemSystemName("Order settings"); +} + +@using (Html.BeginForm()) +{ + @Html.AntiForgeryToken() +
    +

    + @T("Admin.Configuration.Settings.Order") +

    +
    + +
    +
    + + + +
    +
    + @Html.Action("StoreScopeConfiguration", "Setting") + @Html.Action("Mode", "Setting") + @Html.ValidationSummary(false) + + +
    +
    + + { + //custom tabs + var eventMessage = new AdminTabStripCreated(this.Html, "ordersettings-edit"); + EngineContext.Current.Resolve().Publish(eventMessage); + foreach (var eventBlock in eventMessage.BlocksToRender) + { + @eventBlock + } + } + + @*save selected tab name*@ + +} + +@helper TabOrderSettings() +{ + + +
    +
    +
    + @T("Admin.Configuration.Settings.Order.BlockTitle.Checkout") +
    +
    +
    +
    + @Html.OverrideStoreCheckboxFor(model => model.AnonymousCheckoutAllowed_OverrideForStore, model => model.AnonymousCheckoutAllowed, Model.ActiveStoreScopeConfiguration) + @Html.NopLabelFor(model => model.AnonymousCheckoutAllowed) +
    +
    + @Html.NopEditorFor(model => model.AnonymousCheckoutAllowed) + @Html.ValidationMessageFor(model => model.AnonymousCheckoutAllowed) +
    +
    +
    +
    + @Html.OverrideStoreCheckboxFor(model => model.OnePageCheckoutEnabled_OverrideForStore, model => model.OnePageCheckoutEnabled, Model.ActiveStoreScopeConfiguration) + @Html.NopLabelFor(model => model.OnePageCheckoutEnabled) +
    +
    + @Html.NopEditorFor(model => model.OnePageCheckoutEnabled) + @Html.ValidationMessageFor(model => model.OnePageCheckoutEnabled) +
    +
    +
    +
    + @Html.OverrideStoreCheckboxFor(model => model.OnePageCheckoutDisplayOrderTotalsOnPaymentInfoTab_OverrideForStore, model => model.OnePageCheckoutDisplayOrderTotalsOnPaymentInfoTab, Model.ActiveStoreScopeConfiguration) + @Html.NopLabelFor(model => model.OnePageCheckoutDisplayOrderTotalsOnPaymentInfoTab) +
    +
    + @Html.NopEditorFor(model => model.OnePageCheckoutDisplayOrderTotalsOnPaymentInfoTab) + @Html.ValidationMessageFor(model => model.OnePageCheckoutDisplayOrderTotalsOnPaymentInfoTab) +
    +
    +
    +
    + @Html.OverrideStoreCheckboxFor(model => model.TermsOfServiceOnShoppingCartPage_OverrideForStore, model => model.TermsOfServiceOnShoppingCartPage, Model.ActiveStoreScopeConfiguration) + @Html.NopLabelFor(model => model.TermsOfServiceOnShoppingCartPage) +
    +
    + @Html.NopEditorFor(model => model.TermsOfServiceOnShoppingCartPage) + @Html.ValidationMessageFor(model => model.TermsOfServiceOnShoppingCartPage) +
    +
    +
    +
    + @Html.OverrideStoreCheckboxFor(model => model.TermsOfServiceOnOrderConfirmPage_OverrideForStore, model => model.TermsOfServiceOnOrderConfirmPage, Model.ActiveStoreScopeConfiguration) + @Html.NopLabelFor(model => model.TermsOfServiceOnOrderConfirmPage) +
    +
    + @Html.NopEditorFor(model => model.TermsOfServiceOnOrderConfirmPage) + @Html.ValidationMessageFor(model => model.TermsOfServiceOnOrderConfirmPage) +
    +
    +
    +
    + @Html.OverrideStoreCheckboxFor(model => model.DisableBillingAddressCheckoutStep_OverrideForStore, model => model.DisableBillingAddressCheckoutStep, Model.ActiveStoreScopeConfiguration) + @Html.NopLabelFor(model => model.DisableBillingAddressCheckoutStep) +
    +
    + @Html.NopEditorFor(model => model.DisableBillingAddressCheckoutStep) + @Html.ValidationMessageFor(model => model.DisableBillingAddressCheckoutStep) +
    +
    +
    +
    + @Html.OverrideStoreCheckboxFor(model => model.DisableOrderCompletedPage_OverrideForStore, model => model.DisableOrderCompletedPage, Model.ActiveStoreScopeConfiguration) + @Html.NopLabelFor(model => model.DisableOrderCompletedPage) +
    +
    + @Html.NopEditorFor(model => model.DisableOrderCompletedPage) + @Html.ValidationMessageFor(model => model.DisableOrderCompletedPage) +
    +
    +
    +
    +
    +
    + @T("Admin.Configuration.Settings.Order.BlockTitle.OrderTotals") +
    +
    +
    +
    + @Html.OverrideStoreCheckboxFor(model => model.MinOrderSubtotalAmount_OverrideForStore, model => model.MinOrderSubtotalAmount, Model.ActiveStoreScopeConfiguration) + @Html.NopLabelFor(model => model.MinOrderSubtotalAmount) +
    +
    + @Html.NopEditorFor(model => model.MinOrderSubtotalAmount, Model.PrimaryStoreCurrencyCode) + @Html.ValidationMessageFor(model => model.MinOrderSubtotalAmount) +
    +
    +
    +
    + @Html.OverrideStoreCheckboxFor(model => model.MinOrderSubtotalAmountIncludingTax_OverrideForStore, model => model.MinOrderSubtotalAmountIncludingTax, Model.ActiveStoreScopeConfiguration) + @Html.NopLabelFor(model => model.MinOrderSubtotalAmountIncludingTax) +
    +
    + @Html.NopEditorFor(model => model.MinOrderSubtotalAmountIncludingTax) + @Html.ValidationMessageFor(model => model.MinOrderSubtotalAmountIncludingTax) +
    +
    +
    +
    + @Html.OverrideStoreCheckboxFor(model => model.MinOrderTotalAmount_OverrideForStore, model => model.MinOrderTotalAmount, Model.ActiveStoreScopeConfiguration) + @Html.NopLabelFor(model => model.MinOrderTotalAmount) +
    +
    + @Html.NopEditorFor(model => model.MinOrderTotalAmount, Model.PrimaryStoreCurrencyCode) + @Html.ValidationMessageFor(model => model.MinOrderTotalAmount) +
    +
    +
    +
    + @Html.OverrideStoreCheckboxFor(model => model.AutoUpdateOrderTotalsOnEditingOrder_OverrideForStore, model => model.AutoUpdateOrderTotalsOnEditingOrder, Model.ActiveStoreScopeConfiguration) + @Html.NopLabelFor(model => model.AutoUpdateOrderTotalsOnEditingOrder) +
    +
    + @Html.NopEditorFor(model => model.AutoUpdateOrderTotalsOnEditingOrder) + @Html.ValidationMessageFor(model => model.AutoUpdateOrderTotalsOnEditingOrder) +
    +
    +
    +
    +
    +
    + @T("Admin.Configuration.Settings.Order.BlockTitle.Common") +
    +
    + @if (Model.OrderIdent.HasValue) + { +
    +
    + @Html.NopLabelFor(model => model.OrderIdent) +
    +
    + @Html.NopEditorFor(model => model.OrderIdent) +
    +
    + } +
    +
    + @Html.OverrideStoreCheckboxFor(model => model.CustomOrderNumberMask_OverrideForStore, model => model.CustomOrderNumberMask, Model.ActiveStoreScopeConfiguration) + @Html.NopLabelFor(model => model.CustomOrderNumberMask) +
    +
    + @Html.NopEditorFor(model => model.CustomOrderNumberMask) + @Html.ValidationMessageFor(model => model.CustomOrderNumberMask) +
      +
    • + @T("Admin.Configuration.Settings.Order.CustomOrderNumberMask.Description.YYYY") +
    • +
    • + @T("Admin.Configuration.Settings.Order.CustomOrderNumberMask.Description.YY") +
    • +
    • + @T("Admin.Configuration.Settings.Order.CustomOrderNumberMask.Description.MM") +
    • +
    • + @T("Admin.Configuration.Settings.Order.CustomOrderNumberMask.Description.DD") +
    • +
    • + @T("Admin.Configuration.Settings.Order.CustomOrderNumberMask.Description.ID") +
    • +
    +
    +
    +
    +
    + @Html.OverrideStoreCheckboxFor(model => model.InvoiceIdent_OverrideForStore, model => model.InvoiceIdent, model => model.InvoiceYear, Model.ActiveStoreScopeConfiguration) + @Html.NopLabelFor(model => model.InvoiceIdent) +
    +
    + @Html.NopEditorFor(model => model.InvoiceIdent) + @Html.ValidationMessageFor(model => model.InvoiceIdent) +
    +
    +
    +
    + @Html.NopLabelFor(model => model.InvoiceYear) +
    +
    + @Html.NopEditorFor(model => model.InvoiceYear) + @Html.ValidationMessageFor(model => model.InvoiceYear) +
    +
    +
    +
    + @Html.OverrideStoreCheckboxFor(model => model.IsReOrderAllowed_OverrideForStore, model => model.IsReOrderAllowed, Model.ActiveStoreScopeConfiguration) + @Html.NopLabelFor(model => model.IsReOrderAllowed) +
    +
    + @Html.NopEditorFor(model => model.IsReOrderAllowed) + @Html.ValidationMessageFor(model => model.IsReOrderAllowed) +
    +
    +
    +
    + @Html.NopLabelFor(model => model.CompleteOrderWhenDelivered) +
    +
    + @Html.NopEditorFor(model => model.CompleteOrderWhenDelivered) + @Html.ValidationMessageFor(model => model.CompleteOrderWhenDelivered) +
    +
    +
    +
    + @Html.OverrideStoreCheckboxFor(model => model.ExportWithProducts_OverrideForStore, model => model.ExportWithProducts, Model.ActiveStoreScopeConfiguration) + @Html.NopLabelFor(model => model.ExportWithProducts) +
    +
    + @Html.NopEditorFor(model => model.ExportWithProducts) + @Html.ValidationMessageFor(model => model.ExportWithProducts) +
    +
    +
    +
    +
    +
    + @T("Admin.Configuration.Settings.Order.BlockTitle.PdfInvoice") +
    +
    +
    +
    + @Html.OverrideStoreCheckboxFor(model => model.AttachPdfInvoiceToOrderPlacedEmail_OverrideForStore, model => model.AttachPdfInvoiceToOrderPlacedEmail, Model.ActiveStoreScopeConfiguration) + @Html.NopLabelFor(model => model.AttachPdfInvoiceToOrderPlacedEmail) +
    +
    + @Html.NopEditorFor(model => model.AttachPdfInvoiceToOrderPlacedEmail) + @Html.ValidationMessageFor(model => model.AttachPdfInvoiceToOrderPlacedEmail) +
    +
    +
    +
    + @Html.OverrideStoreCheckboxFor(model => model.AttachPdfInvoiceToOrderPaidEmail_OverrideForStore, model => model.AttachPdfInvoiceToOrderPaidEmail, Model.ActiveStoreScopeConfiguration) + @Html.NopLabelFor(model => model.AttachPdfInvoiceToOrderPaidEmail) +
    +
    + @Html.NopEditorFor(model => model.AttachPdfInvoiceToOrderPaidEmail) + @Html.ValidationMessageFor(model => model.AttachPdfInvoiceToOrderPaidEmail) +
    +
    +
    +
    + @Html.OverrideStoreCheckboxFor(model => model.AttachPdfInvoiceToOrderCompletedEmail_OverrideForStore, model => model.AttachPdfInvoiceToOrderCompletedEmail, Model.ActiveStoreScopeConfiguration) + @Html.NopLabelFor(model => model.AttachPdfInvoiceToOrderCompletedEmail) +
    +
    + @Html.NopEditorFor(model => model.AttachPdfInvoiceToOrderCompletedEmail) + @Html.ValidationMessageFor(model => model.AttachPdfInvoiceToOrderCompletedEmail) +
    +
    +
    +
    +
    +
    + @T("Admin.Configuration.Settings.Order.BlockTitle.GiftCards") +
    +
    +
    +
    + @Html.NopLabelFor(model => model.ActivateGiftCardsAfterCompletingOrder) +
    +
    + @Html.NopEditorFor(model => model.ActivateGiftCardsAfterCompletingOrder) + @Html.ValidationMessageFor(model => model.ActivateGiftCardsAfterCompletingOrder) +
    +
    +
    +
    + @Html.NopLabelFor(model => model.DeactivateGiftCardsAfterCancellingOrder) +
    +
    + @Html.NopEditorFor(model => model.DeactivateGiftCardsAfterCancellingOrder) + @Html.ValidationMessageFor(model => model.DeactivateGiftCardsAfterCancellingOrder) +
    +
    +
    +
    + @Html.NopLabelFor(model => model.DeactivateGiftCardsAfterDeletingOrder) +
    +
    + @Html.NopEditorFor(model => model.DeactivateGiftCardsAfterDeletingOrder) + @Html.ValidationMessageFor(model => model.DeactivateGiftCardsAfterDeletingOrder) +
    +
    +
    +
    +
    +} + +@helper TabReturnRequestSettings() +{ +
    +
    +
    + @T("Admin.Configuration.Settings.Order.BlockTitle.Common") +
    +
    +
      +
    • + @T("Admin.Configuration.Settings.Order.ReturnRequestsDescription1") +
    • +
    • + @T("Admin.Configuration.Settings.Order.ReturnRequestsDescription2") +
    • +
    +
    +
    + @Html.OverrideStoreCheckboxFor(model => model.ReturnRequestsEnabled_OverrideForStore, model => model.ReturnRequestsEnabled, Model.ActiveStoreScopeConfiguration) + @Html.NopLabelFor(model => model.ReturnRequestsEnabled) +
    +
    + @Html.NopEditorFor(model => model.ReturnRequestsEnabled) + @Html.ValidationMessageFor(model => model.ReturnRequestsEnabled) +
    +
    +
    +
    + @Html.OverrideStoreCheckboxFor(model => model.ReturnRequestNumberMask_OverrideForStore, model => model.ReturnRequestNumberMask, Model.ActiveStoreScopeConfiguration) + @Html.NopLabelFor(model => model.ReturnRequestNumberMask) +
    +
    + @Html.NopEditorFor(model => model.ReturnRequestNumberMask) + @Html.ValidationMessageFor(model => model.ReturnRequestNumberMask) +
      +
    • + @T("Admin.Configuration.Settings.Order.ReturnRequestNumberMask.Description.ID") +
    • +
    • + @T("Admin.Configuration.Settings.Order.ReturnRequestNumberMask.Description.YYYY") +
    • +
    • + @T("Admin.Configuration.Settings.Order.ReturnRequestNumberMask.Description.YY") +
    • +
    • + @T("Admin.Configuration.Settings.Order.ReturnRequestNumberMask.Description.MM") +
    • +
    • + @T("Admin.Configuration.Settings.Order.ReturnRequestNumberMask.Description.DD") +
    • +
    +
    +
    +
    +
    + @Html.OverrideStoreCheckboxFor(model => model.NumberOfDaysReturnRequestAvailable_OverrideForStore, model => model.NumberOfDaysReturnRequestAvailable, Model.ActiveStoreScopeConfiguration) + @Html.NopLabelFor(model => model.NumberOfDaysReturnRequestAvailable) +
    +
    + @Html.NopEditorFor(model => model.NumberOfDaysReturnRequestAvailable) + @Html.ValidationMessageFor(model => model.NumberOfDaysReturnRequestAvailable) +
    +
    +
    +
    + @Html.OverrideStoreCheckboxFor(model => model.ReturnRequestsAllowFiles_OverrideForStore, model => model.ReturnRequestsAllowFiles, Model.ActiveStoreScopeConfiguration) + @Html.NopLabelFor(model => model.ReturnRequestsAllowFiles) +
    +
    + @Html.NopEditorFor(model => model.ReturnRequestsAllowFiles) + @Html.ValidationMessageFor(model => model.ReturnRequestsAllowFiles) +
    +
    +
    +
    + @Html.Partial("_ReturnRequestReasons") + @Html.Partial("_ReturnRequestActions") +
    } \ No newline at end of file diff --git a/src/Presentation/Nop.Web/App_Data/Install/SqlServer.Indexes.sql b/src/Presentation/Nop.Web/App_Data/Install/SqlServer.Indexes.sql index 585081f1fb4..7676ccd337b 100644 --- a/src/Presentation/Nop.Web/App_Data/Install/SqlServer.Indexes.sql +++ b/src/Presentation/Nop.Web/App_Data/Install/SqlServer.Indexes.sql @@ -37,7 +37,7 @@ GO CREATE NONCLUSTERED INDEX [IX_Order_CustomerId] ON [Order] ([CustomerId] ASC) GO -CREATE UNIQUE NONCLUSTERED INDEX UI_Order_InvoiceId ON dbo.[Order](InvoiceId) +CREATE UNIQUE NONCLUSTERED INDEX UI_Order_InvoiceId ON dbo.[Order](InvoiceId, StoreId) WHERE InvoiceId IS NOT NULL; CREATE NONCLUSTERED INDEX [IX_Language_DisplayOrder] ON [Language] ([DisplayOrder] ASC) diff --git a/src/Presentation/Nop.Web/App_Data/Localization/defaultResources.nopres.xml b/src/Presentation/Nop.Web/App_Data/Localization/defaultResources.nopres.xml index c4ba7860200..19074f1f12f 100644 --- a/src/Presentation/Nop.Web/App_Data/Localization/defaultResources.nopres.xml +++ b/src/Presentation/Nop.Web/App_Data/Localization/defaultResources.nopres.xml @@ -7044,6 +7044,12 @@ Invoice ID counter. This is useful if you want your invoices to start from certain number. This only affects invoices henceforward. The value must be greater than the current maximum invoice ID. + + Year of last invoice + + + Year of last invoice. If actual year is different from this setting, then InvoiceIdent starts from one. + Customer roles diff --git a/src/Presentation/Nop.Web/Views/Order/Details.cshtml b/src/Presentation/Nop.Web/Views/Order/Details.cshtml index b342e7c3586..6cb2acabded 100644 --- a/src/Presentation/Nop.Web/Views/Order/Details.cshtml +++ b/src/Presentation/Nop.Web/Views/Order/Details.cshtml @@ -1,822 +1,822 @@ -@model OrderDetailsModel -@using Nop.Web.Models.Order -@{ - if (!Model.PrintMode) - { - Layout = "~/Views/Shared/_ColumnsOne.cshtml"; - } - else - { - Layout = "~/Views/Shared/_Print.cshtml"; - } - //title - Html.AddTitleParts(T("PageTitle.OrderDetails").Text); - //page class - Html.AppendPageCssClassParts("html-order-details-page"); -} -@if (Model.PrintMode) -{ - -} -
    - @if (!Model.PrintMode) - { -
    -

    @T("Order.OrderInformation")

    - @T("Order.Print") - @if (!Model.PdfInvoiceDisabled) - { - @T("Order.GetPDFInvoice") - } -
    - } -
    - @Html.Widget("orderdetails_page_top", Model.Id) -
    -
    - @T("Order.Order#")@Model.CustomOrderNumber -
    -
      -
    • - @T("Order.OrderDate"): @Model.CreatedOn.ToString("D") -
    • -
    • - @T("Order.OrderStatus"): @Model.OrderStatus -
    • -
    • - @T("Order.OrderTotal"): @Model.OrderAmountIncl -
    • -
    - @Html.Widget("orderdetails_page_overview", Model.Id) -
    -
    -
    -
    -
    - @T("Order.BillingAddress") -
    -
      -
    • - @Model.BillingAddress.FirstName @Model.BillingAddress.LastName -
    • - - @if (Model.BillingAddress.PhoneEnabled) - { -
    • - @T("Order.Phone"): @Model.BillingAddress.PhoneNumber -
    • - } - @if (Model.BillingAddress.FaxEnabled) - { -
    • - @T("Order.Fax"): @Model.BillingAddress.FaxNumber -
    • - } - @if (Model.BillingAddress.CompanyEnabled && !String.IsNullOrEmpty(Model.BillingAddress.Company)) - { -
    • - @Model.BillingAddress.Company -
    • - } - @if (Model.BillingAddress.StreetAddressEnabled) - { -
    • - @Model.BillingAddress.Address1 -
    • - } - @if (Model.BillingAddress.StreetAddress2Enabled && !String.IsNullOrEmpty(Model.BillingAddress.Address2)) - { -
    • - @Model.BillingAddress.Address2 -
    • - } - @if (Model.BillingAddress.CityEnabled || - Model.BillingAddress.StateProvinceEnabled || - Model.BillingAddress.ZipPostalCodeEnabled) - { -
    • - @if (Model.BillingAddress.CityEnabled) - { - @Model.BillingAddress.City - } - @if (Model.BillingAddress.CityEnabled && (Model.BillingAddress.StateProvinceEnabled || Model.BillingAddress.ZipPostalCodeEnabled)) - { - , - } - @if (Model.BillingAddress.StateProvinceEnabled) - { - @Model.BillingAddress.StateProvinceName - } - @if (Model.BillingAddress.ZipPostalCodeEnabled) - { - @Model.BillingAddress.ZipPostalCode - } -
    • - } - @if (Model.BillingAddress.CountryEnabled && !String.IsNullOrEmpty(Model.BillingAddress.CountryName)) - { -
    • - @Model.BillingAddress.CountryName -
    • - } - @if (!String.IsNullOrEmpty(Model.VatNumber)) - { -
    • - - @T("Order.VATNumber") - - - @Model.VatNumber - -
    • - } - @if (!String.IsNullOrEmpty(Model.BillingAddress.FormattedCustomAddressAttributes)) - { -
    • - @Html.Raw(Model.BillingAddress.FormattedCustomAddressAttributes) -
    • - } - @if (Model.CustomValues != null) - { - foreach (var item in Model.CustomValues) - { -
    • - - @item.Key: - - - @(item.Value != null ? item.Value.ToString() : "") - -
    • - } - } -
    -
    - @if (!String.IsNullOrEmpty(Model.PaymentMethod)) - { -
    -
    - @T("Order.Payment") -
    -
      -
    • - - @T("Order.Payment.Method"): - - - @Model.PaymentMethod - -
    • - @if (!Model.PrintMode) - { -
    • - - @T("Order.Payment.Status"): - - - @Model.PaymentMethodStatus - -
    • - } - @if (!Model.PrintMode && Model.CanRePostProcessPayment) - { - @*Complete payment (for redirection payment methods)*@ -
    • - @using (Html.BeginRouteForm("OrderDetails", FormMethod.Post)) - { - @Html.AntiForgeryToken() - -

      - @T("Order.RetryPayment.Hint") -

      - } -
    • - } -
    -
    - } -
    - @if (Model.IsShippable) - { -
    -
    -
    - @(Model.PickUpInStore ? T("Order.PickupAddress") : T("Order.ShippingAddress")) -
    -
      - @if (!Model.PickUpInStore) - { -
    • - @Model.ShippingAddress.FirstName @Model.ShippingAddress.LastName -
    • - - if (Model.ShippingAddress.PhoneEnabled) - { -
    • - @T("Order.Phone"): @Model.ShippingAddress.PhoneNumber -
    • - } - if (Model.ShippingAddress.FaxEnabled) - { -
    • - @T("Order.Fax"): @Model.ShippingAddress.FaxNumber -
    • - } - if (Model.ShippingAddress.CompanyEnabled && !String.IsNullOrEmpty(Model.ShippingAddress.Company)) - { -
    • - @Model.ShippingAddress.Company -
    • - } - if (Model.ShippingAddress.StreetAddressEnabled) - { -
    • - @Model.ShippingAddress.Address1 -
    • - } - if (Model.ShippingAddress.StreetAddress2Enabled && !String.IsNullOrEmpty(Model.ShippingAddress.Address2)) - { -
    • - @Model.ShippingAddress.Address2 -
    • - } - if (Model.ShippingAddress.CityEnabled || - Model.ShippingAddress.StateProvinceEnabled || - Model.ShippingAddress.ZipPostalCodeEnabled) - { -
    • - @if (Model.ShippingAddress.CityEnabled) - { - @Model.ShippingAddress.City - } - @if (Model.ShippingAddress.CityEnabled && (Model.ShippingAddress.StateProvinceEnabled || Model.ShippingAddress.ZipPostalCodeEnabled)) - { - , - } - @if (Model.ShippingAddress.StateProvinceEnabled) - { - @Model.ShippingAddress.StateProvinceName - } - @if (Model.ShippingAddress.ZipPostalCodeEnabled) - { - @Model.ShippingAddress.ZipPostalCode - } -
    • - } - if (Model.ShippingAddress.CountryEnabled && !String.IsNullOrEmpty(Model.ShippingAddress.CountryName)) - { -
    • - @Model.ShippingAddress.CountryName -
    • - } - if (!String.IsNullOrEmpty(Model.ShippingAddress.FormattedCustomAddressAttributes)) - { -
    • - @Html.Raw(Model.ShippingAddress.FormattedCustomAddressAttributes) -
    • - } - } - else - { - if (!string.IsNullOrEmpty(Model.PickupAddress.Address1)) - { -
    • - @Model.PickupAddress.Address1 -
    • - } - if (!string.IsNullOrEmpty(Model.PickupAddress.City) || !string.IsNullOrEmpty(Model.PickupAddress.ZipPostalCode)) - { -
    • - @if (!string.IsNullOrEmpty(Model.PickupAddress.City)) - { - @Model.PickupAddress.City - } - @if (!string.IsNullOrEmpty(Model.PickupAddress.ZipPostalCode)) - { - , - @Model.PickupAddress.ZipPostalCode - } -
    • - } - if (!string.IsNullOrEmpty(Model.PickupAddress.CountryName)) - { -
    • - @Model.PickupAddress.CountryName -
    • - } - } -
    -
    -
    -
    - @T("Order.Shipping") -
    -
      -
    • - - @T("Order.Shipping.Name"): - - - @Model.ShippingMethod - -
    • - @if (!Model.PrintMode) - { -
    • - - @T("Order.Shipping.Status"): - - - @Model.ShippingStatus - -
    • - } -
    -
    -
    - } -
    - @if (!Model.PrintMode && Model.Shipments.Count > 0) - { -
    -
    - @T("Order.Shipments") -
    -
    - - - - - - - - - - - - - - - - - - - @foreach (var item in Model.Shipments) - { - - - - - - - - } - -
    - @T("Order.Shipments.ID") - - @T("Order.Shipments.TrackingNumber") - - @T("Order.Shipments.ShippedDate") - - @T("Order.Shipments.DeliveryDate") - - @T("Order.Shipments.ViewDetails") -
    - - @item.Id.ToString() - - - @item.TrackingNumber - - - @if (item.ShippedDate.HasValue) - { - @item.ShippedDate.Value.ToString("D") - } - else - { - @T("Order.Shipments.ShippedDate.NotYet") - } - - - @if (item.DeliveryDate.HasValue) - { - @item.DeliveryDate.Value.ToString("D") - } - else - { - @T("Order.Shipments.DeliveryDate.NotYet") - } - - @T("Order.Shipments.ViewDetails") -
    -
    -
    - } - @if (Model.Items.Count > 0) - { - if (!Model.PrintMode && Model.OrderNotes.Count > 0) - { -
    -
    - @T("Order.Notes") -
    -
    - - - - - - - - - - - - - @foreach (var item in Model.OrderNotes) - { - - - - - } - -
    - @T("Order.Notes.CreatedOn") - - @T("Order.Notes.Note") -
    - @item.CreatedOn.ToString() - - @Html.Raw(item.Note) - @if (item.HasDownload) - { -

    - @T("Order.Notes.Download") -

    - } -
    -
    -
    - } - @Html.Widget("orderdetails_page_beforeproducts", Model.Id) -
    -
    - @T("Order.Product(s)") -
    -
    - - - @if (Model.ShowSku) - { - - } - - - - - - - - - @if (Model.ShowSku) - { - - } - - - - - - - - - @foreach (var item in Model.Items) - { - - @if (Model.ShowSku) - { - - } - - - - - - - } - -
    - @T("Order.Product(s).SKU") - - @T("Order.Product(s).Name") - - @T("Order.Product(s).Price") - - @T("Order.Product(s).Quantity") - - @T("Order.TaxRateLine.TaxRate") - - @T("Order.Product(s).Total") -
    - - @item.Sku - - @if (!Model.PrintMode) - { - @item.ProductName - } - else - { - @item.ProductName - } - @if (item.ExcludeFromRewardPoints) - { -
    - @T("Products.ExcludeFromRewardPoints") -
    - } - @if (!String.IsNullOrEmpty(item.AttributeInfo)) - { -
    - @Html.Raw(item.AttributeInfo) -
    - } - @if (!String.IsNullOrEmpty(item.RentalInfo)) - { -
    - @Html.Raw(item.RentalInfo) -
    - } - @if (item.DownloadId > 0) - { - - } - @if (item.LicenseId > 0) - { - - } - @Html.Widget("orderdetails_product_line", item.Id) -
    - - @item.UnitPrice - - - @item.Quantity - - - @item.TaxRate - - - @item.SubTotal -
    -
    - @if (Model.Items.Count > 0 && Model.DisplayTaxShippingInfo) - { - var inclTax = Model.PricesIncludeTax; - //tax info is already included in the price (incl/excl tax). that's why we display only shipping info here - //of course, you can modify appropriate locales to include VAT info there -
    - @T(inclTax ? "Order.TaxShipping.InclTax" : "Order.TaxShipping.ExclTax", Url.RouteUrl("Topic", new { SeName = Html.GetTopicSeName("shippinginfo") })) -
    - } -
    - @Html.Widget("orderdetails_page_afterproducts", Model.Id) -
    - @if (!String.IsNullOrEmpty(Model.CheckoutAttributeInfo)) - { -
    - @Html.Raw(Model.CheckoutAttributeInfo) -
    - } -
    -
    -
    - - - - - - - @if (!string.IsNullOrEmpty(Model.OrderSubTotalDiscount)) - { - - - - - } - @if (Model.IsShippable && string.IsNullOrEmpty(Model.OrderShippingNonTaxable)) - { - - - - - } - @if (!string.IsNullOrEmpty(Model.OrderTotalDiscount)) - { - - - - - } - @if (Model.EarnedRewardPointsAreTaxable && Model.RedeemedRewardPoints > 0) - { - - - - - } - @if (!string.IsNullOrEmpty(Model.PaymentMethodAdditionalFee)) - { - - - - - } - @if (!String.IsNullOrEmpty(Model.OrderAmount)) - { - - - - - } - @if (Model.DisplayTax) - { - - - - - } - @if (Model.DisplayTaxRates && Model.TaxRates.Count > 0) - { - - - - } - @if (Model.IsShippable && !string.IsNullOrEmpty(Model.OrderShippingNonTaxable)) - { - - - - - } - @if (!string.IsNullOrEmpty(Model.PaymentMethodAdditionalFeeNonTaxable)) - { - - - - - } - @if (Model.GiftCards.Count > 0) - { - foreach (var gc in Model.GiftCards) - { - - - - - } - } - @if (!Model.EarnedRewardPointsAreTaxable && Model.RedeemedRewardPoints > 0) - { - - - - - } - @if (Model.RedeemedRewardPointsPurchased > 0) - { - - - - - } - - - - - -
    - - - @Model.OrderSubtotal -
    - - - @Model.OrderSubTotalDiscount -
    - - - @Model.OrderShipping -
    - - - @Model.OrderTotalDiscount -
    - - - @Model.RedeemedRewardPointsAmount -
    - - - @Model.PaymentMethodAdditionalFee -
    - - - @(Model.includingTax ? Model.OrderAmountIncl : Model.OrderAmount) -
    - - - @Model.Tax -
    -
      -
    • - @T("Order.TaxRateLine.TaxRate") - @if (Model.includingTax) - { - @T("Order.TaxRateLine.AmountIncl") - } - else - { - @T("Order.TaxRateLine.Amount") - } - @if (Model.includingTax) - { - @T("Order.TaxRateLine.DiscountAmountIncl") - } - else - { - @T("Order.TaxRateLine.DiscountAmount") - } - @T("Order.TaxRateLine.BaseAmount") - @T("Order.TaxRateLine.TaxAmount") -
    • - @foreach (var taxRate in Model.TaxRates) - { -
    • - @taxRate.Rate - @taxRate.Amount - @taxRate.DiscountAmount - @taxRate.BaseAmount - @taxRate.TaxAmount -
    • - } -
    -
    - - - @Model.OrderShippingNonTaxable -
    - - - @Model.PaymentMethodAdditionalFeeNonTaxable -
    - - - @gc.Amount -
    - - - @Model.RedeemedRewardPointsAmount -
    - - - @Model.RedeemedRewardPointsAmountPurchased -
    - - - @Model.OrderTotal -
    -
    - @if (!Model.PrintMode) - { -
    - @if (Model.IsReOrderAllowed) - { - - } - @if (Model.IsReturnRequestAllowed) - { - - } -
    - } -
    - } - @Html.Widget("orderdetails_page_bottom", Model.Id) -
    -
    +@model OrderDetailsModel +@using Nop.Web.Models.Order +@{ + if (!Model.PrintMode) + { + Layout = "~/Views/Shared/_ColumnsOne.cshtml"; + } + else + { + Layout = "~/Views/Shared/_Print.cshtml"; + } + //title + Html.AddTitleParts(T("PageTitle.OrderDetails").Text); + //page class + Html.AppendPageCssClassParts("html-order-details-page"); +} +@if (Model.PrintMode) +{ + +} +
    + @if (!Model.PrintMode) + { +
    +

    @T("Order.OrderInformation")

    + @T("Order.Print") + @if (!Model.PdfInvoiceDisabled) + { + @T("Order.GetPDFInvoice") + } +
    + } +
    + @Html.Widget("orderdetails_page_top", Model.Id) +
    +
    + @T("Order.Order#")@Model.CustomOrderNumber +
    +
      +
    • + @T("Order.OrderDate"): @Model.CreatedOn.ToString("D") +
    • +
    • + @T("Order.OrderStatus"): @Model.OrderStatus +
    • +
    • + @T("Order.OrderTotal"): @Model.OrderTotal +
    • +
    + @Html.Widget("orderdetails_page_overview", Model.Id) +
    +
    +
    +
    +
    + @T("Order.BillingAddress") +
    +
      +
    • + @Model.BillingAddress.FirstName @Model.BillingAddress.LastName +
    • + + @if (Model.BillingAddress.PhoneEnabled) + { +
    • + @T("Order.Phone"): @Model.BillingAddress.PhoneNumber +
    • + } + @if (Model.BillingAddress.FaxEnabled) + { +
    • + @T("Order.Fax"): @Model.BillingAddress.FaxNumber +
    • + } + @if (Model.BillingAddress.CompanyEnabled && !String.IsNullOrEmpty(Model.BillingAddress.Company)) + { +
    • + @Model.BillingAddress.Company +
    • + } + @if (Model.BillingAddress.StreetAddressEnabled) + { +
    • + @Model.BillingAddress.Address1 +
    • + } + @if (Model.BillingAddress.StreetAddress2Enabled && !String.IsNullOrEmpty(Model.BillingAddress.Address2)) + { +
    • + @Model.BillingAddress.Address2 +
    • + } + @if (Model.BillingAddress.CityEnabled || + Model.BillingAddress.StateProvinceEnabled || + Model.BillingAddress.ZipPostalCodeEnabled) + { +
    • + @if (Model.BillingAddress.CityEnabled) + { + @Model.BillingAddress.City + } + @if (Model.BillingAddress.CityEnabled && (Model.BillingAddress.StateProvinceEnabled || Model.BillingAddress.ZipPostalCodeEnabled)) + { + , + } + @if (Model.BillingAddress.StateProvinceEnabled) + { + @Model.BillingAddress.StateProvinceName + } + @if (Model.BillingAddress.ZipPostalCodeEnabled) + { + @Model.BillingAddress.ZipPostalCode + } +
    • + } + @if (Model.BillingAddress.CountryEnabled && !String.IsNullOrEmpty(Model.BillingAddress.CountryName)) + { +
    • + @Model.BillingAddress.CountryName +
    • + } + @if (!String.IsNullOrEmpty(Model.VatNumber)) + { +
    • + + @T("Order.VATNumber") + + + @Model.VatNumber + +
    • + } + @if (!String.IsNullOrEmpty(Model.BillingAddress.FormattedCustomAddressAttributes)) + { +
    • + @Html.Raw(Model.BillingAddress.FormattedCustomAddressAttributes) +
    • + } + @if (Model.CustomValues != null) + { + foreach (var item in Model.CustomValues) + { +
    • + + @item.Key: + + + @(item.Value != null ? item.Value.ToString() : "") + +
    • + } + } +
    +
    + @if (!String.IsNullOrEmpty(Model.PaymentMethod)) + { +
    +
    + @T("Order.Payment") +
    +
      +
    • + + @T("Order.Payment.Method"): + + + @Model.PaymentMethod + +
    • + @if (!Model.PrintMode) + { +
    • + + @T("Order.Payment.Status"): + + + @Model.PaymentMethodStatus + +
    • + } + @if (!Model.PrintMode && Model.CanRePostProcessPayment) + { + @*Complete payment (for redirection payment methods)*@ +
    • + @using (Html.BeginRouteForm("OrderDetails", FormMethod.Post)) + { + @Html.AntiForgeryToken() + +

      + @T("Order.RetryPayment.Hint") +

      + } +
    • + } +
    +
    + } +
    + @if (Model.IsShippable) + { +
    +
    +
    + @(Model.PickUpInStore ? T("Order.PickupAddress") : T("Order.ShippingAddress")) +
    +
      + @if (!Model.PickUpInStore) + { +
    • + @Model.ShippingAddress.FirstName @Model.ShippingAddress.LastName +
    • + + if (Model.ShippingAddress.PhoneEnabled) + { +
    • + @T("Order.Phone"): @Model.ShippingAddress.PhoneNumber +
    • + } + if (Model.ShippingAddress.FaxEnabled) + { +
    • + @T("Order.Fax"): @Model.ShippingAddress.FaxNumber +
    • + } + if (Model.ShippingAddress.CompanyEnabled && !String.IsNullOrEmpty(Model.ShippingAddress.Company)) + { +
    • + @Model.ShippingAddress.Company +
    • + } + if (Model.ShippingAddress.StreetAddressEnabled) + { +
    • + @Model.ShippingAddress.Address1 +
    • + } + if (Model.ShippingAddress.StreetAddress2Enabled && !String.IsNullOrEmpty(Model.ShippingAddress.Address2)) + { +
    • + @Model.ShippingAddress.Address2 +
    • + } + if (Model.ShippingAddress.CityEnabled || + Model.ShippingAddress.StateProvinceEnabled || + Model.ShippingAddress.ZipPostalCodeEnabled) + { +
    • + @if (Model.ShippingAddress.CityEnabled) + { + @Model.ShippingAddress.City + } + @if (Model.ShippingAddress.CityEnabled && (Model.ShippingAddress.StateProvinceEnabled || Model.ShippingAddress.ZipPostalCodeEnabled)) + { + , + } + @if (Model.ShippingAddress.StateProvinceEnabled) + { + @Model.ShippingAddress.StateProvinceName + } + @if (Model.ShippingAddress.ZipPostalCodeEnabled) + { + @Model.ShippingAddress.ZipPostalCode + } +
    • + } + if (Model.ShippingAddress.CountryEnabled && !String.IsNullOrEmpty(Model.ShippingAddress.CountryName)) + { +
    • + @Model.ShippingAddress.CountryName +
    • + } + if (!String.IsNullOrEmpty(Model.ShippingAddress.FormattedCustomAddressAttributes)) + { +
    • + @Html.Raw(Model.ShippingAddress.FormattedCustomAddressAttributes) +
    • + } + } + else + { + if (!string.IsNullOrEmpty(Model.PickupAddress.Address1)) + { +
    • + @Model.PickupAddress.Address1 +
    • + } + if (!string.IsNullOrEmpty(Model.PickupAddress.City) || !string.IsNullOrEmpty(Model.PickupAddress.ZipPostalCode)) + { +
    • + @if (!string.IsNullOrEmpty(Model.PickupAddress.City)) + { + @Model.PickupAddress.City + } + @if (!string.IsNullOrEmpty(Model.PickupAddress.ZipPostalCode)) + { + , + @Model.PickupAddress.ZipPostalCode + } +
    • + } + if (!string.IsNullOrEmpty(Model.PickupAddress.CountryName)) + { +
    • + @Model.PickupAddress.CountryName +
    • + } + } +
    +
    +
    +
    + @T("Order.Shipping") +
    +
      +
    • + + @T("Order.Shipping.Name"): + + + @Model.ShippingMethod + +
    • + @if (!Model.PrintMode) + { +
    • + + @T("Order.Shipping.Status"): + + + @Model.ShippingStatus + +
    • + } +
    +
    +
    + } +
    + @if (!Model.PrintMode && Model.Shipments.Count > 0) + { +
    +
    + @T("Order.Shipments") +
    +
    + + + + + + + + + + + + + + + + + + + @foreach (var item in Model.Shipments) + { + + + + + + + + } + +
    + @T("Order.Shipments.ID") + + @T("Order.Shipments.TrackingNumber") + + @T("Order.Shipments.ShippedDate") + + @T("Order.Shipments.DeliveryDate") + + @T("Order.Shipments.ViewDetails") +
    + + @item.Id.ToString() + + + @item.TrackingNumber + + + @if (item.ShippedDate.HasValue) + { + @item.ShippedDate.Value.ToString("D") + } + else + { + @T("Order.Shipments.ShippedDate.NotYet") + } + + + @if (item.DeliveryDate.HasValue) + { + @item.DeliveryDate.Value.ToString("D") + } + else + { + @T("Order.Shipments.DeliveryDate.NotYet") + } + + @T("Order.Shipments.ViewDetails") +
    +
    +
    + } + @if (Model.Items.Count > 0) + { + if (!Model.PrintMode && Model.OrderNotes.Count > 0) + { +
    +
    + @T("Order.Notes") +
    +
    + + + + + + + + + + + + + @foreach (var item in Model.OrderNotes) + { + + + + + } + +
    + @T("Order.Notes.CreatedOn") + + @T("Order.Notes.Note") +
    + @item.CreatedOn.ToString() + + @Html.Raw(item.Note) + @if (item.HasDownload) + { +

    + @T("Order.Notes.Download") +

    + } +
    +
    +
    + } + @Html.Widget("orderdetails_page_beforeproducts", Model.Id) +
    +
    + @T("Order.Product(s)") +
    +
    + + + @if (Model.ShowSku) + { + + } + + + + + + + + + @if (Model.ShowSku) + { + + } + + + + + + + + + @foreach (var item in Model.Items) + { + + @if (Model.ShowSku) + { + + } + + + + + + + } + +
    + @T("Order.Product(s).SKU") + + @T("Order.Product(s).Name") + + @T("Order.Product(s).Price") + + @T("Order.Product(s).Quantity") + + @T("Order.TaxRateLine.TaxRate") + + @T("Order.Product(s).Total") +
    + + @item.Sku + + @if (!Model.PrintMode) + { + @item.ProductName + } + else + { + @item.ProductName + } + @if (item.ExcludeFromRewardPoints) + { +
    + @T("Products.ExcludeFromRewardPoints") +
    + } + @if (!String.IsNullOrEmpty(item.AttributeInfo)) + { +
    + @Html.Raw(item.AttributeInfo) +
    + } + @if (!String.IsNullOrEmpty(item.RentalInfo)) + { +
    + @Html.Raw(item.RentalInfo) +
    + } + @if (item.DownloadId > 0) + { + + } + @if (item.LicenseId > 0) + { + + } + @Html.Widget("orderdetails_product_line", item.Id) +
    + + @item.UnitPrice + + + @item.Quantity + + + @item.TaxRate + + + @item.SubTotal +
    +
    + @if (Model.Items.Count > 0 && Model.DisplayTaxShippingInfo) + { + var inclTax = Model.PricesIncludeTax; + //tax info is already included in the price (incl/excl tax). that's why we display only shipping info here + //of course, you can modify appropriate locales to include VAT info there +
    + @T(inclTax ? "Order.TaxShipping.InclTax" : "Order.TaxShipping.ExclTax", Url.RouteUrl("Topic", new { SeName = Html.GetTopicSeName("shippinginfo") })) +
    + } +
    + @Html.Widget("orderdetails_page_afterproducts", Model.Id) +
    + @if (!String.IsNullOrEmpty(Model.CheckoutAttributeInfo)) + { +
    + @Html.Raw(Model.CheckoutAttributeInfo) +
    + } +
    +
    +
    + + + + + + + @if (!string.IsNullOrEmpty(Model.OrderSubTotalDiscount)) + { + + + + + } + @if (Model.IsShippable && string.IsNullOrEmpty(Model.OrderShippingNonTaxable)) + { + + + + + } + @if (!string.IsNullOrEmpty(Model.OrderTotalDiscount)) + { + + + + + } + @if (Model.EarnedRewardPointsAreTaxable && Model.RedeemedRewardPoints > 0) + { + + + + + } + @if (!string.IsNullOrEmpty(Model.PaymentMethodAdditionalFee)) + { + + + + + } + @if (!String.IsNullOrEmpty(Model.OrderAmount)) + { + + + + + } + @if (Model.DisplayTax) + { + + + + + } + @if (Model.DisplayTaxRates && Model.TaxRates.Count > 0) + { + + + + } + @if (Model.IsShippable && !string.IsNullOrEmpty(Model.OrderShippingNonTaxable)) + { + + + + + } + @if (!string.IsNullOrEmpty(Model.PaymentMethodAdditionalFeeNonTaxable)) + { + + + + + } + @if (Model.GiftCards.Count > 0) + { + foreach (var gc in Model.GiftCards) + { + + + + + } + } + @if (!Model.EarnedRewardPointsAreTaxable && Model.RedeemedRewardPoints > 0) + { + + + + + } + @if (Model.RedeemedRewardPointsPurchased > 0) + { + + + + + } + + + + + +
    + + + @Model.OrderSubtotal +
    + + + @Model.OrderSubTotalDiscount +
    + + + @Model.OrderShipping +
    + + + @Model.OrderTotalDiscount +
    + + + @Model.RedeemedRewardPointsAmount +
    + + + @Model.PaymentMethodAdditionalFee +
    + + + @(Model.includingTax ? Model.OrderAmountIncl : Model.OrderAmount) +
    + + + @Model.Tax +
    +
      +
    • + @T("Order.TaxRateLine.TaxRate") + @if (Model.includingTax) + { + @T("Order.TaxRateLine.AmountIncl") + } + else + { + @T("Order.TaxRateLine.Amount") + } + @if (Model.includingTax) + { + @T("Order.TaxRateLine.DiscountAmountIncl") + } + else + { + @T("Order.TaxRateLine.DiscountAmount") + } + @T("Order.TaxRateLine.BaseAmount") + @T("Order.TaxRateLine.TaxAmount") +
    • + @foreach (var taxRate in Model.TaxRates) + { +
    • + @taxRate.Rate + @taxRate.Amount + @taxRate.DiscountAmount + @taxRate.BaseAmount + @taxRate.TaxAmount +
    • + } +
    +
    + + + @Model.OrderShippingNonTaxable +
    + + + @Model.PaymentMethodAdditionalFeeNonTaxable +
    + + + @gc.Amount +
    + + + @Model.RedeemedRewardPointsAmount +
    + + + @Model.RedeemedRewardPointsAmountPurchased +
    + + + @Model.OrderTotal +
    +
    + @if (!Model.PrintMode) + { +
    + @if (Model.IsReOrderAllowed) + { + + } + @if (Model.IsReturnRequestAllowed) + { + + } +
    + } +
    + } + @Html.Widget("orderdetails_page_bottom", Model.Id) +
    +
    diff --git a/upgradescripts/3.80-the next version/upgrade.sql b/upgradescripts/3.80-the next version/upgrade.sql index a8c64680062..aec8d353091 100644 --- a/upgradescripts/3.80-the next version/upgrade.sql +++ b/upgradescripts/3.80-the next version/upgrade.sql @@ -248,6 +248,18 @@ set @resources=' You haven''t written any reviews yet + + Last used Invoice No. + + + Invoice ID counter. This is useful if you want your invoices to start from certain number. This only affects invoices henceforward. The value must be greater than the current maximum invoice ID. + + + Year of last invoice + + + Year of last invoice. If actual year is different from this setting, then InvoiceIdent starts from one. + Force entering email twice during registration. @@ -3601,12 +3613,14 @@ BEGIN ADD InvoiceId NVARCHAR(20) NULL; END +GO +DROP INDEX IF EXISTS [UI_Order_InvoiceId] ON [dbo].[Order] GO IF NOT EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'[dbo].[Order]') AND name = N'UI_Order_InvoiceId') BEGIN CREATE UNIQUE NONCLUSTERED INDEX UI_Order_InvoiceId - ON dbo.[Order](InvoiceId) + ON dbo.[Order](InvoiceId, StoreId) WHERE InvoiceId IS NOT NULL; END GO From 51533183a92b6e08f86f91df1405820c587b263b Mon Sep 17 00:00:00 2001 From: Markus Falk Date: Thu, 23 Feb 2017 22:43:42 +0100 Subject: [PATCH 08/13] Set invoice ident from background task --- .../Nop.Core/Domain/Orders/OrderSettings.cs | 4 + .../CodeFirstInstallationService.cs | 24680 +++++------ .../Nop.Services/Nop.Services.csproj | 1 + .../Orders/AssignInvoiceIdentTask.cs | 34 + .../Orders/IOrderProcessingService.cs | 8 +- .../Nop.Services/Orders/IOrderService.cs | 393 +- .../Orders/OrderProcessingService.cs | 30 +- .../Nop.Services/Orders/OrderService.cs | 1030 +- .../Controllers/SettingController.cs | 1 + .../Models/Settings/OrderSettingsModel.cs | 2 + .../Administration/Views/Setting/Order.cshtml | 9 + .../Localization/defaultResources.nopres.xml | 33966 ++++++++-------- .../3.80-the next version/upgrade.sql | 11721 +++--- 13 files changed, 36026 insertions(+), 35853 deletions(-) create mode 100644 src/Libraries/Nop.Services/Orders/AssignInvoiceIdentTask.cs diff --git a/src/Libraries/Nop.Core/Domain/Orders/OrderSettings.cs b/src/Libraries/Nop.Core/Domain/Orders/OrderSettings.cs index d59b10ce7b1..e2a3692631e 100644 --- a/src/Libraries/Nop.Core/Domain/Orders/OrderSettings.cs +++ b/src/Libraries/Nop.Core/Domain/Orders/OrderSettings.cs @@ -138,5 +138,9 @@ public class OrderSettings : ISettings /// Gets or sets a value indicating whether the orders need to be exported with their products /// public bool ExportWithProducts { get; set; } + /// + /// Gets or sets a value indicating whether invoice ident should be set from background task + /// + public bool AssignInvoiceIdentFromTask { get; set; } } } \ No newline at end of file diff --git a/src/Libraries/Nop.Services/Installation/CodeFirstInstallationService.cs b/src/Libraries/Nop.Services/Installation/CodeFirstInstallationService.cs index 00d8e26c00b..7d6d2207490 100644 --- a/src/Libraries/Nop.Services/Installation/CodeFirstInstallationService.cs +++ b/src/Libraries/Nop.Services/Installation/CodeFirstInstallationService.cs @@ -1,12336 +1,12346 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Nop.Core; -using Nop.Core.Data; -using Nop.Core.Domain; -using Nop.Core.Domain.Affiliates; -using Nop.Core.Domain.Blogs; -using Nop.Core.Domain.Catalog; -using Nop.Core.Domain.Cms; -using Nop.Core.Domain.Common; -using Nop.Core.Domain.Customers; -using Nop.Core.Domain.Directory; -using Nop.Core.Domain.Discounts; -using Nop.Core.Domain.Forums; -using Nop.Core.Domain.Localization; -using Nop.Core.Domain.Logging; -using Nop.Core.Domain.Media; -using Nop.Core.Domain.Messages; -using Nop.Core.Domain.News; -using Nop.Core.Domain.Orders; -using Nop.Core.Domain.Payments; -using Nop.Core.Domain.Polls; -using Nop.Core.Domain.Security; -using Nop.Core.Domain.Seo; -using Nop.Core.Domain.Shipping; -using Nop.Core.Domain.Stores; -using Nop.Core.Domain.Tasks; -using Nop.Core.Domain.Tax; -using Nop.Core.Domain.Topics; -using Nop.Core.Domain.Vendors; -using Nop.Core.Infrastructure; -using Nop.Services.Common; -using Nop.Services.Configuration; -using Nop.Services.Customers; -using Nop.Services.Helpers; -using Nop.Services.Localization; -using Nop.Services.Media; -using Nop.Services.Seo; - -namespace Nop.Services.Installation -{ - public partial class CodeFirstInstallationService : IInstallationService - { - #region Fields - - private readonly IRepository _storeRepository; - private readonly IRepository _measureDimensionRepository; - private readonly IRepository _measureWeightRepository; - private readonly IRepository _taxCategoryRepository; - private readonly IRepository _languageRepository; - private readonly IRepository _currencyRepository; - private readonly IRepository _customerRepository; - private readonly IRepository _customerPasswordRepository; - private readonly IRepository _customerRoleRepository; - private readonly IRepository _specificationAttributeRepository; - private readonly IRepository _checkoutAttributeRepository; - private readonly IRepository _productAttributeRepository; - private readonly IRepository _categoryRepository; - private readonly IRepository _manufacturerRepository; - private readonly IRepository _productRepository; - private readonly IRepository _urlRecordRepository; - private readonly IRepository _relatedProductRepository; - private readonly IRepository _emailAccountRepository; - private readonly IRepository _messageTemplateRepository; - private readonly IRepository _forumGroupRepository; - private readonly IRepository _forumRepository; - private readonly IRepository _countryRepository; - private readonly IRepository _stateProvinceRepository; - private readonly IRepository _discountRepository; - private readonly IRepository _blogPostRepository; - private readonly IRepository _topicRepository; - private readonly IRepository _newsItemRepository; - private readonly IRepository _pollRepository; - private readonly IRepository _shippingMethodRepository; - private readonly IRepository _deliveryDateRepository; - private readonly IRepository _productAvailabilityRangeRepository; - private readonly IRepository _activityLogTypeRepository; - private readonly IRepository _activityLogRepository; - private readonly IRepository _productTagRepository; - private readonly IRepository _productTemplateRepository; - private readonly IRepository _categoryTemplateRepository; - private readonly IRepository _manufacturerTemplateRepository; - private readonly IRepository _topicTemplateRepository; - private readonly IRepository _scheduleTaskRepository; - private readonly IRepository _returnRequestReasonRepository; - private readonly IRepository _returnRequestActionRepository; - private readonly IRepository
    _addressRepository; - private readonly IRepository _warehouseRepository; - private readonly IRepository _vendorRepository; - private readonly IRepository _affiliateRepository; - private readonly IRepository _orderRepository; - private readonly IRepository _orderItemRepository; - private readonly IRepository _orderNoteRepository; - private readonly IRepository _giftCardRepository; - private readonly IRepository _shipmentRepository; - private readonly IRepository _searchTermRepository; - private readonly IRepository _shipmentItemRepository; - private readonly IRepository _stockQuantityHistoryRepository; - private readonly IGenericAttributeService _genericAttributeService; - private readonly IWebHelper _webHelper; - - #endregion - - #region Ctor - - public CodeFirstInstallationService(IRepository storeRepository, - IRepository measureDimensionRepository, - IRepository measureWeightRepository, - IRepository taxCategoryRepository, - IRepository languageRepository, - IRepository currencyRepository, - IRepository customerRepository, - IRepository customerPasswordRepository, - IRepository customerRoleRepository, - IRepository specificationAttributeRepository, - IRepository checkoutAttributeRepository, - IRepository productAttributeRepository, - IRepository categoryRepository, - IRepository manufacturerRepository, - IRepository productRepository, - IRepository urlRecordRepository, - IRepository relatedProductRepository, - IRepository emailAccountRepository, - IRepository messageTemplateRepository, - IRepository forumGroupRepository, - IRepository forumRepository, - IRepository countryRepository, - IRepository stateProvinceRepository, - IRepository discountRepository, - IRepository blogPostRepository, - IRepository topicRepository, - IRepository newsItemRepository, - IRepository pollRepository, - IRepository shippingMethodRepository, - IRepository deliveryDateRepository, - IRepository productAvailabilityRangeRepository, - IRepository activityLogTypeRepository, - IRepository activityLogRepository, - IRepository productTagRepository, - IRepository productTemplateRepository, - IRepository categoryTemplateRepository, - IRepository manufacturerTemplateRepository, - IRepository topicTemplateRepository, - IRepository scheduleTaskRepository, - IRepository returnRequestReasonRepository, - IRepository returnRequestActionRepository, - IRepository
    addressRepository, - IRepository warehouseRepository, - IRepository vendorRepository, - IRepository affiliateRepository, - IRepository orderRepository, - IRepository orderItemRepository, - IRepository orderNoteRepository, - IRepository giftCardRepository, - IRepository shipmentRepository, - IRepository shipmentItemRepository, - IRepository searchTermRepository, - IRepository stockQuantityHistoryRepository, - IGenericAttributeService genericAttributeService, - IWebHelper webHelper) - { - this._storeRepository = storeRepository; - this._measureDimensionRepository = measureDimensionRepository; - this._measureWeightRepository = measureWeightRepository; - this._taxCategoryRepository = taxCategoryRepository; - this._languageRepository = languageRepository; - this._currencyRepository = currencyRepository; - this._customerRepository = customerRepository; - this._customerPasswordRepository = customerPasswordRepository; - this._customerRoleRepository = customerRoleRepository; - this._specificationAttributeRepository = specificationAttributeRepository; - this._checkoutAttributeRepository = checkoutAttributeRepository; - this._productAttributeRepository = productAttributeRepository; - this._categoryRepository = categoryRepository; - this._manufacturerRepository = manufacturerRepository; - this._productRepository = productRepository; - this._urlRecordRepository = urlRecordRepository; - this._relatedProductRepository = relatedProductRepository; - this._emailAccountRepository = emailAccountRepository; - this._messageTemplateRepository = messageTemplateRepository; - this._forumGroupRepository = forumGroupRepository; - this._forumRepository = forumRepository; - this._countryRepository = countryRepository; - this._stateProvinceRepository = stateProvinceRepository; - this._discountRepository = discountRepository; - this._blogPostRepository = blogPostRepository; - this._topicRepository = topicRepository; - this._newsItemRepository = newsItemRepository; - this._pollRepository = pollRepository; - this._shippingMethodRepository = shippingMethodRepository; - this._deliveryDateRepository = deliveryDateRepository; - this._productAvailabilityRangeRepository = productAvailabilityRangeRepository; - this._activityLogTypeRepository = activityLogTypeRepository; - this._activityLogRepository = activityLogRepository; - this._productTagRepository = productTagRepository; - this._productTemplateRepository = productTemplateRepository; - this._categoryTemplateRepository = categoryTemplateRepository; - this._manufacturerTemplateRepository = manufacturerTemplateRepository; - this._topicTemplateRepository = topicTemplateRepository; - this._scheduleTaskRepository = scheduleTaskRepository; - this._returnRequestReasonRepository = returnRequestReasonRepository; - this._returnRequestActionRepository = returnRequestActionRepository; - this._addressRepository = addressRepository; - this._warehouseRepository = warehouseRepository; - this._vendorRepository = vendorRepository; - this._affiliateRepository = affiliateRepository; - this._orderRepository = orderRepository; - this._orderItemRepository = orderItemRepository; - this._orderNoteRepository = orderNoteRepository; - this._giftCardRepository = giftCardRepository; - this._shipmentRepository = shipmentRepository; - this._shipmentItemRepository = shipmentItemRepository; - this._searchTermRepository = searchTermRepository; - this._stockQuantityHistoryRepository = stockQuantityHistoryRepository; - this._genericAttributeService = genericAttributeService; - this._webHelper = webHelper; - } - - #endregion - - #region Utilities - - protected virtual void InstallStores() - { - //var storeUrl = "http://www.yourStore.com/"; - var storeUrl = _webHelper.GetStoreLocation(false); - var stores = new List - { - new Store - { - Name = "Your store name", - Url = storeUrl, - SslEnabled = false, - Hosts = "yourstore.com,www.yourstore.com", - DisplayOrder = 1, - //should we set some default company info? - CompanyName = "Your company name", - CompanyAddress = "your company country, state, zip, street, etc", - CompanyPhoneNumber = "(123) 456-78901", - CompanyVat = null, - }, - }; - - _storeRepository.Insert(stores); - } - - protected virtual void InstallMeasures() - { - var measureDimensions = new List - { - new MeasureDimension - { - Name = "inch(es)", - SystemKeyword = "inches", - Ratio = 1M, - DisplayOrder = 1, - }, - new MeasureDimension - { - Name = "feet", - SystemKeyword = "feet", - Ratio = 0.08333333M, - DisplayOrder = 2, - }, - new MeasureDimension - { - Name = "meter(s)", - SystemKeyword = "meters", - Ratio = 0.0254M, - DisplayOrder = 3, - }, - new MeasureDimension - { - Name = "millimetre(s)", - SystemKeyword = "millimetres", - Ratio = 25.4M, - DisplayOrder = 4, - } - }; - - _measureDimensionRepository.Insert(measureDimensions); - - var measureWeights = new List - { - new MeasureWeight - { - Name = "ounce(s)", - SystemKeyword = "ounce", - Ratio = 16M, - DisplayOrder = 1, - }, - new MeasureWeight - { - Name = "lb(s)", - SystemKeyword = "lb", - Ratio = 1M, - DisplayOrder = 2, - }, - new MeasureWeight - { - Name = "kg(s)", - SystemKeyword = "kg", - Ratio = 0.45359237M, - DisplayOrder = 3, - }, - new MeasureWeight - { - Name = "gram(s)", - SystemKeyword = "grams", - Ratio = 453.59237M, - DisplayOrder = 4, - } - }; - - _measureWeightRepository.Insert(measureWeights); - } - - protected virtual void InstallTaxCategories() - { - var taxCategories = new List - { - new TaxCategory - { - Name = "Books", - DisplayOrder = 1, - }, - new TaxCategory - { - Name = "Electronics & Software", - DisplayOrder = 5, - }, - new TaxCategory - { - Name = "Downloadable Products", - DisplayOrder = 10, - }, - new TaxCategory - { - Name = "Jewelry", - DisplayOrder = 15, - }, - new TaxCategory - { - Name = "Apparel", - DisplayOrder = 20, - }, - }; - _taxCategoryRepository.Insert(taxCategories); - - } - - protected virtual void InstallLanguages() - { - var language = new Language - { - Name = "English", - LanguageCulture = "en-US", - UniqueSeoCode = "en", - FlagImageFileName = "us.png", - Published = true, - DisplayOrder = 1 - }; - _languageRepository.Insert(language); - } - - protected virtual void InstallLocaleResources() - { - //'English' language - var language = _languageRepository.Table.Single(l => l.Name == "English"); - - //save resources - foreach (var filePath in System.IO.Directory.EnumerateFiles(CommonHelper.MapPath("~/App_Data/Localization/"), "*.nopres.xml", SearchOption.TopDirectoryOnly)) - { - var localesXml = File.ReadAllText(filePath); - var localizationService = EngineContext.Current.Resolve(); - localizationService.ImportResourcesFromXml(language, localesXml); - } - - } - - protected virtual void InstallCurrencies() - { - var currencies = new List - { - new Currency - { - Name = "US Dollar", - CurrencyCode = "USD", - Rate = 1, - DisplayLocale = "en-US", - CustomFormatting = "", - Published = true, - DisplayOrder = 1, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - RoundingType = RoundingType.Rounding001 - }, - new Currency - { - Name = "Australian Dollar", - CurrencyCode = "AUD", - Rate = 1.36M, - DisplayLocale = "en-AU", - CustomFormatting = "", - Published = false, - DisplayOrder = 2, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - RoundingType = RoundingType.Rounding001 - }, - new Currency - { - Name = "British Pound", - CurrencyCode = "GBP", - Rate = 0.82M, - DisplayLocale = "en-GB", - CustomFormatting = "", - Published = false, - DisplayOrder = 3, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - RoundingType = RoundingType.Rounding001 - }, - new Currency - { - Name = "Canadian Dollar", - CurrencyCode = "CAD", - Rate = 1.32M, - DisplayLocale = "en-CA", - CustomFormatting = "", - Published = false, - DisplayOrder = 4, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - RoundingType = RoundingType.Rounding001 - }, - new Currency - { - Name = "Chinese Yuan Renminbi", - CurrencyCode = "CNY", - Rate = 6.93M, - DisplayLocale = "zh-CN", - CustomFormatting = "", - Published = false, - DisplayOrder = 5, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - RoundingType = RoundingType.Rounding001 - }, - new Currency - { - Name = "Euro", - CurrencyCode = "EUR", - Rate = 0.95M, - DisplayLocale = "", - //CustomFormatting = "0.00", - CustomFormatting = string.Format("{0}0.00", "\u20ac"), - Published = true, - DisplayOrder = 6, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - RoundingType = RoundingType.Rounding001 - }, - new Currency - { - Name = "Hong Kong Dollar", - CurrencyCode = "HKD", - Rate = 7.75M, - DisplayLocale = "zh-HK", - CustomFormatting = "", - Published = false, - DisplayOrder = 7, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - RoundingType = RoundingType.Rounding001 - }, - new Currency - { - Name = "Japanese Yen", - CurrencyCode = "JPY", - Rate = 116.64M, - DisplayLocale = "ja-JP", - CustomFormatting = "", - Published = false, - DisplayOrder = 8, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - RoundingType = RoundingType.Rounding001 - }, - new Currency - { - Name = "Russian Rouble", - CurrencyCode = "RUB", - Rate = 59.75M, - DisplayLocale = "ru-RU", - CustomFormatting = "", - Published = false, - DisplayOrder = 9, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - RoundingType = RoundingType.Rounding001 - }, - new Currency - { - Name = "Swedish Krona", - CurrencyCode = "SEK", - Rate = 9.08M, - DisplayLocale = "sv-SE", - CustomFormatting = "", - Published = false, - DisplayOrder = 10, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - RoundingType = RoundingType.Rounding1 - }, - new Currency - { - Name = "Romanian Leu", - CurrencyCode = "RON", - Rate = 4.28M, - DisplayLocale = "ro-RO", - CustomFormatting = "", - Published = false, - DisplayOrder = 11, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - RoundingType = RoundingType.Rounding001 - }, - new Currency - { - Name = "Indian Rupee", - CurrencyCode = "INR", - Rate = 68.17M, - DisplayLocale = "en-IN", - CustomFormatting = "", - Published = false, - DisplayOrder = 12, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - RoundingType = RoundingType.Rounding001 - }, - }; - _currencyRepository.Insert(currencies); - } - - protected virtual void InstallCountriesAndStates() - { - var cUsa = new Country - { - Name = "United States", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "US", - ThreeLetterIsoCode = "USA", - NumericIsoCode = 840, - SubjectToVat = false, - DisplayOrder = 1, - Published = true, - }; - cUsa.StateProvinces.Add(new StateProvince - { - Name = "AA (Armed Forces Americas)", - Abbreviation = "AA", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "AE (Armed Forces Europe)", - Abbreviation = "AE", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Alabama", - Abbreviation = "AL", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Alaska", - Abbreviation = "AK", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "American Samoa", - Abbreviation = "AS", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "AP (Armed Forces Pacific)", - Abbreviation = "AP", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Arizona", - Abbreviation = "AZ", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Arkansas", - Abbreviation = "AR", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "California", - Abbreviation = "CA", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Colorado", - Abbreviation = "CO", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Connecticut", - Abbreviation = "CT", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Delaware", - Abbreviation = "DE", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "District of Columbia", - Abbreviation = "DC", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Federated States of Micronesia", - Abbreviation = "FM", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Florida", - Abbreviation = "FL", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Georgia", - Abbreviation = "GA", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Guam", - Abbreviation = "GU", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Hawaii", - Abbreviation = "HI", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Idaho", - Abbreviation = "ID", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Illinois", - Abbreviation = "IL", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Indiana", - Abbreviation = "IN", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Iowa", - Abbreviation = "IA", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Kansas", - Abbreviation = "KS", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Kentucky", - Abbreviation = "KY", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Louisiana", - Abbreviation = "LA", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Maine", - Abbreviation = "ME", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Marshall Islands", - Abbreviation = "MH", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Maryland", - Abbreviation = "MD", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Massachusetts", - Abbreviation = "MA", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Michigan", - Abbreviation = "MI", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Minnesota", - Abbreviation = "MN", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Mississippi", - Abbreviation = "MS", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Missouri", - Abbreviation = "MO", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Montana", - Abbreviation = "MT", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Nebraska", - Abbreviation = "NE", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Nevada", - Abbreviation = "NV", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "New Hampshire", - Abbreviation = "NH", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "New Jersey", - Abbreviation = "NJ", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "New Mexico", - Abbreviation = "NM", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "New York", - Abbreviation = "NY", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "North Carolina", - Abbreviation = "NC", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "North Dakota", - Abbreviation = "ND", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Northern Mariana Islands", - Abbreviation = "MP", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Ohio", - Abbreviation = "OH", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Oklahoma", - Abbreviation = "OK", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Oregon", - Abbreviation = "OR", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Palau", - Abbreviation = "PW", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Pennsylvania", - Abbreviation = "PA", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Puerto Rico", - Abbreviation = "PR", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Rhode Island", - Abbreviation = "RI", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "South Carolina", - Abbreviation = "SC", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "South Dakota", - Abbreviation = "SD", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Tennessee", - Abbreviation = "TN", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Texas", - Abbreviation = "TX", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Utah", - Abbreviation = "UT", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Vermont", - Abbreviation = "VT", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Virgin Islands", - Abbreviation = "VI", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Virginia", - Abbreviation = "VA", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Washington", - Abbreviation = "WA", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "West Virginia", - Abbreviation = "WV", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Wisconsin", - Abbreviation = "WI", - Published = true, - DisplayOrder = 1, - }); - cUsa.StateProvinces.Add(new StateProvince - { - Name = "Wyoming", - Abbreviation = "WY", - Published = true, - DisplayOrder = 1, - }); - var cCanada = new Country - { - Name = "Canada", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "CA", - ThreeLetterIsoCode = "CAN", - NumericIsoCode = 124, - SubjectToVat = false, - DisplayOrder = 100, - Published = true, - }; - cCanada.StateProvinces.Add(new StateProvince - { - Name = "Alberta", - Abbreviation = "AB", - Published = true, - DisplayOrder = 1, - }); - cCanada.StateProvinces.Add(new StateProvince - { - Name = "British Columbia", - Abbreviation = "BC", - Published = true, - DisplayOrder = 1, - }); - cCanada.StateProvinces.Add(new StateProvince - { - Name = "Manitoba", - Abbreviation = "MB", - Published = true, - DisplayOrder = 1, - }); - cCanada.StateProvinces.Add(new StateProvince - { - Name = "New Brunswick", - Abbreviation = "NB", - Published = true, - DisplayOrder = 1, - }); - cCanada.StateProvinces.Add(new StateProvince - { - Name = "Newfoundland and Labrador", - Abbreviation = "NL", - Published = true, - DisplayOrder = 1, - }); - cCanada.StateProvinces.Add(new StateProvince - { - Name = "Northwest Territories", - Abbreviation = "NT", - Published = true, - DisplayOrder = 1, - }); - cCanada.StateProvinces.Add(new StateProvince - { - Name = "Nova Scotia", - Abbreviation = "NS", - Published = true, - DisplayOrder = 1, - }); - cCanada.StateProvinces.Add(new StateProvince - { - Name = "Nunavut", - Abbreviation = "NU", - Published = true, - DisplayOrder = 1, - }); - cCanada.StateProvinces.Add(new StateProvince - { - Name = "Ontario", - Abbreviation = "ON", - Published = true, - DisplayOrder = 1, - }); - cCanada.StateProvinces.Add(new StateProvince - { - Name = "Prince Edward Island", - Abbreviation = "PE", - Published = true, - DisplayOrder = 1, - }); - cCanada.StateProvinces.Add(new StateProvince - { - Name = "Quebec", - Abbreviation = "QC", - Published = true, - DisplayOrder = 1, - }); - cCanada.StateProvinces.Add(new StateProvince - { - Name = "Saskatchewan", - Abbreviation = "SK", - Published = true, - DisplayOrder = 1, - }); - cCanada.StateProvinces.Add(new StateProvince - { - Name = "Yukon Territory", - Abbreviation = "YT", - Published = true, - DisplayOrder = 1, - }); - var countries = new List - { - cUsa, - cCanada, - //other countries - new Country - { - Name = "Argentina", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "AR", - ThreeLetterIsoCode = "ARG", - NumericIsoCode = 32, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Armenia", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "AM", - ThreeLetterIsoCode = "ARM", - NumericIsoCode = 51, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Aruba", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "AW", - ThreeLetterIsoCode = "ABW", - NumericIsoCode = 533, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Australia", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "AU", - ThreeLetterIsoCode = "AUS", - NumericIsoCode = 36, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Austria", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "AT", - ThreeLetterIsoCode = "AUT", - NumericIsoCode = 40, - SubjectToVat = true, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Azerbaijan", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "AZ", - ThreeLetterIsoCode = "AZE", - NumericIsoCode = 31, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Bahamas", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "BS", - ThreeLetterIsoCode = "BHS", - NumericIsoCode = 44, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Bangladesh", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "BD", - ThreeLetterIsoCode = "BGD", - NumericIsoCode = 50, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Belarus", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "BY", - ThreeLetterIsoCode = "BLR", - NumericIsoCode = 112, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Belgium", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "BE", - ThreeLetterIsoCode = "BEL", - NumericIsoCode = 56, - SubjectToVat = true, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Belize", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "BZ", - ThreeLetterIsoCode = "BLZ", - NumericIsoCode = 84, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Bermuda", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "BM", - ThreeLetterIsoCode = "BMU", - NumericIsoCode = 60, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Bolivia", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "BO", - ThreeLetterIsoCode = "BOL", - NumericIsoCode = 68, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Bosnia and Herzegowina", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "BA", - ThreeLetterIsoCode = "BIH", - NumericIsoCode = 70, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Brazil", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "BR", - ThreeLetterIsoCode = "BRA", - NumericIsoCode = 76, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Bulgaria", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "BG", - ThreeLetterIsoCode = "BGR", - NumericIsoCode = 100, - SubjectToVat = true, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Cayman Islands", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "KY", - ThreeLetterIsoCode = "CYM", - NumericIsoCode = 136, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Chile", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "CL", - ThreeLetterIsoCode = "CHL", - NumericIsoCode = 152, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "China", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "CN", - ThreeLetterIsoCode = "CHN", - NumericIsoCode = 156, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Colombia", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "CO", - ThreeLetterIsoCode = "COL", - NumericIsoCode = 170, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Costa Rica", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "CR", - ThreeLetterIsoCode = "CRI", - NumericIsoCode = 188, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Croatia", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "HR", - ThreeLetterIsoCode = "HRV", - NumericIsoCode = 191, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Cuba", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "CU", - ThreeLetterIsoCode = "CUB", - NumericIsoCode = 192, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Cyprus", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "CY", - ThreeLetterIsoCode = "CYP", - NumericIsoCode = 196, - SubjectToVat = true, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Czech Republic", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "CZ", - ThreeLetterIsoCode = "CZE", - NumericIsoCode = 203, - SubjectToVat = true, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Denmark", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "DK", - ThreeLetterIsoCode = "DNK", - NumericIsoCode = 208, - SubjectToVat = true, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Dominican Republic", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "DO", - ThreeLetterIsoCode = "DOM", - NumericIsoCode = 214, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "East Timor", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "TL", - ThreeLetterIsoCode = "TLS", - NumericIsoCode = 626, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Ecuador", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "EC", - ThreeLetterIsoCode = "ECU", - NumericIsoCode = 218, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Egypt", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "EG", - ThreeLetterIsoCode = "EGY", - NumericIsoCode = 818, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Finland", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "FI", - ThreeLetterIsoCode = "FIN", - NumericIsoCode = 246, - SubjectToVat = true, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "France", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "FR", - ThreeLetterIsoCode = "FRA", - NumericIsoCode = 250, - SubjectToVat = true, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Georgia", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "GE", - ThreeLetterIsoCode = "GEO", - NumericIsoCode = 268, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Germany", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "DE", - ThreeLetterIsoCode = "DEU", - NumericIsoCode = 276, - SubjectToVat = true, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Gibraltar", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "GI", - ThreeLetterIsoCode = "GIB", - NumericIsoCode = 292, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Greece", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "GR", - ThreeLetterIsoCode = "GRC", - NumericIsoCode = 300, - SubjectToVat = true, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Guatemala", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "GT", - ThreeLetterIsoCode = "GTM", - NumericIsoCode = 320, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Hong Kong", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "HK", - ThreeLetterIsoCode = "HKG", - NumericIsoCode = 344, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Hungary", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "HU", - ThreeLetterIsoCode = "HUN", - NumericIsoCode = 348, - SubjectToVat = true, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "India", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "IN", - ThreeLetterIsoCode = "IND", - NumericIsoCode = 356, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Indonesia", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "ID", - ThreeLetterIsoCode = "IDN", - NumericIsoCode = 360, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Ireland", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "IE", - ThreeLetterIsoCode = "IRL", - NumericIsoCode = 372, - SubjectToVat = true, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Israel", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "IL", - ThreeLetterIsoCode = "ISR", - NumericIsoCode = 376, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Italy", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "IT", - ThreeLetterIsoCode = "ITA", - NumericIsoCode = 380, - SubjectToVat = true, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Jamaica", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "JM", - ThreeLetterIsoCode = "JAM", - NumericIsoCode = 388, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Japan", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "JP", - ThreeLetterIsoCode = "JPN", - NumericIsoCode = 392, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Jordan", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "JO", - ThreeLetterIsoCode = "JOR", - NumericIsoCode = 400, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Kazakhstan", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "KZ", - ThreeLetterIsoCode = "KAZ", - NumericIsoCode = 398, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Korea, Democratic People's Republic of", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "KP", - ThreeLetterIsoCode = "PRK", - NumericIsoCode = 408, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Kuwait", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "KW", - ThreeLetterIsoCode = "KWT", - NumericIsoCode = 414, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Malaysia", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "MY", - ThreeLetterIsoCode = "MYS", - NumericIsoCode = 458, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Mexico", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "MX", - ThreeLetterIsoCode = "MEX", - NumericIsoCode = 484, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Netherlands", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "NL", - ThreeLetterIsoCode = "NLD", - NumericIsoCode = 528, - SubjectToVat = true, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "New Zealand", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "NZ", - ThreeLetterIsoCode = "NZL", - NumericIsoCode = 554, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Norway", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "NO", - ThreeLetterIsoCode = "NOR", - NumericIsoCode = 578, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Pakistan", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "PK", - ThreeLetterIsoCode = "PAK", - NumericIsoCode = 586, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Palestine", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "PS", - ThreeLetterIsoCode = "PSE", - NumericIsoCode = 275, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Paraguay", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "PY", - ThreeLetterIsoCode = "PRY", - NumericIsoCode = 600, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Peru", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "PE", - ThreeLetterIsoCode = "PER", - NumericIsoCode = 604, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Philippines", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "PH", - ThreeLetterIsoCode = "PHL", - NumericIsoCode = 608, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Poland", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "PL", - ThreeLetterIsoCode = "POL", - NumericIsoCode = 616, - SubjectToVat = true, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Portugal", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "PT", - ThreeLetterIsoCode = "PRT", - NumericIsoCode = 620, - SubjectToVat = true, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Puerto Rico", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "PR", - ThreeLetterIsoCode = "PRI", - NumericIsoCode = 630, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Qatar", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "QA", - ThreeLetterIsoCode = "QAT", - NumericIsoCode = 634, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Romania", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "RO", - ThreeLetterIsoCode = "ROM", - NumericIsoCode = 642, - SubjectToVat = true, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Russian Federation", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "RU", - ThreeLetterIsoCode = "RUS", - NumericIsoCode = 643, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Saudi Arabia", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "SA", - ThreeLetterIsoCode = "SAU", - NumericIsoCode = 682, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Singapore", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "SG", - ThreeLetterIsoCode = "SGP", - NumericIsoCode = 702, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Slovakia (Slovak Republic)", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "SK", - ThreeLetterIsoCode = "SVK", - NumericIsoCode = 703, - SubjectToVat = true, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Slovenia", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "SI", - ThreeLetterIsoCode = "SVN", - NumericIsoCode = 705, - SubjectToVat = true, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "South Africa", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "ZA", - ThreeLetterIsoCode = "ZAF", - NumericIsoCode = 710, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Spain", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "ES", - ThreeLetterIsoCode = "ESP", - NumericIsoCode = 724, - SubjectToVat = true, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Sweden", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "SE", - ThreeLetterIsoCode = "SWE", - NumericIsoCode = 752, - SubjectToVat = true, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Switzerland", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "CH", - ThreeLetterIsoCode = "CHE", - NumericIsoCode = 756, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Taiwan", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "TW", - ThreeLetterIsoCode = "TWN", - NumericIsoCode = 158, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Thailand", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "TH", - ThreeLetterIsoCode = "THA", - NumericIsoCode = 764, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Turkey", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "TR", - ThreeLetterIsoCode = "TUR", - NumericIsoCode = 792, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Ukraine", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "UA", - ThreeLetterIsoCode = "UKR", - NumericIsoCode = 804, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "United Arab Emirates", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "AE", - ThreeLetterIsoCode = "ARE", - NumericIsoCode = 784, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "United Kingdom", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "GB", - ThreeLetterIsoCode = "GBR", - NumericIsoCode = 826, - SubjectToVat = true, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "United States minor outlying islands", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "UM", - ThreeLetterIsoCode = "UMI", - NumericIsoCode = 581, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Uruguay", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "UY", - ThreeLetterIsoCode = "URY", - NumericIsoCode = 858, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Uzbekistan", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "UZ", - ThreeLetterIsoCode = "UZB", - NumericIsoCode = 860, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Venezuela", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "VE", - ThreeLetterIsoCode = "VEN", - NumericIsoCode = 862, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Serbia", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "RS", - ThreeLetterIsoCode = "SRB", - NumericIsoCode = 688, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Afghanistan", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "AF", - ThreeLetterIsoCode = "AFG", - NumericIsoCode = 4, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Albania", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "AL", - ThreeLetterIsoCode = "ALB", - NumericIsoCode = 8, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Algeria", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "DZ", - ThreeLetterIsoCode = "DZA", - NumericIsoCode = 12, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "American Samoa", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "AS", - ThreeLetterIsoCode = "ASM", - NumericIsoCode = 16, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Andorra", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "AD", - ThreeLetterIsoCode = "AND", - NumericIsoCode = 20, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Angola", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "AO", - ThreeLetterIsoCode = "AGO", - NumericIsoCode = 24, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Anguilla", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "AI", - ThreeLetterIsoCode = "AIA", - NumericIsoCode = 660, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Antarctica", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "AQ", - ThreeLetterIsoCode = "ATA", - NumericIsoCode = 10, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Antigua and Barbuda", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "AG", - ThreeLetterIsoCode = "ATG", - NumericIsoCode = 28, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Bahrain", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "BH", - ThreeLetterIsoCode = "BHR", - NumericIsoCode = 48, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Barbados", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "BB", - ThreeLetterIsoCode = "BRB", - NumericIsoCode = 52, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Benin", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "BJ", - ThreeLetterIsoCode = "BEN", - NumericIsoCode = 204, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Bhutan", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "BT", - ThreeLetterIsoCode = "BTN", - NumericIsoCode = 64, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Botswana", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "BW", - ThreeLetterIsoCode = "BWA", - NumericIsoCode = 72, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Bouvet Island", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "BV", - ThreeLetterIsoCode = "BVT", - NumericIsoCode = 74, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "British Indian Ocean Territory", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "IO", - ThreeLetterIsoCode = "IOT", - NumericIsoCode = 86, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Brunei Darussalam", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "BN", - ThreeLetterIsoCode = "BRN", - NumericIsoCode = 96, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Burkina Faso", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "BF", - ThreeLetterIsoCode = "BFA", - NumericIsoCode = 854, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Burundi", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "BI", - ThreeLetterIsoCode = "BDI", - NumericIsoCode = 108, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Cambodia", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "KH", - ThreeLetterIsoCode = "KHM", - NumericIsoCode = 116, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Cameroon", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "CM", - ThreeLetterIsoCode = "CMR", - NumericIsoCode = 120, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Cape Verde", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "CV", - ThreeLetterIsoCode = "CPV", - NumericIsoCode = 132, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Central African Republic", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "CF", - ThreeLetterIsoCode = "CAF", - NumericIsoCode = 140, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Chad", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "TD", - ThreeLetterIsoCode = "TCD", - NumericIsoCode = 148, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Christmas Island", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "CX", - ThreeLetterIsoCode = "CXR", - NumericIsoCode = 162, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Cocos (Keeling) Islands", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "CC", - ThreeLetterIsoCode = "CCK", - NumericIsoCode = 166, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Comoros", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "KM", - ThreeLetterIsoCode = "COM", - NumericIsoCode = 174, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Congo", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "CG", - ThreeLetterIsoCode = "COG", - NumericIsoCode = 178, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Congo (Democratic Republic of the)", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "CD", - ThreeLetterIsoCode = "COD", - NumericIsoCode = 180, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Cook Islands", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "CK", - ThreeLetterIsoCode = "COK", - NumericIsoCode = 184, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Cote D'Ivoire", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "CI", - ThreeLetterIsoCode = "CIV", - NumericIsoCode = 384, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Djibouti", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "DJ", - ThreeLetterIsoCode = "DJI", - NumericIsoCode = 262, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Dominica", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "DM", - ThreeLetterIsoCode = "DMA", - NumericIsoCode = 212, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "El Salvador", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "SV", - ThreeLetterIsoCode = "SLV", - NumericIsoCode = 222, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Equatorial Guinea", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "GQ", - ThreeLetterIsoCode = "GNQ", - NumericIsoCode = 226, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Eritrea", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "ER", - ThreeLetterIsoCode = "ERI", - NumericIsoCode = 232, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Estonia", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "EE", - ThreeLetterIsoCode = "EST", - NumericIsoCode = 233, - SubjectToVat = true, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Ethiopia", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "ET", - ThreeLetterIsoCode = "ETH", - NumericIsoCode = 231, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Falkland Islands (Malvinas)", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "FK", - ThreeLetterIsoCode = "FLK", - NumericIsoCode = 238, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Faroe Islands", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "FO", - ThreeLetterIsoCode = "FRO", - NumericIsoCode = 234, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Fiji", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "FJ", - ThreeLetterIsoCode = "FJI", - NumericIsoCode = 242, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "French Guiana", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "GF", - ThreeLetterIsoCode = "GUF", - NumericIsoCode = 254, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "French Polynesia", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "PF", - ThreeLetterIsoCode = "PYF", - NumericIsoCode = 258, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "French Southern Territories", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "TF", - ThreeLetterIsoCode = "ATF", - NumericIsoCode = 260, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Gabon", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "GA", - ThreeLetterIsoCode = "GAB", - NumericIsoCode = 266, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Gambia", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "GM", - ThreeLetterIsoCode = "GMB", - NumericIsoCode = 270, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Ghana", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "GH", - ThreeLetterIsoCode = "GHA", - NumericIsoCode = 288, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Greenland", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "GL", - ThreeLetterIsoCode = "GRL", - NumericIsoCode = 304, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Grenada", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "GD", - ThreeLetterIsoCode = "GRD", - NumericIsoCode = 308, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Guadeloupe", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "GP", - ThreeLetterIsoCode = "GLP", - NumericIsoCode = 312, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Guam", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "GU", - ThreeLetterIsoCode = "GUM", - NumericIsoCode = 316, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Guinea", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "GN", - ThreeLetterIsoCode = "GIN", - NumericIsoCode = 324, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Guinea-bissau", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "GW", - ThreeLetterIsoCode = "GNB", - NumericIsoCode = 624, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Guyana", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "GY", - ThreeLetterIsoCode = "GUY", - NumericIsoCode = 328, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Haiti", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "HT", - ThreeLetterIsoCode = "HTI", - NumericIsoCode = 332, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Heard and Mc Donald Islands", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "HM", - ThreeLetterIsoCode = "HMD", - NumericIsoCode = 334, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Honduras", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "HN", - ThreeLetterIsoCode = "HND", - NumericIsoCode = 340, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Iceland", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "IS", - ThreeLetterIsoCode = "ISL", - NumericIsoCode = 352, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Iran (Islamic Republic of)", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "IR", - ThreeLetterIsoCode = "IRN", - NumericIsoCode = 364, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Iraq", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "IQ", - ThreeLetterIsoCode = "IRQ", - NumericIsoCode = 368, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Kenya", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "KE", - ThreeLetterIsoCode = "KEN", - NumericIsoCode = 404, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Kiribati", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "KI", - ThreeLetterIsoCode = "KIR", - NumericIsoCode = 296, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Korea", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "KR", - ThreeLetterIsoCode = "KOR", - NumericIsoCode = 410, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Kyrgyzstan", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "KG", - ThreeLetterIsoCode = "KGZ", - NumericIsoCode = 417, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Lao People's Democratic Republic", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "LA", - ThreeLetterIsoCode = "LAO", - NumericIsoCode = 418, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Latvia", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "LV", - ThreeLetterIsoCode = "LVA", - NumericIsoCode = 428, - SubjectToVat = true, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Lebanon", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "LB", - ThreeLetterIsoCode = "LBN", - NumericIsoCode = 422, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Lesotho", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "LS", - ThreeLetterIsoCode = "LSO", - NumericIsoCode = 426, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Liberia", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "LR", - ThreeLetterIsoCode = "LBR", - NumericIsoCode = 430, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Libyan Arab Jamahiriya", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "LY", - ThreeLetterIsoCode = "LBY", - NumericIsoCode = 434, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Liechtenstein", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "LI", - ThreeLetterIsoCode = "LIE", - NumericIsoCode = 438, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Lithuania", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "LT", - ThreeLetterIsoCode = "LTU", - NumericIsoCode = 440, - SubjectToVat = true, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Luxembourg", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "LU", - ThreeLetterIsoCode = "LUX", - NumericIsoCode = 442, - SubjectToVat = true, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Macau", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "MO", - ThreeLetterIsoCode = "MAC", - NumericIsoCode = 446, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Macedonia", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "MK", - ThreeLetterIsoCode = "MKD", - NumericIsoCode = 807, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Madagascar", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "MG", - ThreeLetterIsoCode = "MDG", - NumericIsoCode = 450, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Malawi", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "MW", - ThreeLetterIsoCode = "MWI", - NumericIsoCode = 454, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Maldives", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "MV", - ThreeLetterIsoCode = "MDV", - NumericIsoCode = 462, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Mali", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "ML", - ThreeLetterIsoCode = "MLI", - NumericIsoCode = 466, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Malta", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "MT", - ThreeLetterIsoCode = "MLT", - NumericIsoCode = 470, - SubjectToVat = true, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Marshall Islands", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "MH", - ThreeLetterIsoCode = "MHL", - NumericIsoCode = 584, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Martinique", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "MQ", - ThreeLetterIsoCode = "MTQ", - NumericIsoCode = 474, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Mauritania", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "MR", - ThreeLetterIsoCode = "MRT", - NumericIsoCode = 478, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Mauritius", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "MU", - ThreeLetterIsoCode = "MUS", - NumericIsoCode = 480, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Mayotte", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "YT", - ThreeLetterIsoCode = "MYT", - NumericIsoCode = 175, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Micronesia", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "FM", - ThreeLetterIsoCode = "FSM", - NumericIsoCode = 583, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Moldova", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "MD", - ThreeLetterIsoCode = "MDA", - NumericIsoCode = 498, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Monaco", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "MC", - ThreeLetterIsoCode = "MCO", - NumericIsoCode = 492, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Mongolia", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "MN", - ThreeLetterIsoCode = "MNG", - NumericIsoCode = 496, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Montenegro", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "ME", - ThreeLetterIsoCode = "MNE", - NumericIsoCode = 499, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Montserrat", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "MS", - ThreeLetterIsoCode = "MSR", - NumericIsoCode = 500, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Morocco", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "MA", - ThreeLetterIsoCode = "MAR", - NumericIsoCode = 504, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Mozambique", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "MZ", - ThreeLetterIsoCode = "MOZ", - NumericIsoCode = 508, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Myanmar", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "MM", - ThreeLetterIsoCode = "MMR", - NumericIsoCode = 104, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Namibia", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "NA", - ThreeLetterIsoCode = "NAM", - NumericIsoCode = 516, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Nauru", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "NR", - ThreeLetterIsoCode = "NRU", - NumericIsoCode = 520, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Nepal", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "NP", - ThreeLetterIsoCode = "NPL", - NumericIsoCode = 524, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Netherlands Antilles", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "AN", - ThreeLetterIsoCode = "ANT", - NumericIsoCode = 530, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "New Caledonia", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "NC", - ThreeLetterIsoCode = "NCL", - NumericIsoCode = 540, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Nicaragua", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "NI", - ThreeLetterIsoCode = "NIC", - NumericIsoCode = 558, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Niger", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "NE", - ThreeLetterIsoCode = "NER", - NumericIsoCode = 562, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Nigeria", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "NG", - ThreeLetterIsoCode = "NGA", - NumericIsoCode = 566, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Niue", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "NU", - ThreeLetterIsoCode = "NIU", - NumericIsoCode = 570, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Norfolk Island", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "NF", - ThreeLetterIsoCode = "NFK", - NumericIsoCode = 574, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Northern Mariana Islands", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "MP", - ThreeLetterIsoCode = "MNP", - NumericIsoCode = 580, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Oman", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "OM", - ThreeLetterIsoCode = "OMN", - NumericIsoCode = 512, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Palau", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "PW", - ThreeLetterIsoCode = "PLW", - NumericIsoCode = 585, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Panama", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "PA", - ThreeLetterIsoCode = "PAN", - NumericIsoCode = 591, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Papua New Guinea", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "PG", - ThreeLetterIsoCode = "PNG", - NumericIsoCode = 598, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Pitcairn", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "PN", - ThreeLetterIsoCode = "PCN", - NumericIsoCode = 612, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Reunion", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "RE", - ThreeLetterIsoCode = "REU", - NumericIsoCode = 638, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Rwanda", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "RW", - ThreeLetterIsoCode = "RWA", - NumericIsoCode = 646, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Saint Kitts and Nevis", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "KN", - ThreeLetterIsoCode = "KNA", - NumericIsoCode = 659, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Saint Lucia", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "LC", - ThreeLetterIsoCode = "LCA", - NumericIsoCode = 662, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Saint Vincent and the Grenadines", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "VC", - ThreeLetterIsoCode = "VCT", - NumericIsoCode = 670, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Samoa", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "WS", - ThreeLetterIsoCode = "WSM", - NumericIsoCode = 882, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "San Marino", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "SM", - ThreeLetterIsoCode = "SMR", - NumericIsoCode = 674, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Sao Tome and Principe", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "ST", - ThreeLetterIsoCode = "STP", - NumericIsoCode = 678, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Senegal", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "SN", - ThreeLetterIsoCode = "SEN", - NumericIsoCode = 686, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Seychelles", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "SC", - ThreeLetterIsoCode = "SYC", - NumericIsoCode = 690, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Sierra Leone", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "SL", - ThreeLetterIsoCode = "SLE", - NumericIsoCode = 694, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Solomon Islands", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "SB", - ThreeLetterIsoCode = "SLB", - NumericIsoCode = 90, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Somalia", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "SO", - ThreeLetterIsoCode = "SOM", - NumericIsoCode = 706, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "South Georgia & South Sandwich Islands", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "GS", - ThreeLetterIsoCode = "SGS", - NumericIsoCode = 239, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "South Sudan", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "SS", - ThreeLetterIsoCode = "SSD", - NumericIsoCode = 728, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Sri Lanka", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "LK", - ThreeLetterIsoCode = "LKA", - NumericIsoCode = 144, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "St. Helena", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "SH", - ThreeLetterIsoCode = "SHN", - NumericIsoCode = 654, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "St. Pierre and Miquelon", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "PM", - ThreeLetterIsoCode = "SPM", - NumericIsoCode = 666, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Sudan", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "SD", - ThreeLetterIsoCode = "SDN", - NumericIsoCode = 736, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Suriname", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "SR", - ThreeLetterIsoCode = "SUR", - NumericIsoCode = 740, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Svalbard and Jan Mayen Islands", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "SJ", - ThreeLetterIsoCode = "SJM", - NumericIsoCode = 744, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Swaziland", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "SZ", - ThreeLetterIsoCode = "SWZ", - NumericIsoCode = 748, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Syrian Arab Republic", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "SY", - ThreeLetterIsoCode = "SYR", - NumericIsoCode = 760, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Tajikistan", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "TJ", - ThreeLetterIsoCode = "TJK", - NumericIsoCode = 762, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Tanzania", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "TZ", - ThreeLetterIsoCode = "TZA", - NumericIsoCode = 834, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Togo", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "TG", - ThreeLetterIsoCode = "TGO", - NumericIsoCode = 768, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Tokelau", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "TK", - ThreeLetterIsoCode = "TKL", - NumericIsoCode = 772, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Tonga", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "TO", - ThreeLetterIsoCode = "TON", - NumericIsoCode = 776, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Trinidad and Tobago", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "TT", - ThreeLetterIsoCode = "TTO", - NumericIsoCode = 780, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Tunisia", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "TN", - ThreeLetterIsoCode = "TUN", - NumericIsoCode = 788, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Turkmenistan", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "TM", - ThreeLetterIsoCode = "TKM", - NumericIsoCode = 795, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Turks and Caicos Islands", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "TC", - ThreeLetterIsoCode = "TCA", - NumericIsoCode = 796, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Tuvalu", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "TV", - ThreeLetterIsoCode = "TUV", - NumericIsoCode = 798, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Uganda", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "UG", - ThreeLetterIsoCode = "UGA", - NumericIsoCode = 800, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Vanuatu", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "VU", - ThreeLetterIsoCode = "VUT", - NumericIsoCode = 548, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Vatican City State (Holy See)", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "VA", - ThreeLetterIsoCode = "VAT", - NumericIsoCode = 336, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Viet Nam", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "VN", - ThreeLetterIsoCode = "VNM", - NumericIsoCode = 704, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Virgin Islands (British)", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "VG", - ThreeLetterIsoCode = "VGB", - NumericIsoCode = 92, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Virgin Islands (U.S.)", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "VI", - ThreeLetterIsoCode = "VIR", - NumericIsoCode = 850, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Wallis and Futuna Islands", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "WF", - ThreeLetterIsoCode = "WLF", - NumericIsoCode = 876, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Western Sahara", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "EH", - ThreeLetterIsoCode = "ESH", - NumericIsoCode = 732, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Yemen", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "YE", - ThreeLetterIsoCode = "YEM", - NumericIsoCode = 887, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Zambia", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "ZM", - ThreeLetterIsoCode = "ZMB", - NumericIsoCode = 894, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - new Country - { - Name = "Zimbabwe", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "ZW", - ThreeLetterIsoCode = "ZWE", - NumericIsoCode = 716, - SubjectToVat = false, - DisplayOrder = 100, - Published = true - }, - }; - _countryRepository.Insert(countries); - } - - protected virtual void InstallShippingMethods() - { - var shippingMethods = new List - { - new ShippingMethod - { - Name = "Ground", - Description ="Compared to other shipping methods, ground shipping is carried out closer to the earth", - DisplayOrder = 1 - }, - new ShippingMethod - { - Name = "Next Day Air", - Description ="The one day air shipping", - DisplayOrder = 3 - }, - new ShippingMethod - { - Name = "2nd Day Air", - Description ="The two day air shipping", - DisplayOrder = 3 - } - }; - _shippingMethodRepository.Insert(shippingMethods); - } - - protected virtual void InstallDeliveryDates() - { - var deliveryDates = new List - { - new DeliveryDate - { - Name = "1-2 days", - DisplayOrder = 1 - }, - new DeliveryDate - { - Name = "3-5 days", - DisplayOrder = 5 - }, - new DeliveryDate - { - Name = "1 week", - DisplayOrder = 10 - }, - }; - _deliveryDateRepository.Insert(deliveryDates); - } - - protected virtual void InstallProductAvailabilityRanges() - { - var productAvailabilityRanges = new List - { - new ProductAvailabilityRange - { - Name = "2-4 days", - DisplayOrder = 1 - }, - new ProductAvailabilityRange - { - Name = "7-10 days", - DisplayOrder = 2 - }, - new ProductAvailabilityRange - { - Name = "2 week", - DisplayOrder = 3 - }, - }; - _productAvailabilityRangeRepository.Insert(productAvailabilityRanges); - } - - protected virtual void InstallCustomersAndUsers(string defaultUserEmail, string defaultUserPassword) - { - var crAdministrators = new CustomerRole - { - Name = "Administrators", - Active = true, - IsSystemRole = true, - SystemName = SystemCustomerRoleNames.Administrators, - }; - var crForumModerators = new CustomerRole - { - Name = "Forum Moderators", - Active = true, - IsSystemRole = true, - SystemName = SystemCustomerRoleNames.ForumModerators, - }; - var crRegistered = new CustomerRole - { - Name = "Registered", - Active = true, - IsSystemRole = true, - SystemName = SystemCustomerRoleNames.Registered, - }; - var crGuests = new CustomerRole - { - Name = "Guests", - Active = true, - IsSystemRole = true, - SystemName = SystemCustomerRoleNames.Guests, - }; - var crVendors = new CustomerRole - { - Name = "Vendors", - Active = true, - IsSystemRole = true, - SystemName = SystemCustomerRoleNames.Vendors, - }; - var customerRoles = new List - { - crAdministrators, - crForumModerators, - crRegistered, - crGuests, - crVendors - }; - _customerRoleRepository.Insert(customerRoles); - - //default store - var defaultStore = _storeRepository.Table.FirstOrDefault(); - - if (defaultStore == null) - throw new Exception("No default store could be loaded"); - - var storeId = defaultStore.Id; - - //admin user - var adminUser = new Customer - { - CustomerGuid = Guid.NewGuid(), - Email = defaultUserEmail, - Username = defaultUserEmail, - Active = true, - CreatedOnUtc = DateTime.UtcNow, - LastActivityDateUtc = DateTime.UtcNow, - RegisteredInStoreId = storeId - }; - - var defaultAdminUserAddress = new Address - { - FirstName = "John", - LastName = "Smith", - PhoneNumber = "12345678", - Email = defaultUserEmail, - FaxNumber = "", - Company = "Nop Solutions Ltd", - Address1 = "21 West 52nd Street", - Address2 = "", - City = "New York", - StateProvince = _stateProvinceRepository.Table.FirstOrDefault(sp => sp.Name == "New York"), - Country = _countryRepository.Table.FirstOrDefault(c => c.ThreeLetterIsoCode == "USA"), - ZipPostalCode = "10021", - CreatedOnUtc = DateTime.UtcNow, - }; - adminUser.Addresses.Add(defaultAdminUserAddress); - adminUser.BillingAddress = defaultAdminUserAddress; - adminUser.ShippingAddress = defaultAdminUserAddress; - - adminUser.CustomerRoles.Add(crAdministrators); - adminUser.CustomerRoles.Add(crForumModerators); - adminUser.CustomerRoles.Add(crRegistered); - - _customerRepository.Insert(adminUser); - //set default customer name - _genericAttributeService.SaveAttribute(adminUser, SystemCustomerAttributeNames.FirstName, "John"); - _genericAttributeService.SaveAttribute(adminUser, SystemCustomerAttributeNames.LastName, "Smith"); - - //set hashed admin password - var customerRegistrationService = EngineContext.Current.Resolve(); - customerRegistrationService.ChangePassword(new ChangePasswordRequest(defaultUserEmail, false, - PasswordFormat.Hashed, defaultUserPassword)); - - //second user - var secondUserEmail = "steve_gates@nopCommerce.com"; - var secondUser = new Customer - { - CustomerGuid = Guid.NewGuid(), - Email = secondUserEmail, - Username = secondUserEmail, - Active = true, - CreatedOnUtc = DateTime.UtcNow, - LastActivityDateUtc = DateTime.UtcNow, - RegisteredInStoreId = storeId - }; - var defaultSecondUserAddress = new Address - { - FirstName = "Steve", - LastName = "Gates", - PhoneNumber = "87654321", - Email = secondUserEmail, - FaxNumber = "", - Company = "Steve Company", - Address1 = "750 Bel Air Rd.", - Address2 = "", - City = "Los Angeles", - StateProvince = _stateProvinceRepository.Table.FirstOrDefault(sp => sp.Name == "California"), - Country = _countryRepository.Table.FirstOrDefault(c => c.ThreeLetterIsoCode == "USA"), - ZipPostalCode = "90077", - CreatedOnUtc = DateTime.UtcNow, - }; - secondUser.Addresses.Add(defaultSecondUserAddress); - secondUser.BillingAddress = defaultSecondUserAddress; - secondUser.ShippingAddress = defaultSecondUserAddress; - - secondUser.CustomerRoles.Add(crRegistered); - - _customerRepository.Insert(secondUser); - //set default customer name - _genericAttributeService.SaveAttribute(secondUser, SystemCustomerAttributeNames.FirstName, defaultSecondUserAddress.FirstName); - _genericAttributeService.SaveAttribute(secondUser, SystemCustomerAttributeNames.LastName, defaultSecondUserAddress.LastName); - - //set customer password - _customerPasswordRepository.Insert(new CustomerPassword - { - Customer = secondUser, - Password = "123456", - PasswordFormat = PasswordFormat.Clear, - PasswordSalt = string.Empty, - CreatedOnUtc = DateTime.UtcNow - }); - - //third user - var thirdUserEmail = "arthur_holmes@nopCommerce.com"; - var thirdUser = new Customer - { - CustomerGuid = Guid.NewGuid(), - Email = thirdUserEmail, - Username = thirdUserEmail, - Active = true, - CreatedOnUtc = DateTime.UtcNow, - LastActivityDateUtc = DateTime.UtcNow, - RegisteredInStoreId = storeId - }; - var defaultThirdUserAddress = new Address - { - FirstName = "Arthur", - LastName = "Holmes", - PhoneNumber = "111222333", - Email = thirdUserEmail, - FaxNumber = "", - Company = "Holmes Company", - Address1 = "221B Baker Street", - Address2 = "", - City = "London", - Country = _countryRepository.Table.FirstOrDefault(c => c.ThreeLetterIsoCode == "GBR"), - ZipPostalCode = "NW1 6XE", - CreatedOnUtc = DateTime.UtcNow, - }; - thirdUser.Addresses.Add(defaultThirdUserAddress); - thirdUser.BillingAddress = defaultThirdUserAddress; - thirdUser.ShippingAddress = defaultThirdUserAddress; - - thirdUser.CustomerRoles.Add(crRegistered); - - _customerRepository.Insert(thirdUser); - //set default customer name - _genericAttributeService.SaveAttribute(thirdUser, SystemCustomerAttributeNames.FirstName, defaultThirdUserAddress.FirstName); - _genericAttributeService.SaveAttribute(thirdUser, SystemCustomerAttributeNames.LastName, defaultThirdUserAddress.LastName); - - //set customer password - _customerPasswordRepository.Insert(new CustomerPassword - { - Customer = thirdUser, - Password = "123456", - PasswordFormat = PasswordFormat.Clear, - PasswordSalt = string.Empty, - CreatedOnUtc = DateTime.UtcNow - }); - - //fourth user - var fourthUserEmail = "james_pan@nopCommerce.com"; - var fourthUser = new Customer - { - CustomerGuid = Guid.NewGuid(), - Email = fourthUserEmail, - Username = fourthUserEmail, - Active = true, - CreatedOnUtc = DateTime.UtcNow, - LastActivityDateUtc = DateTime.UtcNow, - RegisteredInStoreId = storeId - }; - var defaultFourthUserAddress = new Address - { - FirstName = "James", - LastName = "Pan", - PhoneNumber = "369258147", - Email = fourthUserEmail, - FaxNumber = "", - Company = "Pan Company", - Address1 = "St Katharines West 16", - Address2 = "", - City = "St Andrews", - Country = _countryRepository.Table.FirstOrDefault(c => c.ThreeLetterIsoCode == "GBR"), - ZipPostalCode = "KY16 9AX", - CreatedOnUtc = DateTime.UtcNow, - }; - fourthUser.Addresses.Add(defaultFourthUserAddress); - fourthUser.BillingAddress = defaultFourthUserAddress; - fourthUser.ShippingAddress = defaultFourthUserAddress; - - fourthUser.CustomerRoles.Add(crRegistered); - - _customerRepository.Insert(fourthUser); - //set default customer name - _genericAttributeService.SaveAttribute(fourthUser, SystemCustomerAttributeNames.FirstName, defaultFourthUserAddress.FirstName); - _genericAttributeService.SaveAttribute(fourthUser, SystemCustomerAttributeNames.LastName, defaultFourthUserAddress.LastName); - - //set customer password - _customerPasswordRepository.Insert(new CustomerPassword - { - Customer = fourthUser, - Password = "123456", - PasswordFormat = PasswordFormat.Clear, - PasswordSalt = string.Empty, - CreatedOnUtc = DateTime.UtcNow - }); - - //fifth user - var fifthUserEmail = "brenda_lindgren@nopCommerce.com"; - var fifthUser = new Customer - { - CustomerGuid = Guid.NewGuid(), - Email = fifthUserEmail, - Username = fifthUserEmail, - Active = true, - CreatedOnUtc = DateTime.UtcNow, - LastActivityDateUtc = DateTime.UtcNow, - RegisteredInStoreId = storeId - }; - var defaultFifthUserAddress = new Address - { - FirstName = "Brenda", - LastName = "Lindgren", - PhoneNumber = "14785236", - Email = fifthUserEmail, - FaxNumber = "", - Company = "Brenda Company", - Address1 = "1249 Tongass Avenue, Suite B", - Address2 = "", - City = "Ketchikan", - StateProvince = _stateProvinceRepository.Table.FirstOrDefault(sp => sp.Name == "Alaska"), - Country = _countryRepository.Table.FirstOrDefault(c => c.ThreeLetterIsoCode == "USA"), - ZipPostalCode = "99901", - CreatedOnUtc = DateTime.UtcNow, - }; - fifthUser.Addresses.Add(defaultFifthUserAddress); - fifthUser.BillingAddress = defaultFifthUserAddress; - fifthUser.ShippingAddress = defaultFifthUserAddress; - - fifthUser.CustomerRoles.Add(crRegistered); - - _customerRepository.Insert(fifthUser); - //set default customer name - _genericAttributeService.SaveAttribute(fifthUser, SystemCustomerAttributeNames.FirstName, defaultFifthUserAddress.FirstName); - _genericAttributeService.SaveAttribute(fifthUser, SystemCustomerAttributeNames.LastName, defaultFifthUserAddress.LastName); - - //set customer password - _customerPasswordRepository.Insert(new CustomerPassword - { - Customer = fifthUser, - Password = "123456", - PasswordFormat = PasswordFormat.Clear, - PasswordSalt = string.Empty, - CreatedOnUtc = DateTime.UtcNow - }); - - //sixth user - var sixthUserEmail = "victoria_victoria@nopCommerce.com"; - var sixthUser = new Customer - { - CustomerGuid = Guid.NewGuid(), - Email = sixthUserEmail, - Username = sixthUserEmail, - Active = true, - CreatedOnUtc = DateTime.UtcNow, - LastActivityDateUtc = DateTime.UtcNow, - RegisteredInStoreId = storeId - }; - var defaultSixthUserAddress = new Address - { - FirstName = "Victoria", - LastName = "Terces", - PhoneNumber = "45612378", - Email = sixthUserEmail, - FaxNumber = "", - Company = "Terces Company", - Address1 = "201 1st Avenue South", - Address2 = "", - City = "Saskatoon", - StateProvince = _stateProvinceRepository.Table.FirstOrDefault(sp => sp.Name == "Saskatchewan"), - Country = _countryRepository.Table.FirstOrDefault(c => c.ThreeLetterIsoCode == "CAN"), - ZipPostalCode = "S7K 1J9", - CreatedOnUtc = DateTime.UtcNow, - }; - sixthUser.Addresses.Add(defaultSixthUserAddress); - sixthUser.BillingAddress = defaultSixthUserAddress; - sixthUser.ShippingAddress = defaultSixthUserAddress; - - sixthUser.CustomerRoles.Add(crRegistered); - - _customerRepository.Insert(sixthUser); - //set default customer name - _genericAttributeService.SaveAttribute(sixthUser, SystemCustomerAttributeNames.FirstName, defaultSixthUserAddress.FirstName); - _genericAttributeService.SaveAttribute(sixthUser, SystemCustomerAttributeNames.LastName, defaultSixthUserAddress.LastName); - - //set customer password - _customerPasswordRepository.Insert(new CustomerPassword - { - Customer = sixthUser, - Password = "123456", - PasswordFormat = PasswordFormat.Clear, - PasswordSalt = string.Empty, - CreatedOnUtc = DateTime.UtcNow - }); - - //search engine (crawler) built-in user - var searchEngineUser = new Customer - { - Email = "builtin@search_engine_record.com", - CustomerGuid = Guid.NewGuid(), - AdminComment = "Built-in system guest record used for requests from search engines.", - Active = true, - IsSystemAccount = true, - SystemName = SystemCustomerNames.SearchEngine, - CreatedOnUtc = DateTime.UtcNow, - LastActivityDateUtc = DateTime.UtcNow, - RegisteredInStoreId = storeId - }; - searchEngineUser.CustomerRoles.Add(crGuests); - _customerRepository.Insert(searchEngineUser); - - - //built-in user for background tasks - var backgroundTaskUser = new Customer - { - Email = "builtin@background-task-record.com", - CustomerGuid = Guid.NewGuid(), - AdminComment = "Built-in system record used for background tasks.", - Active = true, - IsSystemAccount = true, - SystemName = SystemCustomerNames.BackgroundTask, - CreatedOnUtc = DateTime.UtcNow, - LastActivityDateUtc = DateTime.UtcNow, - RegisteredInStoreId = storeId - }; - backgroundTaskUser.CustomerRoles.Add(crGuests); - _customerRepository.Insert(backgroundTaskUser); - } - - protected virtual void InstallOrders() - { - //default store - var defaultStore = _storeRepository.Table.FirstOrDefault(); - if (defaultStore == null) - throw new Exception("No default store could be loaded"); - - //first order - var firstCustomer = _customerRepository.Table.First(c => c.Email.Equals("steve_gates@nopCommerce.com")); - var firstOrder = new Order() - { - StoreId = defaultStore.Id, - OrderGuid = Guid.NewGuid(), - Customer = firstCustomer, - CustomerLanguageId = _languageRepository.Table.First().Id, - CustomerIp = "127.0.0.1", - OrderSubtotalInclTax = 1855M, - OrderSubtotalExclTax = 1855M, - OrderSubTotalDiscountInclTax = decimal.Zero, - OrderSubTotalDiscountExclTax = decimal.Zero, - OrderShippingInclTax = decimal.Zero, - OrderShippingExclTax = decimal.Zero, - OrderShippingNonTaxable = decimal.Zero, - PaymentMethodAdditionalFeeInclTax = decimal.Zero, - PaymentMethodAdditionalFeeExclTax = decimal.Zero, - PaymentMethodAdditionalFeeNonTaxable = decimal.Zero, - TaxRates = "0:0;", - OrderTax = decimal.Zero, - OrderTotal = 1855M, - OrderAmount = 1855M, - OrderAmountIncl = 1855M, - OrderDiscountIncl = decimal.Zero, - EarnedRewardPointsBaseAmountIncl = 1855M, - EarnedRewardPointsBaseAmountExcl = 1855M, - RefundedAmount = decimal.Zero, - OrderDiscount = decimal.Zero, - CheckoutAttributeDescription = string.Empty, - CheckoutAttributesXml = string.Empty, - CustomerCurrencyCode = "USD", - CurrencyRate = 1M, - AffiliateId = 0, - OrderStatus = OrderStatus.Processing, - AllowStoringCreditCardNumber = false, - CardType = string.Empty, - CardName = string.Empty, - CardNumber = string.Empty, - MaskedCreditCardNumber = string.Empty, - CardCvv2 = string.Empty, - CardExpirationMonth = string.Empty, - CardExpirationYear = string.Empty, - PaymentMethodSystemName = "Payments.CheckMoneyOrder", - AuthorizationTransactionId = string.Empty, - AuthorizationTransactionCode = string.Empty, - AuthorizationTransactionResult = string.Empty, - CaptureTransactionId = string.Empty, - CaptureTransactionResult = string.Empty, - SubscriptionTransactionId = string.Empty, - PaymentStatus = PaymentStatus.Paid, - PaidDateUtc = DateTime.UtcNow, - BillingAddress = (Address)firstCustomer.BillingAddress.Clone(), - ShippingAddress = (Address)firstCustomer.ShippingAddress.Clone(), - ShippingStatus = ShippingStatus.NotYetShipped, - ShippingMethod = "Ground", - PickUpInStore = false, - ShippingRateComputationMethodSystemName = "Shipping.FixedOrByWeight", - CustomValuesXml = string.Empty, - VatNumber = string.Empty, - CreatedOnUtc = DateTime.UtcNow, - CustomOrderNumber = string.Empty - }; - _orderRepository.Insert(firstOrder); - firstOrder.CustomOrderNumber = firstOrder.Id.ToString(); - _orderRepository.Update(firstOrder); - - //item Apple iCam - var firstOrderItem1 = new OrderItem() - { - OrderItemGuid = Guid.NewGuid(), - Order = firstOrder, - ProductId = _productRepository.Table.First(p => p.Name.Equals("Apple iCam")).Id, - UnitPriceInclTax = 1300M, - UnitPriceExclTax = 1300M, - PriceInclTax = 1300M, - PriceExclTax = 1300M, - OriginalProductCost = decimal.Zero, - AttributeDescription = string.Empty, - AttributesXml = string.Empty, - Quantity = 1, - DiscountAmountInclTax = decimal.Zero, - DiscountAmountExclTax = decimal.Zero, - DownloadCount = 0, - IsDownloadActivated = false, - LicenseDownloadId = 0, - ItemWeight = null, - RentalStartDateUtc = null, - RentalEndDateUtc = null, - TaxRate = 0 - }; - _orderItemRepository.Insert(firstOrderItem1); - - //item Leica T Mirrorless Digital Camera - var fierstOrderItem2 = new OrderItem() - { - OrderItemGuid = Guid.NewGuid(), - Order = firstOrder, - ProductId = _productRepository.Table.First(p => p.Name.Equals("Leica T Mirrorless Digital Camera")).Id, - UnitPriceInclTax = 530M, - UnitPriceExclTax = 530M, - PriceInclTax = 530M, - PriceExclTax = 530M, - OriginalProductCost = decimal.Zero, - AttributeDescription = string.Empty, - AttributesXml = string.Empty, - Quantity = 1, - DiscountAmountInclTax = decimal.Zero, - DiscountAmountExclTax = decimal.Zero, - DownloadCount = 0, - IsDownloadActivated = false, - LicenseDownloadId = 0, - ItemWeight = null, - RentalStartDateUtc = null, - RentalEndDateUtc = null, - TaxRate = 0 - }; - _orderItemRepository.Insert(fierstOrderItem2); - - //item $25 Virtual Gift Card - var firstOrderItem3 = new OrderItem() - { - OrderItemGuid = Guid.NewGuid(), - Order = firstOrder, - ProductId = _productRepository.Table.First(p => p.Name.Equals("$25 Virtual Gift Card")).Id, - UnitPriceInclTax = 25M, - UnitPriceExclTax = 25M, - PriceInclTax = 25M, - PriceExclTax = 25M, - OriginalProductCost = decimal.Zero, - AttributeDescription = "From: Steve Gates <steve_gates@nopCommerce.com>
    For: Brenda Lindgren <brenda_lindgren@nopCommerce.com>", - AttributesXml = "Brenda Lindgrenbrenda_lindgren@nopCommerce.comSteve Gatessteve_gates@gmail.com", - Quantity = 1, - DiscountAmountInclTax = decimal.Zero, - DiscountAmountExclTax = decimal.Zero, - DownloadCount = 0, - IsDownloadActivated = false, - LicenseDownloadId = 0, - ItemWeight = null, - RentalStartDateUtc = null, - RentalEndDateUtc = null, - TaxRate = 0 - }; - _orderItemRepository.Insert(firstOrderItem3); - - var firstOrderGiftcard = new GiftCard - { - GiftCardType = GiftCardType.Virtual, - PurchasedWithOrderItem = firstOrderItem3, - Amount = 25M, - IsGiftCardActivated = false, - GiftCardCouponCode = string.Empty, - RecipientName = "Brenda Lindgren", - RecipientEmail = "brenda_lindgren@nopCommerce.com", - SenderName = "Steve Gates", - SenderEmail = "steve_gates@nopCommerce.com", - Message = string.Empty, - IsRecipientNotified = false, - CreatedOnUtc = DateTime.UtcNow - }; - _giftCardRepository.Insert(firstOrderGiftcard); - - //order notes - _orderNoteRepository.Insert(new OrderNote() - { - CreatedOnUtc = DateTime.UtcNow, - Note = "Order placed", - Order = firstOrder - }); - _orderNoteRepository.Insert(new OrderNote() - { - CreatedOnUtc = DateTime.UtcNow, - Note = "Order paid", - Order = firstOrder - }); - - - //second order - var secondCustomer = _customerRepository.Table.First(c => c.Email.Equals("arthur_holmes@nopCommerce.com")); - var secondOrder = new Order() - { - StoreId = defaultStore.Id, - OrderGuid = Guid.NewGuid(), - Customer = secondCustomer, - CustomerLanguageId = _languageRepository.Table.First().Id, - CustomerIp = "127.0.0.1", - OrderSubtotalInclTax = 2460M, - OrderSubtotalExclTax = 2460M, - OrderSubTotalDiscountInclTax = decimal.Zero, - OrderSubTotalDiscountExclTax = decimal.Zero, - OrderShippingInclTax = decimal.Zero, - OrderShippingExclTax = decimal.Zero, - OrderShippingNonTaxable = decimal.Zero, - PaymentMethodAdditionalFeeInclTax = decimal.Zero, - PaymentMethodAdditionalFeeExclTax = decimal.Zero, - PaymentMethodAdditionalFeeNonTaxable = decimal.Zero, - TaxRates = "0:0;", - OrderTax = decimal.Zero, - OrderTotal = 2460M, - OrderAmount = 2460M, - OrderAmountIncl = 2460M, - OrderDiscountIncl = decimal.Zero, - EarnedRewardPointsBaseAmountIncl = 2460M, - EarnedRewardPointsBaseAmountExcl = 2460M, - RefundedAmount = decimal.Zero, - OrderDiscount = decimal.Zero, - CheckoutAttributeDescription = string.Empty, - CheckoutAttributesXml = string.Empty, - CustomerCurrencyCode = "USD", - CurrencyRate = 1M, - AffiliateId = 0, - OrderStatus = OrderStatus.Pending, - AllowStoringCreditCardNumber = false, - CardType = string.Empty, - CardName = string.Empty, - CardNumber = string.Empty, - MaskedCreditCardNumber = string.Empty, - CardCvv2 = string.Empty, - CardExpirationMonth = string.Empty, - CardExpirationYear = string.Empty, - PaymentMethodSystemName = "Payments.CheckMoneyOrder", - AuthorizationTransactionId = string.Empty, - AuthorizationTransactionCode = string.Empty, - AuthorizationTransactionResult = string.Empty, - CaptureTransactionId = string.Empty, - CaptureTransactionResult = string.Empty, - SubscriptionTransactionId = string.Empty, - PaymentStatus = PaymentStatus.Pending, - PaidDateUtc = null, - BillingAddress = (Address)secondCustomer.BillingAddress.Clone(), - ShippingAddress = (Address)secondCustomer.ShippingAddress.Clone(), - ShippingStatus = ShippingStatus.NotYetShipped, - ShippingMethod = "Next Day Air", - PickUpInStore = false, - ShippingRateComputationMethodSystemName = "Shipping.FixedOrByWeight", - CustomValuesXml = string.Empty, - VatNumber = string.Empty, - CreatedOnUtc = DateTime.UtcNow, - CustomOrderNumber = string.Empty - }; - _orderRepository.Insert(secondOrder); - secondOrder.CustomOrderNumber = secondOrder.Id.ToString(); - _orderRepository.Update(secondOrder); - - //order notes - _orderNoteRepository.Insert(new OrderNote() - { - CreatedOnUtc = DateTime.UtcNow, - Note = "Order placed", - Order = secondOrder - }); - - //item Vintage Style Engagement Ring - var secondOrderItem1 = new OrderItem() - { - OrderItemGuid = Guid.NewGuid(), - Order = secondOrder, - ProductId = _productRepository.Table.First(p => p.Name.Equals("Vintage Style Engagement Ring")).Id, - UnitPriceInclTax = 2100M, - UnitPriceExclTax = 2100M, - PriceInclTax = 2100M, - PriceExclTax = 2100M, - OriginalProductCost = decimal.Zero, - AttributeDescription = string.Empty, - AttributesXml = string.Empty, - Quantity = 1, - DiscountAmountInclTax = decimal.Zero, - DiscountAmountExclTax = decimal.Zero, - DownloadCount = 0, - IsDownloadActivated = false, - LicenseDownloadId = 0, - ItemWeight = null, - RentalStartDateUtc = null, - RentalEndDateUtc = null, - TaxRate = 0 - }; - _orderItemRepository.Insert(secondOrderItem1); - - //item Flower Girl Bracelet - var secondOrderItem2 = new OrderItem() - { - OrderItemGuid = Guid.NewGuid(), - Order = secondOrder, - ProductId = _productRepository.Table.First(p => p.Name.Equals("Flower Girl Bracelet")).Id, - UnitPriceInclTax = 360M, - UnitPriceExclTax = 360M, - PriceInclTax = 360M, - PriceExclTax = 360M, - OriginalProductCost = decimal.Zero, - AttributeDescription = string.Empty, - AttributesXml = string.Empty, - Quantity = 1, - DiscountAmountInclTax = decimal.Zero, - DiscountAmountExclTax = decimal.Zero, - DownloadCount = 0, - IsDownloadActivated = false, - LicenseDownloadId = 0, - ItemWeight = null, - RentalStartDateUtc = null, - RentalEndDateUtc = null, - TaxRate = 0 - }; - _orderItemRepository.Insert(secondOrderItem2); - - - //third order - var thirdCustomer = _customerRepository.Table.First(c => c.Email.Equals("james_pan@nopCommerce.com")); - var thirdOrder = new Order() - { - StoreId = defaultStore.Id, - OrderGuid = Guid.NewGuid(), - Customer = thirdCustomer, - CustomerLanguageId = _languageRepository.Table.First().Id, - CustomerIp = "127.0.0.1", - OrderSubtotalInclTax = 8.80M, - OrderSubtotalExclTax = 8.80M, - OrderSubTotalDiscountInclTax = decimal.Zero, - OrderSubTotalDiscountExclTax = decimal.Zero, - OrderShippingInclTax = decimal.Zero, - OrderShippingExclTax = decimal.Zero, - OrderShippingNonTaxable = decimal.Zero, - PaymentMethodAdditionalFeeInclTax = decimal.Zero, - PaymentMethodAdditionalFeeExclTax = decimal.Zero, - PaymentMethodAdditionalFeeNonTaxable = decimal.Zero, - TaxRates = "0:0;", - OrderTax = decimal.Zero, - OrderTotal = 8.80M, - OrderAmount = 8.80M, - OrderAmountIncl = 8.80M, - OrderDiscountIncl = decimal.Zero, - EarnedRewardPointsBaseAmountIncl = 8.80M, - EarnedRewardPointsBaseAmountExcl = 8.80M, - RefundedAmount = decimal.Zero, - OrderDiscount = decimal.Zero, - CheckoutAttributeDescription = string.Empty, - CheckoutAttributesXml = string.Empty, - CustomerCurrencyCode = "USD", - CurrencyRate = 1M, - AffiliateId = 0, - OrderStatus = OrderStatus.Pending, - AllowStoringCreditCardNumber = false, - CardType = string.Empty, - CardName = string.Empty, - CardNumber = string.Empty, - MaskedCreditCardNumber = string.Empty, - CardCvv2 = string.Empty, - CardExpirationMonth = string.Empty, - CardExpirationYear = string.Empty, - PaymentMethodSystemName = "Payments.CheckMoneyOrder", - AuthorizationTransactionId = string.Empty, - AuthorizationTransactionCode = string.Empty, - AuthorizationTransactionResult = string.Empty, - CaptureTransactionId = string.Empty, - CaptureTransactionResult = string.Empty, - SubscriptionTransactionId = string.Empty, - PaymentStatus = PaymentStatus.Pending, - PaidDateUtc = null, - BillingAddress = (Address)thirdCustomer.BillingAddress.Clone(), - ShippingAddress = null, - ShippingStatus = ShippingStatus.ShippingNotRequired, - ShippingMethod = string.Empty, - PickUpInStore = false, - ShippingRateComputationMethodSystemName = string.Empty, - CustomValuesXml = string.Empty, - VatNumber = string.Empty, - CreatedOnUtc = DateTime.UtcNow, - CustomOrderNumber = string.Empty - }; - _orderRepository.Insert(thirdOrder); - thirdOrder.CustomOrderNumber = thirdOrder.Id.ToString(); - _orderRepository.Update(thirdOrder); - - //order notes - _orderNoteRepository.Insert(new OrderNote() - { - CreatedOnUtc = DateTime.UtcNow, - Note = "Order placed", - Order = thirdOrder - }); - - //item If You Wait - var thirdOrderItem1 = new OrderItem() - { - OrderItemGuid = Guid.NewGuid(), - Order = thirdOrder, - ProductId = _productRepository.Table.First(p => p.Name.Equals("If You Wait (donation)")).Id, - UnitPriceInclTax = 3M, - UnitPriceExclTax = 3M, - PriceInclTax = 3M, - PriceExclTax = 3M, - OriginalProductCost = decimal.Zero, - AttributeDescription = string.Empty, - AttributesXml = string.Empty, - Quantity = 1, - DiscountAmountInclTax = decimal.Zero, - DiscountAmountExclTax = decimal.Zero, - DownloadCount = 0, - IsDownloadActivated = false, - LicenseDownloadId = 0, - ItemWeight = null, - RentalStartDateUtc = null, - RentalEndDateUtc = null, - TaxRate = 0 - }; - _orderItemRepository.Insert(thirdOrderItem1); - - //item Night Visions - var thirdOrderItem2 = new OrderItem() - { - OrderItemGuid = Guid.NewGuid(), - Order = thirdOrder, - ProductId = _productRepository.Table.First(p => p.Name.Equals("Night Visions")).Id, - UnitPriceInclTax = 2.8M, - UnitPriceExclTax = 2.8M, - PriceInclTax = 2.8M, - PriceExclTax = 2.8M, - OriginalProductCost = decimal.Zero, - AttributeDescription = string.Empty, - AttributesXml = string.Empty, - Quantity = 1, - DiscountAmountInclTax = decimal.Zero, - DiscountAmountExclTax = decimal.Zero, - DownloadCount = 0, - IsDownloadActivated = false, - LicenseDownloadId = 0, - ItemWeight = null, - RentalStartDateUtc = null, - RentalEndDateUtc = null, - TaxRate = 0 - }; - _orderItemRepository.Insert(thirdOrderItem2); - - //item Science & Faith - var thirdOrderItem3 = new OrderItem() - { - OrderItemGuid = Guid.NewGuid(), - Order = thirdOrder, - ProductId = _productRepository.Table.First(p => p.Name.Equals("Science & Faith")).Id, - UnitPriceInclTax = 3M, - UnitPriceExclTax = 3M, - PriceInclTax = 3M, - PriceExclTax = 3M, - OriginalProductCost = decimal.Zero, - AttributeDescription = string.Empty, - AttributesXml = string.Empty, - Quantity = 1, - DiscountAmountInclTax = decimal.Zero, - DiscountAmountExclTax = decimal.Zero, - DownloadCount = 0, - IsDownloadActivated = false, - LicenseDownloadId = 0, - ItemWeight = null, - RentalStartDateUtc = null, - RentalEndDateUtc = null, - TaxRate = 0 - }; - _orderItemRepository.Insert(thirdOrderItem3); - - - //fourth order - var fourthCustomer = _customerRepository.Table.First(c => c.Email.Equals("brenda_lindgren@nopCommerce.com")); - var fourthOrder = new Order() - { - StoreId = defaultStore.Id, - OrderGuid = Guid.NewGuid(), - Customer = fourthCustomer, - CustomerLanguageId = _languageRepository.Table.First().Id, - CustomerIp = "127.0.0.1", - OrderSubtotalInclTax = 102M, - OrderSubtotalExclTax = 102M, - OrderSubTotalDiscountInclTax = decimal.Zero, - OrderSubTotalDiscountExclTax = decimal.Zero, - OrderShippingInclTax = decimal.Zero, - OrderShippingExclTax = decimal.Zero, - OrderShippingNonTaxable = decimal.Zero, - PaymentMethodAdditionalFeeInclTax = decimal.Zero, - PaymentMethodAdditionalFeeExclTax = decimal.Zero, - PaymentMethodAdditionalFeeNonTaxable = decimal.Zero, - TaxRates = "0:0;", - OrderTax = decimal.Zero, - OrderTotal = 102M, - OrderAmount = 102M, - OrderAmountIncl = 102M, - OrderDiscountIncl = decimal.Zero, - EarnedRewardPointsBaseAmountIncl = 102M, - EarnedRewardPointsBaseAmountExcl = 102M, - RefundedAmount = decimal.Zero, - OrderDiscount = decimal.Zero, - CheckoutAttributeDescription = string.Empty, - CheckoutAttributesXml = string.Empty, - CustomerCurrencyCode = "USD", - CurrencyRate = 1M, - AffiliateId = 0, - OrderStatus = OrderStatus.Processing, - AllowStoringCreditCardNumber = false, - CardType = string.Empty, - CardName = string.Empty, - CardNumber = string.Empty, - MaskedCreditCardNumber = string.Empty, - CardCvv2 = string.Empty, - CardExpirationMonth = string.Empty, - CardExpirationYear = string.Empty, - PaymentMethodSystemName = "Payments.CheckMoneyOrder", - AuthorizationTransactionId = string.Empty, - AuthorizationTransactionCode = string.Empty, - AuthorizationTransactionResult = string.Empty, - CaptureTransactionId = string.Empty, - CaptureTransactionResult = string.Empty, - SubscriptionTransactionId = string.Empty, - PaymentStatus = PaymentStatus.Paid, - PaidDateUtc = DateTime.UtcNow, - BillingAddress = (Address)fourthCustomer.BillingAddress.Clone(), - ShippingAddress = (Address)fourthCustomer.ShippingAddress.Clone(), - ShippingStatus = ShippingStatus.Shipped, - ShippingMethod = "Pickup in store", - PickUpInStore = true, - PickupAddress = (Address)fourthCustomer.ShippingAddress.Clone(), - ShippingRateComputationMethodSystemName = "Pickup.PickupInStore", - CustomValuesXml = string.Empty, - VatNumber = string.Empty, - CreatedOnUtc = DateTime.UtcNow, - CustomOrderNumber = string.Empty - }; - _orderRepository.Insert(fourthOrder); - fourthOrder.CustomOrderNumber = fourthOrder.Id.ToString(); - _orderRepository.Update(fourthOrder); - - //order notes - _orderNoteRepository.Insert(new OrderNote() - { - CreatedOnUtc = DateTime.UtcNow, - Note = "Order placed", - Order = fourthOrder - }); - _orderNoteRepository.Insert(new OrderNote() - { - CreatedOnUtc = DateTime.UtcNow, - Note = "Order paid", - Order = fourthOrder - }); - _orderNoteRepository.Insert(new OrderNote() - { - CreatedOnUtc = DateTime.UtcNow, - Note = "Order shipped", - Order = fourthOrder - }); - - //item Pride and Prejudice - var fourthOrderItem1 = new OrderItem() - { - OrderItemGuid = Guid.NewGuid(), - Order = fourthOrder, - ProductId = _productRepository.Table.First(p => p.Name.Equals("Pride and Prejudice")).Id, - UnitPriceInclTax = 24M, - UnitPriceExclTax = 24M, - PriceInclTax = 24M, - PriceExclTax = 24M, - OriginalProductCost = decimal.Zero, - AttributeDescription = string.Empty, - AttributesXml = string.Empty, - Quantity = 1, - DiscountAmountInclTax = decimal.Zero, - DiscountAmountExclTax = decimal.Zero, - DownloadCount = 0, - IsDownloadActivated = false, - LicenseDownloadId = 0, - ItemWeight = null, - RentalStartDateUtc = null, - RentalEndDateUtc = null, - TaxRate = 0 - }; - _orderItemRepository.Insert(fourthOrderItem1); - - //item First Prize Pies - var fourthOrderItem2 = new OrderItem() - { - OrderItemGuid = Guid.NewGuid(), - Order = fourthOrder, - ProductId = _productRepository.Table.First(p => p.Name.Equals("First Prize Pies")).Id, - UnitPriceInclTax = 51M, - UnitPriceExclTax = 51M, - PriceInclTax = 51M, - PriceExclTax = 51M, - OriginalProductCost = decimal.Zero, - AttributeDescription = string.Empty, - AttributesXml = string.Empty, - Quantity = 1, - DiscountAmountInclTax = decimal.Zero, - DiscountAmountExclTax = decimal.Zero, - DownloadCount = 0, - IsDownloadActivated = false, - LicenseDownloadId = 0, - ItemWeight = null, - RentalStartDateUtc = null, - RentalEndDateUtc = null, - TaxRate = 0 - }; - _orderItemRepository.Insert(fourthOrderItem2); - - //item Fahrenheit 451 by Ray Bradbury - var fourthOrderItem3 = new OrderItem() - { - OrderItemGuid = Guid.NewGuid(), - Order = fourthOrder, - ProductId = _productRepository.Table.First(p => p.Name.Equals("Fahrenheit 451 by Ray Bradbury")).Id, - UnitPriceInclTax = 27M, - UnitPriceExclTax = 27M, - PriceInclTax = 27M, - PriceExclTax = 27M, - OriginalProductCost = decimal.Zero, - AttributeDescription = string.Empty, - AttributesXml = string.Empty, - Quantity = 1, - DiscountAmountInclTax = decimal.Zero, - DiscountAmountExclTax = decimal.Zero, - DownloadCount = 0, - IsDownloadActivated = false, - LicenseDownloadId = 0, - ItemWeight = null, - RentalStartDateUtc = null, - RentalEndDateUtc = null, - TaxRate = 0 - }; - _orderItemRepository.Insert(fourthOrderItem3); - - //shipments - //shipment 1 - var fourthOrderShipment1 = new Shipment - { - Order = fourthOrder, - TrackingNumber = string.Empty, - TotalWeight = 4M, - ShippedDateUtc = DateTime.UtcNow, - DeliveryDateUtc = DateTime.UtcNow, - AdminComment = string.Empty, - CreatedOnUtc = DateTime.UtcNow - }; - _shipmentRepository.Insert(fourthOrderShipment1); - - var fourthOrderShipment1Item1 = new ShipmentItem() - { - OrderItemId = fourthOrderItem1.Id, - Quantity = 1, - WarehouseId = 0, - Shipment = fourthOrderShipment1 - }; - _shipmentItemRepository.Insert(fourthOrderShipment1Item1); - - var fourthOrderShipment1Item2 = new ShipmentItem() - { - OrderItemId = fourthOrderItem2.Id, - Quantity = 1, - WarehouseId = 0, - Shipment = fourthOrderShipment1 - }; - _shipmentItemRepository.Insert(fourthOrderShipment1Item2); - - //shipment 2 - var fourthOrderShipment2 = new Shipment - { - Order = fourthOrder, - TrackingNumber = string.Empty, - TotalWeight = 2M, - ShippedDateUtc = DateTime.UtcNow, - DeliveryDateUtc = DateTime.UtcNow, - AdminComment = string.Empty, - CreatedOnUtc = DateTime.UtcNow - }; - _shipmentRepository.Insert(fourthOrderShipment2); - - var fourthOrderShipment2Item1 = new ShipmentItem() - { - OrderItemId = fourthOrderItem3.Id, - Quantity = 1, - WarehouseId = 0, - Shipment = fourthOrderShipment2 - }; - _shipmentItemRepository.Insert(fourthOrderShipment2Item1); - - - - - //fifth order - var fifthCustomer = _customerRepository.Table.First(c => c.Email.Equals("victoria_victoria@nopCommerce.com")); - var fifthOrder = new Order() - { - StoreId = defaultStore.Id, - OrderGuid = Guid.NewGuid(), - Customer = fifthCustomer, - CustomerLanguageId = _languageRepository.Table.First().Id, - CustomerIp = "127.0.0.1", - OrderSubtotalInclTax = 43.50M, - OrderSubtotalExclTax = 43.50M, - OrderSubTotalDiscountInclTax = decimal.Zero, - OrderSubTotalDiscountExclTax = decimal.Zero, - OrderShippingInclTax = decimal.Zero, - OrderShippingExclTax = decimal.Zero, - OrderShippingNonTaxable = decimal.Zero, - PaymentMethodAdditionalFeeInclTax = decimal.Zero, - PaymentMethodAdditionalFeeExclTax = decimal.Zero, - PaymentMethodAdditionalFeeNonTaxable = decimal.Zero, - TaxRates = "0:0;", - OrderTax = decimal.Zero, - OrderTotal = 43.50M, - OrderAmount = 43.50M, - OrderAmountIncl = 43.50M, - OrderDiscountIncl = decimal.Zero, - EarnedRewardPointsBaseAmountIncl = 43.50M, - EarnedRewardPointsBaseAmountExcl = 43.50M, - RefundedAmount = decimal.Zero, - OrderDiscount = decimal.Zero, - CheckoutAttributeDescription = string.Empty, - CheckoutAttributesXml = string.Empty, - CustomerCurrencyCode = "USD", - CurrencyRate = 1M, - AffiliateId = 0, - OrderStatus = OrderStatus.Complete, - AllowStoringCreditCardNumber = false, - CardType = string.Empty, - CardName = string.Empty, - CardNumber = string.Empty, - MaskedCreditCardNumber = string.Empty, - CardCvv2 = string.Empty, - CardExpirationMonth = string.Empty, - CardExpirationYear = string.Empty, - PaymentMethodSystemName = "Payments.CheckMoneyOrder", - AuthorizationTransactionId = string.Empty, - AuthorizationTransactionCode = string.Empty, - AuthorizationTransactionResult = string.Empty, - CaptureTransactionId = string.Empty, - CaptureTransactionResult = string.Empty, - SubscriptionTransactionId = string.Empty, - PaymentStatus = PaymentStatus.Paid, - PaidDateUtc = DateTime.UtcNow, - BillingAddress = (Address)fifthCustomer.BillingAddress.Clone(), - ShippingAddress = (Address)fifthCustomer.ShippingAddress.Clone(), - ShippingStatus = ShippingStatus.Delivered, - ShippingMethod = "Ground", - PickUpInStore = false, - ShippingRateComputationMethodSystemName = "Shipping.FixedOrByWeight", - CustomValuesXml = string.Empty, - VatNumber = string.Empty, - CreatedOnUtc = DateTime.UtcNow, - CustomOrderNumber = string.Empty - }; - _orderRepository.Insert(fifthOrder); - fifthOrder.CustomOrderNumber = fifthOrder.Id.ToString(); - _orderRepository.Update(fifthOrder); - - //order notes - _orderNoteRepository.Insert(new OrderNote() - { - CreatedOnUtc = DateTime.UtcNow, - Note = "Order placed", - Order = fifthOrder - }); - _orderNoteRepository.Insert(new OrderNote() - { - CreatedOnUtc = DateTime.UtcNow, - Note = "Order paid", - Order = fifthOrder - }); - _orderNoteRepository.Insert(new OrderNote() - { - CreatedOnUtc = DateTime.UtcNow, - Note = "Order shipped", - Order = fifthOrder - }); - _orderNoteRepository.Insert(new OrderNote() - { - CreatedOnUtc = DateTime.UtcNow, - Note = "Order delivered", - Order = fifthOrder - }); - - //item Levi's 511 Jeans - var fifthOrderItem1 = new OrderItem() - { - OrderItemGuid = Guid.NewGuid(), - Order = fifthOrder, - ProductId = _productRepository.Table.First(p => p.Name.Equals("Levi's 511 Jeans")).Id, - UnitPriceInclTax = 43.50M, - UnitPriceExclTax = 43.50M, - PriceInclTax = 43.50M, - PriceExclTax = 43.50M, - OriginalProductCost = decimal.Zero, - AttributeDescription = string.Empty, - AttributesXml = string.Empty, - Quantity = 1, - DiscountAmountInclTax = decimal.Zero, - DiscountAmountExclTax = decimal.Zero, - DownloadCount = 0, - IsDownloadActivated = false, - LicenseDownloadId = 0, - ItemWeight = null, - RentalStartDateUtc = null, - RentalEndDateUtc = null, - TaxRate = 0 - }; - _orderItemRepository.Insert(fifthOrderItem1); - - //shipment 1 - var fifthOrderShipment1 = new Shipment - { - Order = fifthOrder, - TrackingNumber = string.Empty, - TotalWeight = 2M, - ShippedDateUtc = DateTime.UtcNow, - DeliveryDateUtc = DateTime.UtcNow, - AdminComment = string.Empty, - CreatedOnUtc = DateTime.UtcNow - }; - _shipmentRepository.Insert(fifthOrderShipment1); - - var fifthOrderShipment1Item1 = new ShipmentItem() - { - OrderItemId = fifthOrderItem1.Id, - Quantity = 1, - WarehouseId = 0, - Shipment = fifthOrderShipment1 - }; - _shipmentItemRepository.Insert(fifthOrderShipment1Item1); - } - - protected virtual void InstallActivityLog(string defaultUserEmail) - { - //default customer/user - var defaultCustomer = _customerRepository.Table.FirstOrDefault(x => x.Email == defaultUserEmail); - if (defaultCustomer == null) - throw new Exception("Cannot load default customer"); - - _activityLogRepository.Insert(new ActivityLog() - { - ActivityLogType = _activityLogTypeRepository.Table.First(alt => alt.SystemKeyword.Equals("EditCategory")), - Comment = "Edited a category ('Computers')", - CreatedOnUtc = DateTime.UtcNow, - Customer = defaultCustomer, - IpAddress = "127.0.0.1" - }); - _activityLogRepository.Insert(new ActivityLog() - { - ActivityLogType = _activityLogTypeRepository.Table.First(alt => alt.SystemKeyword.Equals("EditDiscount")), - Comment = "Edited a discount ('Sample discount with coupon code')", - CreatedOnUtc = DateTime.UtcNow, - Customer = defaultCustomer, - IpAddress = "127.0.0.1" - }); - _activityLogRepository.Insert(new ActivityLog() - { - ActivityLogType = _activityLogTypeRepository.Table.First(alt => alt.SystemKeyword.Equals("EditSpecAttribute")), - Comment = "Edited a specification attribute ('CPU Type')", - CreatedOnUtc = DateTime.UtcNow, - Customer = defaultCustomer, - IpAddress = "127.0.0.1" - }); - _activityLogRepository.Insert(new ActivityLog() - { - ActivityLogType = _activityLogTypeRepository.Table.First(alt => alt.SystemKeyword.Equals("AddNewProductAttribute")), - Comment = "Added a new product attribute ('Some attribute')", - CreatedOnUtc = DateTime.UtcNow, - Customer = defaultCustomer, - IpAddress = "127.0.0.1" - }); - _activityLogRepository.Insert(new ActivityLog() - { - ActivityLogType = _activityLogTypeRepository.Table.First(alt => alt.SystemKeyword.Equals("DeleteGiftCard")), - Comment = "Deleted a gift card ('bdbbc0ef-be57')", - CreatedOnUtc = DateTime.UtcNow, - Customer = defaultCustomer, - IpAddress = "127.0.0.1" - }); - } - - protected virtual void InstallSearchTerms() - { - //default store - var defaultStore = _storeRepository.Table.FirstOrDefault(); - if (defaultStore == null) - throw new Exception("No default store could be loaded"); - - _searchTermRepository.Insert(new SearchTerm() - { - Count = 34, - Keyword = "computer", - StoreId = defaultStore.Id - }); - _searchTermRepository.Insert(new SearchTerm() - { - Count = 30, - Keyword = "camera", - StoreId = defaultStore.Id - }); - _searchTermRepository.Insert(new SearchTerm() - { - Count = 27, - Keyword = "jewelry", - StoreId = defaultStore.Id - }); - _searchTermRepository.Insert(new SearchTerm() - { - Count = 26, - Keyword = "shoes", - StoreId = defaultStore.Id - }); - _searchTermRepository.Insert(new SearchTerm() - { - Count = 19, - Keyword = "jeans", - StoreId = defaultStore.Id - }); - _searchTermRepository.Insert(new SearchTerm() - { - Count = 10, - Keyword = "gift", - StoreId = defaultStore.Id - }); - } - - protected virtual void InstallEmailAccounts() - { - var emailAccounts = new List - { - new EmailAccount - { - Email = "test@mail.com", - DisplayName = "Store name", - Host = "smtp.mail.com", - Port = 25, - Username = "123", - Password = "123", - EnableSsl = false, - UseDefaultCredentials = false - }, - }; - _emailAccountRepository.Insert(emailAccounts); - } - - protected virtual void InstallMessageTemplates() - { - var eaGeneral = _emailAccountRepository.Table.FirstOrDefault(); - if (eaGeneral == null) - throw new Exception("Default email account cannot be loaded"); - - var messageTemplates = new List - { - new MessageTemplate - { - Name = MessageTemplateSystemNames.BlogCommentNotification, - Subject = "%Store.Name%. New blog comment.", - Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}A new blog comment has been created for blog post \"%BlogComment.BlogPostTitle%\".{0}

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.BackInStockNotification, - Subject = "%Store.Name%. Back in stock notification", - Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}Hello %Customer.FullName%,{0}
    {0}Product %BackInStockSubscription.ProductName% is in stock.{0}

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.CustomerEmailValidationMessage, - Subject = "%Store.Name%. Email validation", - Body = string.Format("%Store.Name%{0}
    {0}
    {0}To activate your account click here.{0}
    {0}
    {0}%Store.Name%{0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.CustomerEmailRevalidationMessage, - Subject = "%Store.Name%. Email validation", - Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}Hello %Customer.FullName%!{0}
    {0}To validate your new email address click here.{0}
    {0}
    {0}%Store.Name%{0}

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.PrivateMessageNotification, - Subject = "%Store.Name%. You have received a new private message", - Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}You have received a new private message.{0}

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.CustomerPasswordRecoveryMessage, - Subject = "%Store.Name%. Password recovery", - Body = string.Format("%Store.Name%{0}
    {0}
    {0}To change your password click here.{0}
    {0}
    {0}%Store.Name%{0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.CustomerWelcomeMessage, - Subject = "Welcome to %Store.Name%", - Body = string.Format("We welcome you to %Store.Name%.{0}
    {0}
    {0}You can now take part in the various services we have to offer you. Some of these services include:{0}
    {0}
    {0}Permanent Cart - Any products added to your online cart remain there until you remove them, or check them out.{0}
    {0}Address Book - We can now deliver your products to another address other than yours! This is perfect to send birthday gifts direct to the birthday-person themselves.{0}
    {0}Order History - View your history of purchases that you have made with us.{0}
    {0}Products Reviews - Share your opinions on products with our other customers.{0}
    {0}
    {0}For help with any of our online services, please email the store-owner: %Store.Email%.{0}
    {0}
    {0}Note: This email address was provided on our registration page. If you own the email and did not register on our site, please send an email to %Store.Email%.{0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.NewForumPostMessage, - Subject = "%Store.Name%. New Post Notification.", - Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}A new post has been created in the topic \"%Forums.TopicName%\" at \"%Forums.ForumName%\" forum.{0}
    {0}
    {0}Click here for more info.{0}
    {0}
    {0}Post author: %Forums.PostAuthor%{0}
    {0}Post body: %Forums.PostBody%{0}

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.NewForumTopicMessage, - Subject = "%Store.Name%. New Topic Notification.", - Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}A new topic \"%Forums.TopicName%\" has been created at \"%Forums.ForumName%\" forum.{0}
    {0}
    {0}Click here for more info.{0}

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.GiftCardNotification, - Subject = "%GiftCard.SenderName% has sent you a gift card for %Store.Name%", - Body = string.Format("

    {0}You have received a gift card for %Store.Name%{0}

    {0}

    {0}Dear %GiftCard.RecipientName%,{0}
    {0}
    {0}%GiftCard.SenderName% (%GiftCard.SenderEmail%) has sent you a %GiftCard.Amount% gift cart for %Store.Name%{0}

    {0}

    {0}You gift card code is %GiftCard.CouponCode%{0}

    {0}

    {0}%GiftCard.Message%{0}

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.CustomerRegisteredNotification, - Subject = "%Store.Name%. New customer registration", - Body = string.Format("

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

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.NewReturnRequestStoreOwnerNotification, - Subject = "%Store.Name%. New return request.", - Body = string.Format("

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

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.NewReturnRequestCustomerNotification, - Subject = "%Store.Name%. New return request.", - Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}Hello %Customer.FullName%!{0}
    {0}You have just submitted a new return request. Details are below:{0}
    {0}Request ID: %ReturnRequest.CustomNumber%{0}
    {0}Product: %ReturnRequest.Product.Quantity% x Product: %ReturnRequest.Product.Name%{0}
    {0}Reason for return: %ReturnRequest.Reason%{0}
    {0}Requested action: %ReturnRequest.RequestedAction%{0}
    {0}Customer comments:{0}
    {0}%ReturnRequest.CustomerComment%{0}

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.NewsCommentNotification, - Subject = "%Store.Name%. New news comment.", - Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}A new news comment has been created for news \"%NewsComment.NewsTitle%\".{0}

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.NewsletterSubscriptionActivationMessage, - Subject = "%Store.Name%. Subscription activation message.", - Body = string.Format("

    {0}Click here to confirm your subscription to our list.{0}

    {0}

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

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.NewsletterSubscriptionDeactivationMessage, - Subject = "%Store.Name%. Subscription deactivation message.", - Body = string.Format("

    {0}Click here to unsubscribe from our newsletter.{0}

    {0}

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

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.NewVatSubmittedStoreOwnerNotification, - Subject = "%Store.Name%. New VAT number is submitted.", - Body = string.Format("

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

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.OrderCancelledCustomerNotification, - Subject = "%Store.Name%. Your order cancelled", - Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}Hello %Order.CustomerFullName%,{0}
    {0}Your order has been cancelled. Below is the summary of the order.{0}
    {0}
    {0}Order Number: %Order.OrderNumber%{0}
    {0}Order Details: %Order.OrderURLForCustomer%{0}
    {0}Date Ordered: %Order.CreatedOn%{0}
    {0}
    {0}
    {0}
    {0}Billing Address{0}
    {0}%Order.BillingFirstName% %Order.BillingLastName%{0}
    {0}%Order.BillingAddress1%{0}
    {0}%Order.BillingCity% %Order.BillingZipPostalCode%{0}
    {0}%Order.BillingStateProvince% %Order.BillingCountry%{0}
    {0}
    {0}
    {0}
    {0}%if (%Order.Shippable%) Shipping Address{0}
    {0}%Order.ShippingFirstName% %Order.ShippingLastName%{0}
    {0}%Order.ShippingAddress1%{0}
    {0}%Order.ShippingCity% %Order.ShippingZipPostalCode%{0}
    {0}%Order.ShippingStateProvince% %Order.ShippingCountry%{0}
    {0}
    {0}Shipping Method: %Order.ShippingMethod%{0}
    {0}
    {0} endif% %Order.Product(s)%{0}

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.OrderCompletedCustomerNotification, - Subject = "%Store.Name%. Your order completed", - Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}Hello %Order.CustomerFullName%,{0}
    {0}Your order has been completed. Below is the summary of the order.{0}
    {0}
    {0}Order Number: %Order.OrderNumber%{0}
    {0}Order Details: %Order.OrderURLForCustomer%{0}
    {0}Date Ordered: %Order.CreatedOn%{0}
    {0}
    {0}
    {0}
    {0}Billing Address{0}
    {0}%Order.BillingFirstName% %Order.BillingLastName%{0}
    {0}%Order.BillingAddress1%{0}
    {0}%Order.BillingCity% %Order.BillingZipPostalCode%{0}
    {0}%Order.BillingStateProvince% %Order.BillingCountry%{0}
    {0}
    {0}
    {0}
    {0}%if (%Order.Shippable%) Shipping Address{0}
    {0}%Order.ShippingFirstName% %Order.ShippingLastName%{0}
    {0}%Order.ShippingAddress1%{0}
    {0}%Order.ShippingCity% %Order.ShippingZipPostalCode%{0}
    {0}%Order.ShippingStateProvince% %Order.ShippingCountry%{0}
    {0}
    {0}Shipping Method: %Order.ShippingMethod%{0}
    {0}
    {0} endif% %Order.Product(s)%{0}

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.ShipmentDeliveredCustomerNotification, - Subject = "Your order from %Store.Name% has been delivered.", - Body = string.Format("

    {0} %Store.Name%{0}
    {0}
    {0}Hello %Order.CustomerFullName%,{0}
    {0}Good news! You order has been delivered.{0}
    {0}Order Number: %Order.OrderNumber%{0}
    {0}Order Details: %Order.OrderURLForCustomer%{0}
    {0}Date Ordered: %Order.CreatedOn%{0}
    {0}
    {0}
    {0}
    {0}Billing Address{0}
    {0}%Order.BillingFirstName% %Order.BillingLastName%{0}
    {0}%Order.BillingAddress1%{0}
    {0}%Order.BillingCity% %Order.BillingZipPostalCode%{0}
    {0}%Order.BillingStateProvince% %Order.BillingCountry%{0}
    {0}
    {0}
    {0}
    {0}%if (%Order.Shippable%) Shipping Address{0}
    {0}%Order.ShippingFirstName% %Order.ShippingLastName%{0}
    {0}%Order.ShippingAddress1%{0}
    {0}%Order.ShippingCity% %Order.ShippingZipPostalCode%{0}
    {0}%Order.ShippingStateProvince% %Order.ShippingCountry%{0}
    {0}
    {0}Shipping Method: %Order.ShippingMethod%{0}
    {0}
    {0} endif% Delivered Products:{0}
    {0}
    {0}%Shipment.Product(s)%{0}

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.OrderPlacedCustomerNotification, - Subject = "Order receipt from %Store.Name%.", - Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}Hello %Order.CustomerFullName%,{0}
    {0}Thanks for buying from %Store.Name%. Below is the summary of the order.{0}
    {0}
    {0}Order Number: %Order.OrderNumber%{0}
    {0}Order Details: %Order.OrderURLForCustomer%{0}
    {0}Date Ordered: %Order.CreatedOn%{0}
    {0}
    {0}
    {0}
    {0}Billing Address{0}
    {0}%Order.BillingFirstName% %Order.BillingLastName%{0}
    {0}%Order.BillingAddress1%{0}
    {0}%Order.BillingCity% %Order.BillingZipPostalCode%{0}
    {0}%Order.BillingStateProvince% %Order.BillingCountry%{0}
    {0}
    {0}
    {0}
    {0}%if (%Order.Shippable%) Shipping Address{0}
    {0}%Order.ShippingFirstName% %Order.ShippingLastName%{0}
    {0}%Order.ShippingAddress1%{0}
    {0}%Order.ShippingCity% %Order.ShippingZipPostalCode%{0}
    {0}%Order.ShippingStateProvince% %Order.ShippingCountry%{0}
    {0}
    {0}Shipping Method: %Order.ShippingMethod%{0}
    {0}
    {0} endif% %Order.Product(s)%{0}

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.OrderPlacedStoreOwnerNotification, - Subject = "%Store.Name%. Purchase Receipt for Order #%Order.OrderNumber%", - Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}%Order.CustomerFullName% (%Order.CustomerEmail%) has just placed an order from your store. Below is the summary of the order.{0}
    {0}
    {0}Order Number: %Order.OrderNumber%{0}
    {0}Date Ordered: %Order.CreatedOn%{0}
    {0}
    {0}
    {0}
    {0}Billing Address{0}
    {0}%Order.BillingFirstName% %Order.BillingLastName%{0}
    {0}%Order.BillingAddress1%{0}
    {0}%Order.BillingCity% %Order.BillingZipPostalCode%{0}
    {0}%Order.BillingStateProvince% %Order.BillingCountry%{0}
    {0}
    {0}
    {0}
    {0}%if (%Order.Shippable%) Shipping Address{0}
    {0}%Order.ShippingFirstName% %Order.ShippingLastName%{0}
    {0}%Order.ShippingAddress1%{0}
    {0}%Order.ShippingCity% %Order.ShippingZipPostalCode%{0}
    {0}%Order.ShippingStateProvince% %Order.ShippingCountry%{0}
    {0}
    {0}Shipping Method: %Order.ShippingMethod%{0}
    {0}
    {0} endif% %Order.Product(s)%{0}

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.ShipmentSentCustomerNotification, - Subject = "Your order from %Store.Name% has been shipped.", - Body = string.Format("

    {0} %Store.Name%{0}
    {0}
    {0}Hello %Order.CustomerFullName%!,{0}
    {0}Good news! You order has been shipped.{0}
    {0}Order Number: %Order.OrderNumber%{0}
    {0}Order Details: %Order.OrderURLForCustomer%{0}
    {0}Date Ordered: %Order.CreatedOn%{0}
    {0}
    {0}
    {0}
    {0}Billing Address{0}
    {0}%Order.BillingFirstName% %Order.BillingLastName%{0}
    {0}%Order.BillingAddress1%{0}
    {0}%Order.BillingCity% %Order.BillingZipPostalCode%{0}
    {0}%Order.BillingStateProvince% %Order.BillingCountry%{0}
    {0}
    {0}
    {0}
    {0}%if (%Order.Shippable%) Shipping Address{0}
    {0}%Order.ShippingFirstName% %Order.ShippingLastName%{0}
    {0}%Order.ShippingAddress1%{0}
    {0}%Order.ShippingCity% %Order.ShippingZipPostalCode%{0}
    {0}%Order.ShippingStateProvince% %Order.ShippingCountry%{0}
    {0}
    {0}Shipping Method: %Order.ShippingMethod%{0}
    {0}
    {0} endif% Shipped Products:{0}
    {0}
    {0}%Shipment.Product(s)%{0}

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.ProductReviewNotification, - Subject = "%Store.Name%. New product review.", - Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}A new product review has been written for product \"%ProductReview.ProductName%\".{0}

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.QuantityBelowStoreOwnerNotification, - Subject = "%Store.Name%. Quantity below notification. %Product.Name%", - Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}%Product.Name% (ID: %Product.ID%) low quantity.{0}
    {0}
    {0}Quantity: %Product.StockQuantity%{0}
    {0}

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.QuantityBelowAttributeCombinationStoreOwnerNotification, - Subject = "%Store.Name%. Quantity below notification. %Product.Name%", - Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}%Product.Name% (ID: %Product.ID%) low quantity.{0}
    {0}%AttributeCombination.Formatted%{0}
    {0}Quantity: %AttributeCombination.StockQuantity%{0}
    {0}

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.ReturnRequestStatusChangedCustomerNotification, - Subject = "%Store.Name%. Return request status was changed.", - Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}Hello %Customer.FullName%,{0}
    {0}Your return request #%ReturnRequest.CustomNumber% status has been changed.{0}

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.EmailAFriendMessage, - Subject = "%Store.Name%. Referred Item", - Body = string.Format("

    {0} %Store.Name%{0}
    {0}
    {0}%EmailAFriend.Email% was shopping on %Store.Name% and wanted to share the following item with you.{0}
    {0}
    {0}%Product.Name%{0}
    {0}%Product.ShortDescription%{0}
    {0}
    {0}For more info click here{0}
    {0}
    {0}
    {0}%EmailAFriend.PersonalMessage%{0}
    {0}
    {0}%Store.Name%{0}

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.WishlistToFriendMessage, - Subject = "%Store.Name%. Wishlist", - Body = string.Format("

    {0} %Store.Name%{0}
    {0}
    {0}%Wishlist.Email% was shopping on %Store.Name% and wanted to share a wishlist with you.{0}
    {0}
    {0}
    {0}For more info click here{0}
    {0}
    {0}
    {0}%Wishlist.PersonalMessage%{0}
    {0}
    {0}%Store.Name%{0}

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.NewOrderNoteAddedCustomerNotification, - Subject = "%Store.Name%. New order note has been added", - Body = string.Format("

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

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.RecurringPaymentCancelledStoreOwnerNotification, - Subject = "%Store.Name%. Recurring payment cancelled", - Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}%if (%RecurringPayment.CancelAfterFailedPayment%) The last payment for the recurring payment ID=%RecurringPayment.ID% failed, so it was cancelled. endif% %if (!%RecurringPayment.CancelAfterFailedPayment%) %Customer.FullName% (%Customer.Email%) has just cancelled a recurring payment ID=%RecurringPayment.ID%. endif%{0}

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.RecurringPaymentCancelledCustomerNotification, - Subject = "%Store.Name%. Recurring payment cancelled", - Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}Hello %Customer.FullName%,{0}
    {0}%if (%RecurringPayment.CancelAfterFailedPayment%) It appears your credit card didn't go through for this recurring payment (%Order.OrderURLForCustomer%){0}
    {0}So your subscription has been canceled. endif% %if (!%RecurringPayment.CancelAfterFailedPayment%) The recurring payment ID=%RecurringPayment.ID% was cancelled. endif%{0}

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.RecurringPaymentFailedCustomerNotification, - Subject = "%Store.Name%. Last recurring payment failed", - Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}Hello %Customer.FullName%,{0}
    {0}It appears your credit card didn't go through for this recurring payment (%Order.OrderURLForCustomer%){0}
    %if (%RecurringPayment.RecurringPaymentType% == \"Manual\") {0}You can recharge balance and manually retry payment or cancel it on the order history page. endif% %if (%RecurringPayment.RecurringPaymentType% == \"Automatic\") {0}You can recharge balance and wait, we will try to make the payment again, or you can cancel it on the order history page. endif%{0}

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.OrderPlacedVendorNotification, - Subject = "%Store.Name%. Order placed", - Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}%Customer.FullName% (%Customer.Email%) has just placed an order.{0}
    {0}
    {0}Order Number: %Order.OrderNumber%{0}
    {0}Date Ordered: %Order.CreatedOn%{0}
    {0}
    {0}%Order.Product(s)%{0}

    {0}", Environment.NewLine), - //this template is disabled by default - IsActive = false, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.OrderRefundedCustomerNotification, - Subject = "%Store.Name%. Order #%Order.OrderNumber% refunded", - Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}Hello %Order.CustomerFullName%,{0}
    {0}Thanks for buying from %Store.Name%. Order #%Order.OrderNumber% has been has been refunded. Please allow 7-14 days for the refund to be reflected in your account.{0}
    {0}
    {0}Amount refunded: %Order.AmountRefunded%{0}
    {0}
    {0}Below is the summary of the order.{0}
    {0}
    {0}Order Number: %Order.OrderNumber%{0}
    {0}Order Details: %Order.OrderURLForCustomer%{0}
    {0}Date Ordered: %Order.CreatedOn%{0}
    {0}
    {0}
    {0}
    {0}Billing Address{0}
    {0}%Order.BillingFirstName% %Order.BillingLastName%{0}
    {0}%Order.BillingAddress1%{0}
    {0}%Order.BillingCity% %Order.BillingZipPostalCode%{0}
    {0}%Order.BillingStateProvince% %Order.BillingCountry%{0}
    {0}
    {0}
    {0}
    {0}%if (%Order.Shippable%) Shipping Address{0}
    {0}%Order.ShippingFirstName% %Order.ShippingLastName%{0}
    {0}%Order.ShippingAddress1%{0}
    {0}%Order.ShippingCity% %Order.ShippingZipPostalCode%{0}
    {0}%Order.ShippingStateProvince% %Order.ShippingCountry%{0}
    {0}
    Shipping Method: %Order.ShippingMethod%{0}
    {0}
    {0} endif% %Order.Product(s)%{0}

    {0}", Environment.NewLine), - //this template is disabled by default - IsActive = false, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.OrderRefundedStoreOwnerNotification, - Subject = "%Store.Name%. Order #%Order.OrderNumber% refunded", - Body = string.Format("%Store.Name%. Order #%Order.OrderNumber% refunded', N'{0}

    {0}%Store.Name%{0}
    {0}
    {0}Order #%Order.OrderNumber% has been just refunded{0}
    {0}
    {0}Amount refunded: %Order.AmountRefunded%{0}
    {0}
    {0}Date Ordered: %Order.CreatedOn%{0}

    {0}", Environment.NewLine), - //this template is disabled by default - IsActive = false, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.OrderPaidStoreOwnerNotification, - Subject = "%Store.Name%. Order #%Order.OrderNumber% paid", - Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}Order #%Order.OrderNumber% has been just paid{0}
    {0}Date Ordered: %Order.CreatedOn%{0}

    {0}", Environment.NewLine), - //this template is disabled by default - IsActive = false, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.OrderPaidCustomerNotification, - Subject = "%Store.Name%. Order #%Order.OrderNumber% paid", - Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}Hello %Order.CustomerFullName%,{0}
    {0}Thanks for buying from %Store.Name%. Order #%Order.OrderNumber% has been just paid. Below is the summary of the order.{0}
    {0}
    {0}Order Number: %Order.OrderNumber%{0}
    {0}Order Details: %Order.OrderURLForCustomer%{0}
    {0}Date Ordered: %Order.CreatedOn%{0}
    {0}
    {0}
    {0}
    {0}Billing Address{0}
    {0}%Order.BillingFirstName% %Order.BillingLastName%{0}
    {0}%Order.BillingAddress1%{0}
    {0}%Order.BillingCity% %Order.BillingZipPostalCode%{0}
    {0}%Order.BillingStateProvince% %Order.BillingCountry%{0}
    {0}
    {0}
    {0}
    {0}%if (%Order.Shippable%) Shipping Address{0}
    {0}%Order.ShippingFirstName% %Order.ShippingLastName%{0}
    {0}%Order.ShippingAddress1%{0}
    {0}%Order.ShippingCity% %Order.ShippingZipPostalCode%{0}
    {0}%Order.ShippingStateProvince% %Order.ShippingCountry%{0}
    {0}
    {0}Shipping Method: %Order.ShippingMethod%{0}
    {0}
    {0} endif% %Order.Product(s)%{0}

    {0}", Environment.NewLine), - //this template is disabled by default - IsActive = false, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.OrderPaidVendorNotification, - Subject = "%Store.Name%. Order #%Order.OrderNumber% paid", - Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}Order #%Order.OrderNumber% has been just paid.{0}
    {0}
    {0}Order Number: %Order.OrderNumber%{0}
    {0}Date Ordered: %Order.CreatedOn%{0}
    {0}
    {0}%Order.Product(s)%{0}

    {0}", Environment.NewLine), - //this template is disabled by default - IsActive = false, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.NewVendorAccountApplyStoreOwnerNotification, - Subject = "%Store.Name%. New vendor account submitted.", - Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}%Customer.FullName% (%Customer.Email%) has just submitted for a vendor account. Details are below:{0}
    {0}Vendor name: %Vendor.Name%{0}
    {0}Vendor email: %Vendor.Email%{0}
    {0}
    {0}You can activate it in admin area.{0}

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.VendorInformationChangeNotification, - Subject = "%Store.Name%. Vendor information change.", - Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}Vendor %Vendor.Name% (%Vendor.Email%) has just changed information about itself.{0}

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.ContactUsMessage, - Subject = "%Store.Name%. Contact us", - Body = string.Format("

    {0}%ContactUs.Body%{0}

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - }, - new MessageTemplate - { - Name = MessageTemplateSystemNames.ContactVendorMessage, - Subject = "%Store.Name%. Contact us", - Body = string.Format("

    {0}%ContactUs.Body%{0}

    {0}", Environment.NewLine), - IsActive = true, - EmailAccountId = eaGeneral.Id, - } - }; - _messageTemplateRepository.Insert(messageTemplates); - } - - protected virtual void InstallTopics() - { - var defaultTopicTemplate = - _topicTemplateRepository.Table.FirstOrDefault(tt => tt.Name == "Default template"); - if (defaultTopicTemplate == null) - throw new Exception("Topic template cannot be loaded"); - - var topics = new List - { - new Topic - { - SystemName = "AboutUs", - IncludeInSitemap = false, - IsPasswordProtected = false, - IncludeInFooterColumn1 = true, - DisplayOrder = 20, - Published = true, - Title = "About us", - Body = "

    Put your "About Us" information here. You can edit this in the admin site.

    ", - TopicTemplateId = defaultTopicTemplate.Id - }, - new Topic - { - SystemName = "CheckoutAsGuestOrRegister", - IncludeInSitemap = false, - IsPasswordProtected = false, - DisplayOrder = 1, - Published = true, - Title = "", - Body = "

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

    • Fast and easy check out
    • Easy access to your order history and status
    ", - TopicTemplateId = defaultTopicTemplate.Id - }, - new Topic - { - SystemName = "ConditionsOfUse", - IncludeInSitemap = false, - IsPasswordProtected = false, - IncludeInFooterColumn1 = true, - DisplayOrder = 15, - Published = true, - Title = "Conditions of Use", - Body = "

    Put your conditions of use information here. You can edit this in the admin site.

    ", - TopicTemplateId = defaultTopicTemplate.Id - }, - new Topic - { - SystemName = "ContactUs", - IncludeInSitemap = false, - IsPasswordProtected = false, - DisplayOrder = 1, - Published = true, - Title = "", - Body = "

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

    ", - TopicTemplateId = defaultTopicTemplate.Id - }, - new Topic - { - SystemName = "ForumWelcomeMessage", - IncludeInSitemap = false, - IsPasswordProtected = false, - DisplayOrder = 1, - Published = true, - Title = "Forums", - Body = "

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

    ", - TopicTemplateId = defaultTopicTemplate.Id - }, - new Topic - { - SystemName = "HomePageText", - IncludeInSitemap = false, - IsPasswordProtected = false, - DisplayOrder = 1, - Published = true, - Title = "Welcome to our store", - Body = "

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

    If you have questions, see the Documentation, or post in the Forums at nopCommerce.com

    ", - TopicTemplateId = defaultTopicTemplate.Id - }, - new Topic - { - SystemName = "LoginRegistrationInfo", - IncludeInSitemap = false, - IsPasswordProtected = false, - DisplayOrder = 1, - Published = true, - Title = "About login / registration", - Body = "

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

    ", - TopicTemplateId = defaultTopicTemplate.Id - }, - new Topic - { - SystemName = "PrivacyInfo", - IncludeInSitemap = false, - IsPasswordProtected = false, - IncludeInFooterColumn1 = true, - DisplayOrder = 10, - Published = true, - Title = "Privacy notice", - Body = "

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

    ", - TopicTemplateId = defaultTopicTemplate.Id - }, - new Topic - { - SystemName = "PageNotFound", - IncludeInSitemap = false, - IsPasswordProtected = false, - DisplayOrder = 1, - Published = true, - Title = "", - Body = "

    The page you requested was not found, and we have a fine guess why.

    • If you typed the URL directly, please make sure the spelling is correct.
    • The page no longer exists. In this case, we profusely apologize for the inconvenience and for any damage this may cause.
    ", - TopicTemplateId = defaultTopicTemplate.Id - }, - new Topic - { - SystemName = "ShippingInfo", - IncludeInSitemap = false, - IsPasswordProtected = false, - IncludeInFooterColumn1 = true, - DisplayOrder = 5, - Published = true, - Title = "Shipping & returns", - Body = "

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

    ", - TopicTemplateId = defaultTopicTemplate.Id - }, - new Topic - { - SystemName = "ApplyVendor", - IncludeInSitemap = false, - IsPasswordProtected = false, - DisplayOrder = 1, - Published = true, - Title = "", - Body = "

    Put your apply vendor instructions here. You can edit this in the admin site.

    ", - TopicTemplateId = defaultTopicTemplate.Id - }, - }; - _topicRepository.Insert(topics); - - - //search engine names - foreach (var topic in topics) - { - _urlRecordRepository.Insert(new UrlRecord - { - EntityId = topic.Id, - EntityName = "Topic", - LanguageId = 0, - IsActive = true, - Slug = topic.ValidateSeName("", !String.IsNullOrEmpty(topic.Title) ? topic.Title : topic.SystemName, true) - }); - } - - } - - protected virtual void InstallSettings(bool installSampleData) - { - var settingService = EngineContext.Current.Resolve(); - settingService.SaveSetting(new PdfSettings - { - LogoPictureId = 0, - LetterPageSizeEnabled = false, - RenderOrderNotes = true, - FontFileName = "FreeSerif.ttf", - InvoiceFooterTextColumn1 = null, - InvoiceFooterTextColumn2 = null, - }); - - settingService.SaveSetting(new CommonSettings - { - UseSystemEmailForContactUsForm = true, - UseStoredProceduresIfSupported = true, - UseStoredProcedureForLoadingCategories = false, - SitemapEnabled = true, - SitemapIncludeCategories = true, - SitemapIncludeManufacturers = true, - SitemapIncludeProducts = false, - DisplayJavaScriptDisabledWarning = false, - UseFullTextSearch = false, - FullTextMode = FulltextSearchMode.ExactMatch, - Log404Errors = true, - BreadcrumbDelimiter = "/", - RenderXuaCompatible = false, - XuaCompatibleValue = "IE=edge", - BbcodeEditorOpenLinksInNewWindow = false - }); - - settingService.SaveSetting(new SeoSettings - { - PageTitleSeparator = ". ", - PageTitleSeoAdjustment = PageTitleSeoAdjustment.PagenameAfterStorename, - DefaultTitle = "Your store", - DefaultMetaKeywords = "", - DefaultMetaDescription = "", - GenerateProductMetaDescription = true, - ConvertNonWesternChars = false, - AllowUnicodeCharsInUrls = true, - CanonicalUrlsEnabled = false, - WwwRequirement = WwwRequirement.NoMatter, - //we disable bundling out of the box because it requires a lot of server resources - EnableJsBundling = false, - EnableCssBundling = false, - TwitterMetaTags = true, - OpenGraphMetaTags = true, - ReservedUrlRecordSlugs = new List - { - "admin", - "install", - "recentlyviewedproducts", - "newproducts", - "compareproducts", - "clearcomparelist", - "setproductreviewhelpfulness", - "login", - "register", - "logout", - "cart", - "wishlist", - "emailwishlist", - "checkout", - "onepagecheckout", - "contactus", - "passwordrecovery", - "subscribenewsletter", - "blog", - "boards", - "inboxupdate", - "sentupdate", - "news", - "sitemap", - "search", - "config", - "eucookielawaccept", - "page-not-found", - //system names are not allowed (anyway they will cause a runtime error), - "con", - "lpt1", - "lpt2", - "lpt3", - "lpt4", - "lpt5", - "lpt6", - "lpt7", - "lpt8", - "lpt9", - "com1", - "com2", - "com3", - "com4", - "com5", - "com6", - "com7", - "com8", - "com9", - "null", - "prn", - "aux" - }, - CustomHeadTags = "" - }); - - settingService.SaveSetting(new AdminAreaSettings - { - DefaultGridPageSize = 15, - PopupGridPageSize = 10, - GridPageSizes = "10, 15, 20, 50, 100", - RichEditorAdditionalSettings = null, - RichEditorAllowJavaScript = false, - UseRichEditorInMessageTemplates = false, - UseIsoDateTimeConverterInJson = true - }); - - - settingService.SaveSetting(new ProductEditorSettings - { - Weight = true, - Dimensions = true, - ProductAttributes = true, - SpecificationAttributes =true - }); - - settingService.SaveSetting(new CatalogSettings - { - AllowViewUnpublishedProductPage = true, - DisplayDiscontinuedMessageForUnpublishedProducts = true, - PublishBackProductWhenCancellingOrders = false, - ShowSkuOnProductDetailsPage = true, - ShowSkuOnCatalogPages = false, - ShowManufacturerPartNumber = false, - ShowGtin = false, - ShowFreeShippingNotification = true, - AllowProductSorting = true, - AllowProductViewModeChanging = true, - DefaultViewMode = "grid", - ShowProductsFromSubcategories = false, - ShowCategoryProductNumber = false, - ShowCategoryProductNumberIncludingSubcategories = false, - CategoryBreadcrumbEnabled = true, - ShowShareButton = true, - PageShareCode = "
    ", - ProductReviewsMustBeApproved = false, - DefaultProductRatingValue = 5, - AllowAnonymousUsersToReviewProduct = false, - ProductReviewPossibleOnlyAfterPurchasing = false, - NotifyStoreOwnerAboutNewProductReviews = false, - EmailAFriendEnabled = true, - AllowAnonymousUsersToEmailAFriend = false, - RecentlyViewedProductsNumber = 3, - RecentlyViewedProductsEnabled = true, - NewProductsNumber = 6, - NewProductsEnabled = true, - CompareProductsEnabled = true, - CompareProductsNumber = 4, - ProductSearchAutoCompleteEnabled = true, - ProductSearchAutoCompleteNumberOfProducts = 10, - ProductSearchTermMinimumLength = 3, - ShowProductImagesInSearchAutoComplete = false, - ShowBestsellersOnHomepage = false, - NumberOfBestsellersOnHomepage = 4, - SearchPageProductsPerPage = 6, - SearchPageAllowCustomersToSelectPageSize = true, - SearchPagePageSizeOptions = "6, 3, 9, 18", - ProductsAlsoPurchasedEnabled = true, - ProductsAlsoPurchasedNumber = 4, - AjaxProcessAttributeChange = true, - NumberOfProductTags = 15, - ProductsByTagPageSize = 6, - IncludeShortDescriptionInCompareProducts = false, - IncludeFullDescriptionInCompareProducts = false, - IncludeFeaturedProductsInNormalLists = false, - DisplayTierPricesWithDiscounts = true, - IgnoreDiscounts = false, - IgnoreFeaturedProducts = false, - IgnoreAcl = true, - IgnoreStoreLimitations = true, - CacheProductPrices = false, - ProductsByTagAllowCustomersToSelectPageSize = true, - ProductsByTagPageSizeOptions = "6, 3, 9, 18", - MaximumBackInStockSubscriptions = 200, - ManufacturersBlockItemsToDisplay = 2, - DisplayTaxShippingInfoFooter = false, - DisplayTaxShippingInfoProductDetailsPage = false, - DisplayTaxShippingInfoProductBoxes = false, - DisplayTaxShippingInfoShoppingCart = false, - DisplayTaxShippingInfoWishlist = false, - DisplayTaxShippingInfoOrderDetailsPage = false, - DefaultCategoryPageSizeOptions = "6, 3, 9", - DefaultCategoryPageSize = 6, - DefaultManufacturerPageSizeOptions = "6, 3, 9", - DefaultManufacturerPageSize = 6, - ShowProductReviewsTabOnAccountPage = true, - ProductReviewsPageSizeOnAccountPage = 10, - ExportImportProductAttributes = true, - ExportImportUseDropdownlistsForAssociatedEntities = true - }); - - settingService.SaveSetting(new LocalizationSettings - { - DefaultAdminLanguageId = _languageRepository.Table.Single(l => l.Name == "English").Id, - UseImagesForLanguageSelection = false, - SeoFriendlyUrlsForLanguagesEnabled = false, - AutomaticallyDetectLanguage = false, - LoadAllLocaleRecordsOnStartup = true, - LoadAllLocalizedPropertiesOnStartup = true, - LoadAllUrlRecordsOnStartup = false, - IgnoreRtlPropertyForAdminArea = false - }); - - settingService.SaveSetting(new CustomerSettings - { - UsernamesEnabled = false, - CheckUsernameAvailabilityEnabled = false, - AllowUsersToChangeUsernames = false, - DefaultPasswordFormat = PasswordFormat.Hashed, - HashedPasswordFormat = "SHA1", - PasswordMinLength = 6, - UnduplicatedPasswordsNumber = 4, - PasswordRecoveryLinkDaysValid = 7, - PasswordLifetime = 90, - FailedPasswordAllowedAttempts = 0, - FailedPasswordLockoutMinutes = 30, - UserRegistrationType = UserRegistrationType.Standard, - AllowCustomersToUploadAvatars = false, - AvatarMaximumSizeBytes = 20000, - DefaultAvatarEnabled = true, - ShowCustomersLocation = false, - ShowCustomersJoinDate = false, - AllowViewingProfiles = false, - NotifyNewCustomerRegistration = false, - HideDownloadableProductsTab = false, - HideBackInStockSubscriptionsTab = false, - DownloadableProductsValidateUser = false, - CustomerNameFormat = CustomerNameFormat.ShowFirstName, - GenderEnabled = true, - DateOfBirthEnabled = true, - DateOfBirthRequired = false, - DateOfBirthMinimumAge = null, - CompanyEnabled = true, - StreetAddressEnabled = false, - StreetAddress2Enabled = false, - ZipPostalCodeEnabled = false, - CityEnabled = false, - CountryEnabled = false, - CountryRequired = false, - StateProvinceEnabled = false, - StateProvinceRequired = false, - PhoneEnabled = false, - FaxEnabled = false, - AcceptPrivacyPolicyEnabled = false, - NewsletterEnabled = true, - NewsletterTickedByDefault = true, - HideNewsletterBlock = false, - NewsletterBlockAllowToUnsubscribe = false, - OnlineCustomerMinutes = 20, - StoreLastVisitedPage = false, - SuffixDeletedCustomers = false, - EnteringEmailTwice = false, - RequireRegistrationForDownloadableProducts = false, - DeleteGuestTaskOlderThanMinutes = 1440 - }); - - settingService.SaveSetting(new AddressSettings - { - CompanyEnabled = true, - StreetAddressEnabled = true, - StreetAddressRequired = true, - StreetAddress2Enabled = true, - ZipPostalCodeEnabled = true, - ZipPostalCodeRequired = true, - CityEnabled = true, - CityRequired = true, - CountryEnabled = true, - StateProvinceEnabled = true, - PhoneEnabled = true, - PhoneRequired = true, - FaxEnabled = true, - }); - - settingService.SaveSetting(new MediaSettings - { - AvatarPictureSize = 120, - ProductThumbPictureSize = 415, - ProductDetailsPictureSize = 550, - ProductThumbPictureSizeOnProductDetailsPage = 100, - AssociatedProductPictureSize = 220, - CategoryThumbPictureSize = 450, - ManufacturerThumbPictureSize = 420, - VendorThumbPictureSize = 450, - CartThumbPictureSize = 80, - MiniCartThumbPictureSize = 70, - AutoCompleteSearchThumbPictureSize = 20, - ImageSquarePictureSize = 32, - MaximumImageSize = 1980, - DefaultPictureZoomEnabled = false, - DefaultImageQuality = 80, - MultipleThumbDirectories = false, - ImportProductImagesUsingHash = true, - AzureCacheControlHeader = string.Empty - }); - - settingService.SaveSetting(new StoreInformationSettings - { - StoreClosed = false, - DefaultStoreTheme = "DefaultClean", - AllowCustomerToSelectTheme = false, - DisplayMiniProfilerInPublicStore = false, - DisplayMiniProfilerForAdminOnly = false, - DisplayEuCookieLawWarning = false, - FacebookLink = "http://www.facebook.com/nopCommerce", - TwitterLink = "https://twitter.com/nopCommerce", - YoutubeLink = "http://www.youtube.com/user/nopCommerce", - GooglePlusLink = "https://plus.google.com/+nopcommerce", - HidePoweredByNopCommerce = false - }); - - settingService.SaveSetting(new ExternalAuthenticationSettings - { - AutoRegisterEnabled = true, - RequireEmailValidation = false - }); - - settingService.SaveSetting(new RewardPointsSettings - { - Enabled = true, - ExchangeRate = 1, - PointsForRegistration = 0, - PointsForPurchases_Amount = 10, - PointsForPurchases_Points = 1, - ActivationDelay = 0, - ActivationDelayPeriodId = 0, - DisplayHowMuchWillBeEarned = true, - PointsAccumulatedForAllStores = true, - PageSize = 10, - EarnedRewardPointsAreTaxable = false, - AwardPointsIncludeShipping = true, - AwardPointsIncludePaymentMethodAdditionalFee = true, - AwardPointsExcludeGiftCard = true, - AwardPointsExcludePurchasedRewardPoints = true, - EarnRewardPointsOnlyWhenUsingPurchasedRewardPoints = false - }); - - settingService.SaveSetting(new CurrencySettings - { - DisplayCurrencyLabel = false, - PrimaryStoreCurrencyId = _currencyRepository.Table.Single(c => c.CurrencyCode == "USD").Id, - PrimaryExchangeRateCurrencyId = _currencyRepository.Table.Single(c => c.CurrencyCode == "USD").Id, - ActiveExchangeRateProviderSystemName = "CurrencyExchange.MoneyConverter", - AutoUpdateEnabled = false - }); - - settingService.SaveSetting(new MeasureSettings - { - BaseDimensionId = _measureDimensionRepository.Table.Single(m => m.SystemKeyword == "inches").Id, - BaseWeightId = _measureWeightRepository.Table.Single(m => m.SystemKeyword == "lb").Id, - }); - - settingService.SaveSetting(new MessageTemplatesSettings - { - CaseInvariantReplacement = false, - Color1 = "#b9babe", - Color2 = "#ebecee", - Color3 = "#dde2e6", - }); - - settingService.SaveSetting(new ShoppingCartSettings - { - DisplayCartAfterAddingProduct = false, - DisplayWishlistAfterAddingProduct = false, - MaximumShoppingCartItems = 1000, - MaximumWishlistItems = 1000, - AllowOutOfStockItemsToBeAddedToWishlist = false, - MoveItemsFromWishlistToCart = true, - CartsSharedBetweenStores = false, - ShowProductImagesOnShoppingCart = true, - ShowProductImagesOnWishList = true, - ShowDiscountBox = true, - ShowGiftCardBox = true, - CrossSellsNumber = 4, - EmailWishlistEnabled = true, - AllowAnonymousUsersToEmailWishlist = false, - MiniShoppingCartEnabled = true, - ShowProductImagesInMiniShoppingCart = true, - MiniShoppingCartProductNumber = 5, - RoundPricesDuringCalculation = true, - GroupTierPricesForDistinctShoppingCartItems = false, - AllowCartItemEditing = true, - RenderAssociatedAttributeValueQuantity = true, - RenderProductAttributePrices = true - }); - - settingService.SaveSetting(new OrderSettings - { - ReturnRequestNumberMask = "{ID}", - IsReOrderAllowed = true, - MinOrderSubtotalAmount = 0, - MinOrderSubtotalAmountIncludingTax = false, - MinOrderTotalAmount = 0, - AutoUpdateOrderTotalsOnEditingOrder = false, - AnonymousCheckoutAllowed = true, - TermsOfServiceOnShoppingCartPage = true, - TermsOfServiceOnOrderConfirmPage = false, - OnePageCheckoutEnabled = true, - OnePageCheckoutDisplayOrderTotalsOnPaymentInfoTab = false, - DisableBillingAddressCheckoutStep = false, - DisableOrderCompletedPage = false, - AttachPdfInvoiceToOrderPlacedEmail = false, - AttachPdfInvoiceToOrderCompletedEmail = false, - GeneratePdfInvoiceInCustomerLanguage = true, - AttachPdfInvoiceToOrderPaidEmail = false, - ReturnRequestsEnabled = true, - ReturnRequestsAllowFiles = false, - ReturnRequestsFileMaximumSize = 2048, - NumberOfDaysReturnRequestAvailable = 365, - MinimumOrderPlacementInterval = 30, - ActivateGiftCardsAfterCompletingOrder = false, - DeactivateGiftCardsAfterCancellingOrder = false, - DeactivateGiftCardsAfterDeletingOrder = false, - CompleteOrderWhenDelivered = true, - CustomOrderNumberMask = "{ID}", - ExportWithProducts = true - }); - - settingService.SaveSetting(new SecuritySettings - { - ForceSslForAllPages = false, - EncryptionKey = CommonHelper.GenerateRandomDigitCode(16), - AdminAreaAllowedIpAddresses = null, - EnableXsrfProtectionForAdminArea = true, - EnableXsrfProtectionForPublicStore = true, - HoneypotEnabled = false, - HoneypotInputName = "hpinput" - }); - - settingService.SaveSetting(new ShippingSettings - { - ActiveShippingRateComputationMethodSystemNames = new List { "Shipping.FixedOrByWeight" }, - ActivePickupPointProviderSystemNames = new List { "Pickup.PickupInStore" }, - ShipToSameAddress = true, - AllowPickUpInStore = true, - DisplayPickupPointsOnMap = false, - UseWarehouseLocation = false, - NotifyCustomerAboutShippingFromMultipleLocations = false, - FreeShippingOverXEnabled = false, - FreeShippingOverXValue = decimal.Zero, - FreeShippingOverXIncludingTax = false, - EstimateShippingEnabled = true, - DisplayShipmentEventsToCustomers = false, - DisplayShipmentEventsToStoreOwner = false, - HideShippingTotal = false, - ReturnValidOptionsIfThereAreAny = true, - BypassShippingMethodSelectionIfOnlyOne = false, - UseCubeRootMethod = true, - ConsiderAssociatedProductsDimensions = true - }); - - settingService.SaveSetting(new PaymentSettings - { - ActivePaymentMethodSystemNames = new List - { - "Payments.CheckMoneyOrder", - "Payments.Manual", - "Payments.PayInStore", - "Payments.PurchaseOrder", - }, - AllowRePostingPayments = true, - BypassPaymentMethodSelectionIfOnlyOne = true, - ShowPaymentMethodDescriptions = true, - SkipPaymentInfoStepForRedirectionPaymentMethods = false, - CancelRecurringPaymentsAfterFailedPayment = false - }); - - settingService.SaveSetting(new TaxSettings - { - TaxBasedOn = TaxBasedOn.BillingAddress, - TaxBasedOnPickupPointAddress = false, - TaxDisplayType = TaxDisplayType.ExcludingTax, - ActiveTaxProviderSystemName = "Tax.FixedOrByCountryStateZip", - DefaultTaxAddressId = 0, - DisplayTaxSuffix = false, - DisplayTaxRates = false, - PricesIncludeTax = false, - AllowCustomersToSelectTaxDisplayType = false, - ForceTaxExclusionFromOrderSubtotal = false, - DefaultTaxCategoryId = 0, - HideZeroTax = false, - HideTaxInOrderSummary = false, - ShippingIsTaxable = false, - ShippingPriceIncludesTax = false, - ShippingTaxClassId = 0, - PaymentMethodAdditionalFeeIsTaxable = false, - PaymentMethodAdditionalFeeIncludesTax = false, - PaymentMethodAdditionalFeeTaxClassId = 0, - EuVatEnabled = false, - EuVatShopCountryId = 0, - EuVatAllowVatExemption = true, - EuVatUseWebService = false, - EuVatAssumeValid = false, - EuVatEmailAdminWhenNewVatSubmitted = false, - LogErrors = false - }); - - settingService.SaveSetting(new DateTimeSettings - { - DefaultStoreTimeZoneId = "", - AllowCustomersToSetTimeZone = false - }); - - settingService.SaveSetting(new BlogSettings - { - Enabled = true, - PostsPageSize = 10, - AllowNotRegisteredUsersToLeaveComments = true, - NotifyAboutNewBlogComments = false, - NumberOfTags = 15, - ShowHeaderRssUrl = false, - BlogCommentsMustBeApproved = false, - ShowBlogCommentsPerStore = false - }); - settingService.SaveSetting(new NewsSettings - { - Enabled = true, - AllowNotRegisteredUsersToLeaveComments = true, - NotifyAboutNewNewsComments = false, - ShowNewsOnMainPage = true, - MainPageNewsCount = 3, - NewsArchivePageSize = 10, - ShowHeaderRssUrl = false, - NewsCommentsMustBeApproved = false, - ShowNewsCommentsPerStore = false - }); - - settingService.SaveSetting(new ForumSettings - { - ForumsEnabled = false, - RelativeDateTimeFormattingEnabled = true, - AllowCustomersToDeletePosts = false, - AllowCustomersToEditPosts = false, - AllowCustomersToManageSubscriptions = false, - AllowGuestsToCreatePosts = false, - AllowGuestsToCreateTopics = false, - AllowPostVoting = true, - MaxVotesPerDay = 30, - TopicSubjectMaxLength = 450, - PostMaxLength = 4000, - StrippedTopicMaxLength = 45, - TopicsPageSize = 10, - PostsPageSize = 10, - SearchResultsPageSize = 10, - ActiveDiscussionsPageSize = 50, - LatestCustomerPostsPageSize = 10, - ShowCustomersPostCount = true, - ForumEditor = EditorType.BBCodeEditor, - SignaturesEnabled = true, - AllowPrivateMessages = false, - ShowAlertForPM = false, - PrivateMessagesPageSize = 10, - ForumSubscriptionsPageSize = 10, - NotifyAboutPrivateMessages = false, - PMSubjectMaxLength = 450, - PMTextMaxLength = 4000, - HomePageActiveDiscussionsTopicCount = 5, - ActiveDiscussionsFeedEnabled = false, - ActiveDiscussionsFeedCount = 25, - ForumFeedsEnabled = false, - ForumFeedCount = 10, - ForumSearchTermMinimumLength = 3, - }); - - settingService.SaveSetting(new VendorSettings - { - DefaultVendorPageSizeOptions = "6, 3, 9", - VendorsBlockItemsToDisplay = 0, - ShowVendorOnProductDetailsPage = true, - AllowCustomersToContactVendors = true, - AllowCustomersToApplyForVendorAccount = true, - AllowVendorsToEditInfo = false, - NotifyStoreOwnerAboutVendorInformationChange = true, - MaximumProductNumber = 3000, - AllowVendorsToImportProducts = true - }); - - var eaGeneral = _emailAccountRepository.Table.FirstOrDefault(); - if (eaGeneral == null) - throw new Exception("Default email account cannot be loaded"); - settingService.SaveSetting(new EmailAccountSettings - { - DefaultEmailAccountId = eaGeneral.Id - }); - - settingService.SaveSetting(new WidgetSettings - { - ActiveWidgetSystemNames = new List { "Widgets.NivoSlider" }, - }); - - settingService.SaveSetting(new DisplayDefaultMenuItemSettings - { - DisplayHomePageMenuItem = !installSampleData, - DisplayNewProductsMenuItem = !installSampleData, - DisplayProductSearchMenuItem = !installSampleData, - DisplayCustomerInfoMenuItem = !installSampleData, - DisplayBlogMenuItem = !installSampleData, - DisplayForumsMenuItem = !installSampleData, - DisplayContactUsMenuItem = !installSampleData - }); - } - - protected virtual void InstallCheckoutAttributes() - { - var ca1 = new CheckoutAttribute - { - Name = "Gift wrapping", - IsRequired = true, - ShippableProductRequired = true, - AttributeControlType = AttributeControlType.DropdownList, - DisplayOrder = 1, - }; - ca1.CheckoutAttributeValues.Add(new CheckoutAttributeValue - { - Name = "No", - PriceAdjustment = 0, - DisplayOrder = 1, - IsPreSelected = true, - }); - ca1.CheckoutAttributeValues.Add(new CheckoutAttributeValue - { - Name = "Yes", - PriceAdjustment = 10, - DisplayOrder = 2, - }); - var checkoutAttributes = new List - { - ca1, - }; - _checkoutAttributeRepository.Insert(checkoutAttributes); - } - - protected virtual void InstallSpecificationAttributes() - { - var sa1 = new SpecificationAttribute - { - Name = "Screensize", - DisplayOrder = 1, - }; - sa1.SpecificationAttributeOptions.Add(new SpecificationAttributeOption - { - Name = "13.0''", - DisplayOrder = 2, - }); - sa1.SpecificationAttributeOptions.Add(new SpecificationAttributeOption - { - Name = "13.3''", - DisplayOrder = 3, - }); - sa1.SpecificationAttributeOptions.Add(new SpecificationAttributeOption - { - Name = "14.0''", - DisplayOrder = 4, - }); - sa1.SpecificationAttributeOptions.Add(new SpecificationAttributeOption - { - Name = "15.0''", - DisplayOrder = 4, - }); - sa1.SpecificationAttributeOptions.Add(new SpecificationAttributeOption - { - Name = "15.6''", - DisplayOrder = 5, - }); - var sa2 = new SpecificationAttribute - { - Name = "CPU Type", - DisplayOrder = 2, - }; - sa2.SpecificationAttributeOptions.Add(new SpecificationAttributeOption - { - Name = "Intel Core i5", - DisplayOrder = 1, - }); - sa2.SpecificationAttributeOptions.Add(new SpecificationAttributeOption - { - Name = "Intel Core i7", - DisplayOrder = 2, - }); - var sa3 = new SpecificationAttribute - { - Name = "Memory", - DisplayOrder = 3, - }; - sa3.SpecificationAttributeOptions.Add(new SpecificationAttributeOption - { - Name = "4 GB", - DisplayOrder = 1, - }); - sa3.SpecificationAttributeOptions.Add(new SpecificationAttributeOption - { - Name = "8 GB", - DisplayOrder = 2, - }); - sa3.SpecificationAttributeOptions.Add(new SpecificationAttributeOption - { - Name = "16 GB", - DisplayOrder = 3, - }); - var sa4 = new SpecificationAttribute - { - Name = "Hardrive", - DisplayOrder = 5, - }; - sa4.SpecificationAttributeOptions.Add(new SpecificationAttributeOption - { - Name = "128 GB", - DisplayOrder = 7, - }); - sa4.SpecificationAttributeOptions.Add(new SpecificationAttributeOption - { - Name = "500 GB", - DisplayOrder = 4, - }); - sa4.SpecificationAttributeOptions.Add(new SpecificationAttributeOption - { - Name = "1 TB", - DisplayOrder = 3, - }); - var sa5 = new SpecificationAttribute - { - Name = "Color", - DisplayOrder = 1, - }; - sa5.SpecificationAttributeOptions.Add(new SpecificationAttributeOption - { - Name = "Grey", - DisplayOrder = 2, - ColorSquaresRgb = "#8a97a8" - }); - sa5.SpecificationAttributeOptions.Add(new SpecificationAttributeOption - { - Name = "Red", - DisplayOrder = 3, - ColorSquaresRgb = "#8a374a" - }); - sa5.SpecificationAttributeOptions.Add(new SpecificationAttributeOption - { - Name = "Blue", - DisplayOrder = 4, - ColorSquaresRgb = "#47476f" - }); - var specificationAttributes = new List - { - sa1, - sa2, - sa3, - sa4, - sa5 - }; - _specificationAttributeRepository.Insert(specificationAttributes); - } - - protected virtual void InstallProductAttributes() - { - var productAttributes = new List - { - new ProductAttribute - { - Name = "Color", - }, - new ProductAttribute - { - Name = "Print", - }, - new ProductAttribute - { - Name = "Custom Text", - }, - new ProductAttribute - { - Name = "HDD", - }, - new ProductAttribute - { - Name = "OS", - }, - new ProductAttribute - { - Name = "Processor", - }, - new ProductAttribute - { - Name = "RAM", - }, - new ProductAttribute - { - Name = "Size", - }, - new ProductAttribute - { - Name = "Software", - }, - }; - _productAttributeRepository.Insert(productAttributes); - } - - protected virtual void InstallCategories() - { - //pictures - var pictureService = EngineContext.Current.Resolve(); - var sampleImagesPath = CommonHelper.MapPath("~/content/samples/"); - - - - var categoryTemplateInGridAndLines = _categoryTemplateRepository - .Table.FirstOrDefault(pt => pt.Name == "Products in Grid or Lines"); - if (categoryTemplateInGridAndLines == null) - throw new Exception("Category template cannot be loaded"); - - - //categories - var allCategories = new List(); - var categoryComputers = new Category - { - Name = "Computers", - CategoryTemplateId = categoryTemplateInGridAndLines.Id, - PageSize = 6, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "6, 3, 9", - PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "category_computers.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName("Computers")).Id, - IncludeInTopMenu = true, - Published = true, - DisplayOrder = 1, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow - }; - allCategories.Add(categoryComputers); - _categoryRepository.Insert(categoryComputers); - - - var categoryDesktops = new Category - { - Name = "Desktops", - CategoryTemplateId = categoryTemplateInGridAndLines.Id, - PageSize = 6, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "6, 3, 9", - ParentCategoryId = categoryComputers.Id, - PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "category_desktops.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName("Desktops")).Id, - PriceRanges = "-1000;1000-1200;1200-;", - IncludeInTopMenu = true, - Published = true, - DisplayOrder = 1, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow - }; - allCategories.Add(categoryDesktops); - _categoryRepository.Insert(categoryDesktops); - - - var categoryNotebooks = new Category - { - Name = "Notebooks", - CategoryTemplateId = categoryTemplateInGridAndLines.Id, - PageSize = 6, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "6, 3, 9", - ParentCategoryId = categoryComputers.Id, - PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "category_notebooks.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName("Notebooks")).Id, - IncludeInTopMenu = true, - Published = true, - DisplayOrder = 2, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow - }; - allCategories.Add(categoryNotebooks); - _categoryRepository.Insert(categoryNotebooks); - - - var categorySoftware = new Category - { - Name = "Software", - CategoryTemplateId = categoryTemplateInGridAndLines.Id, - PageSize = 6, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "6, 3, 9", - ParentCategoryId = categoryComputers.Id, - PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "category_software.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName("Software")).Id, - IncludeInTopMenu = true, - Published = true, - DisplayOrder = 3, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow - }; - allCategories.Add(categorySoftware); - _categoryRepository.Insert(categorySoftware); - - - var categoryElectronics = new Category - { - Name = "Electronics", - CategoryTemplateId = categoryTemplateInGridAndLines.Id, - PageSize = 6, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "6, 3, 9", - PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "category_electronics.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName("Electronics")).Id, - IncludeInTopMenu = true, - Published = true, - ShowOnHomePage = true, - DisplayOrder = 2, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow - }; - allCategories.Add(categoryElectronics); - _categoryRepository.Insert(categoryElectronics); - - - var categoryCameraPhoto = new Category - { - Name = "Camera & photo", - CategoryTemplateId = categoryTemplateInGridAndLines.Id, - PageSize = 6, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "6, 3, 9", - ParentCategoryId = categoryElectronics.Id, - PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "category_camera_photo.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName("Camera, photo")).Id, - PriceRanges = "-500;500-;", - IncludeInTopMenu = true, - Published = true, - DisplayOrder = 1, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow - }; - allCategories.Add(categoryCameraPhoto); - _categoryRepository.Insert(categoryCameraPhoto); - - - var categoryCellPhones = new Category - { - Name = "Cell phones", - CategoryTemplateId = categoryTemplateInGridAndLines.Id, - PageSize = 6, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "6, 3, 9", - ParentCategoryId = categoryElectronics.Id, - PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "category_cell_phones.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName("Cell phones")).Id, - IncludeInTopMenu = true, - Published = true, - DisplayOrder = 2, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow - }; - allCategories.Add(categoryCellPhones); - _categoryRepository.Insert(categoryCellPhones); - - - var categoryOthers = new Category - { - Name = "Others", - CategoryTemplateId = categoryTemplateInGridAndLines.Id, - PageSize = 6, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "6, 3, 9", - ParentCategoryId = categoryElectronics.Id, - PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "category_accessories.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName("Accessories")).Id, - IncludeInTopMenu = true, - PriceRanges = "-100;100-;", - Published = true, - DisplayOrder = 3, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow - }; - allCategories.Add(categoryOthers); - _categoryRepository.Insert(categoryOthers); - - - var categoryApparel = new Category - { - Name = "Apparel", - CategoryTemplateId = categoryTemplateInGridAndLines.Id, - PageSize = 6, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "6, 3, 9", - PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "category_apparel.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName("Apparel")).Id, - IncludeInTopMenu = true, - Published = true, - ShowOnHomePage = true, - DisplayOrder = 3, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow - }; - allCategories.Add(categoryApparel); - _categoryRepository.Insert(categoryApparel); - - - var categoryShoes = new Category - { - Name = "Shoes", - CategoryTemplateId = categoryTemplateInGridAndLines.Id, - PageSize = 6, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "6, 3, 9", - ParentCategoryId = categoryApparel.Id, - PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "category_shoes.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName("Shoes")).Id, - PriceRanges = "-500;500-;", - IncludeInTopMenu = true, - Published = true, - DisplayOrder = 1, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow - }; - allCategories.Add(categoryShoes); - _categoryRepository.Insert(categoryShoes); - - - var categoryClothing = new Category - { - Name = "Clothing", - CategoryTemplateId = categoryTemplateInGridAndLines.Id, - PageSize = 6, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "6, 3, 9", - ParentCategoryId = categoryApparel.Id, - PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "category_clothing.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName("Clothing")).Id, - IncludeInTopMenu = true, - Published = true, - DisplayOrder = 2, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow - }; - allCategories.Add(categoryClothing); - _categoryRepository.Insert(categoryClothing); - - - var categoryAccessories = new Category - { - Name = "Accessories", - CategoryTemplateId = categoryTemplateInGridAndLines.Id, - PageSize = 6, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "6, 3, 9", - ParentCategoryId = categoryApparel.Id, - PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "category_apparel_accessories.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName("Apparel Accessories")).Id, - IncludeInTopMenu = true, - PriceRanges = "-100;100-;", - Published = true, - DisplayOrder = 3, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow - }; - allCategories.Add(categoryAccessories); - _categoryRepository.Insert(categoryAccessories); - - - var categoryDigitalDownloads = new Category - { - Name = "Digital downloads", - CategoryTemplateId = categoryTemplateInGridAndLines.Id, - PageSize = 6, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "6, 3, 9", - PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "category_digital_downloads.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName("Digital downloads")).Id, - IncludeInTopMenu = true, - Published = true, - ShowOnHomePage = true, - DisplayOrder = 4, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow - }; - allCategories.Add(categoryDigitalDownloads); - _categoryRepository.Insert(categoryDigitalDownloads); - - - var categoryBooks = new Category - { - Name = "Books", - CategoryTemplateId = categoryTemplateInGridAndLines.Id, - MetaKeywords = "Books, Dictionary, Textbooks", - MetaDescription = "Books category description", - PageSize = 6, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "6, 3, 9", - PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "category_book.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName("Book")).Id, - PriceRanges = "-25;25-50;50-;", - IncludeInTopMenu = true, - Published = true, - DisplayOrder = 5, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow - }; - allCategories.Add(categoryBooks); - _categoryRepository.Insert(categoryBooks); - - - var categoryJewelry = new Category - { - Name = "Jewelry", - CategoryTemplateId = categoryTemplateInGridAndLines.Id, - PageSize = 6, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "6, 3, 9", - PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "category_jewelry.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName("Jewelry")).Id, - PriceRanges = "0-500;500-700;700-3000;", - IncludeInTopMenu = true, - Published = true, - DisplayOrder = 6, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow - }; - allCategories.Add(categoryJewelry); - _categoryRepository.Insert(categoryJewelry); - - var categoryGiftCards = new Category - { - Name = "Gift Cards", - CategoryTemplateId = categoryTemplateInGridAndLines.Id, - PageSize = 6, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "6, 3, 9", - PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "category_gift_cards.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName("Gift Cards")).Id, - IncludeInTopMenu = true, - Published = true, - DisplayOrder = 7, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow - }; - allCategories.Add(categoryGiftCards); - _categoryRepository.Insert(categoryGiftCards); - - - - //search engine names - foreach (var category in allCategories) - { - _urlRecordRepository.Insert(new UrlRecord - { - EntityId = category.Id, - EntityName = "Category", - LanguageId = 0, - IsActive = true, - Slug = category.ValidateSeName("", category.Name, true) - }); - } - } - - protected virtual void InstallManufacturers() - { - var pictureService = EngineContext.Current.Resolve(); - var sampleImagesPath = CommonHelper.MapPath("~/content/samples/"); - - var manufacturerTemplateInGridAndLines = - _manufacturerTemplateRepository.Table.FirstOrDefault(pt => pt.Name == "Products in Grid or Lines"); - if (manufacturerTemplateInGridAndLines == null) - throw new Exception("Manufacturer template cannot be loaded"); - - var allManufacturers = new List(); - var manufacturerAsus = new Manufacturer - { - Name = "Apple", - ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, - PageSize = 6, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "6, 3, 9", - Published = true, - PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "manufacturer_apple.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName("Apple")).Id, - DisplayOrder = 1, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow - }; - _manufacturerRepository.Insert(manufacturerAsus); - allManufacturers.Add(manufacturerAsus); - - - var manufacturerHp = new Manufacturer - { - Name = "HP", - ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, - PageSize = 6, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "6, 3, 9", - Published = true, - PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "manufacturer_hp.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName("Hp")).Id, - DisplayOrder = 5, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow - }; - _manufacturerRepository.Insert(manufacturerHp); - allManufacturers.Add(manufacturerHp); - - - var manufacturerNike = new Manufacturer - { - Name = "Nike", - ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, - PageSize = 6, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "6, 3, 9", - Published = true, - PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "manufacturer_nike.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName("Nike")).Id, - DisplayOrder = 5, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow - }; - _manufacturerRepository.Insert(manufacturerNike); - allManufacturers.Add(manufacturerNike); - - //search engine names - foreach (var manufacturer in allManufacturers) - { - _urlRecordRepository.Insert(new UrlRecord - { - EntityId = manufacturer.Id, - EntityName = "Manufacturer", - LanguageId = 0, - IsActive = true, - Slug = manufacturer.ValidateSeName("", manufacturer.Name, true) - }); - } - } - - protected virtual void InstallProducts(string defaultUserEmail) - { - var productTemplateSimple = _productTemplateRepository.Table.FirstOrDefault(pt => pt.Name == "Simple product"); - if (productTemplateSimple == null) - throw new Exception("Simple product template could not be loaded"); - var productTemplateGrouped = _productTemplateRepository.Table.FirstOrDefault(pt => pt.Name == "Grouped product (with variants)"); - if (productTemplateGrouped == null) - throw new Exception("Grouped product template could not be loaded"); - - //delivery date - var deliveryDate = _deliveryDateRepository.Table.FirstOrDefault(); - if (deliveryDate == null) - throw new Exception("No default deliveryDate could be loaded"); - - //product availability range - var productAvailabilityRange = _productAvailabilityRangeRepository.Table.FirstOrDefault(); - if (productAvailabilityRange == null) - throw new Exception("No default product availability range could be loaded"); - - //default customer/user - var defaultCustomer = _customerRepository.Table.FirstOrDefault(x => x.Email == defaultUserEmail); - if (defaultCustomer == null) - throw new Exception("Cannot load default customer"); - - //default store - var defaultStore = _storeRepository.Table.FirstOrDefault(); - if (defaultStore == null) - throw new Exception("No default store could be loaded"); - - - //pictures - var pictureService = EngineContext.Current.Resolve(); - var sampleImagesPath = CommonHelper.MapPath("~/content/samples/"); - - //downloads - var downloadService = EngineContext.Current.Resolve(); - var sampleDownloadsPath = CommonHelper.MapPath("~/content/samples/"); - - //products - var allProducts = new List(); - - #region Desktops - - - var productBuildComputer = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Build your own computer", - Sku = "COMP_CUST", - ShortDescription = "Build it", - FullDescription = "

    Fight back against cluttered workspaces with the stylish IBM zBC12 All-in-One desktop PC, featuring powerful computing resources and a stunning 20.1-inch widescreen display with stunning XBRITE-HiColor LCD technology. The black IBM zBC12 has a built-in microphone and MOTION EYE camera with face-tracking technology that allows for easy communication with friends and family. And it has a built-in DVD burner and Sony's Movie Store software so you can create a digital entertainment library for personal viewing at your convenience. Easy to setup and even easier to use, this JS-series All-in-One includes an elegantly designed keyboard and a USB mouse.

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "build-your-own-computer", - AllowCustomerReviews = true, - Price = 1200M, - IsShipEnabled = true, - IsFreeShipping = true, - Weight = 2, - Length = 2, - Width = 2, - Height = 2, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - ShowOnHomePage = true, - MarkAsNew = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductAttributeMappings = - { - new ProductAttributeMapping - { - ProductAttribute = _productAttributeRepository.Table.Single(x => x.Name == "Processor"), - AttributeControlType = AttributeControlType.DropdownList, - IsRequired = true, - ProductAttributeValues = - { - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "2.2 GHz Intel Pentium Dual-Core E2200", - DisplayOrder = 1, - }, - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "2.5 GHz Intel Pentium Dual-Core E2200", - IsPreSelected = true, - PriceAdjustment = 15, - DisplayOrder = 2, - } - } - }, - new ProductAttributeMapping - { - ProductAttribute = _productAttributeRepository.Table.Single(x => x.Name == "RAM"), - AttributeControlType = AttributeControlType.DropdownList, - IsRequired = true, - ProductAttributeValues = - { - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "2 GB", - DisplayOrder = 1, - }, - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "4GB", - PriceAdjustment = 20, - DisplayOrder = 2, - }, - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "8GB", - PriceAdjustment = 60, - DisplayOrder = 3, - } - } - }, - new ProductAttributeMapping - { - ProductAttribute = _productAttributeRepository.Table.Single(x => x.Name == "HDD"), - AttributeControlType = AttributeControlType.RadioList, - IsRequired = true, - ProductAttributeValues = - { - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "320 GB", - DisplayOrder = 1, - }, - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "400 GB", - PriceAdjustment = 100, - DisplayOrder = 2, - } - } - }, - new ProductAttributeMapping - { - ProductAttribute = _productAttributeRepository.Table.Single(x => x.Name == "OS"), - AttributeControlType = AttributeControlType.RadioList, - IsRequired = true, - ProductAttributeValues = - { - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "Vista Home", - PriceAdjustment = 50, - IsPreSelected = true, - DisplayOrder = 1, - }, - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "Vista Premium", - PriceAdjustment = 60, - DisplayOrder = 2, - } - } - }, - new ProductAttributeMapping - { - ProductAttribute = _productAttributeRepository.Table.Single(x => x.Name == "Software"), - AttributeControlType = AttributeControlType.Checkboxes, - ProductAttributeValues = - { - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "Microsoft Office", - PriceAdjustment = 50, - IsPreSelected = true, - DisplayOrder = 1, - }, - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "Acrobat Reader", - PriceAdjustment = 10, - DisplayOrder = 2, - }, - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "Total Commander", - PriceAdjustment = 5, - DisplayOrder = 2, - } - } - } - }, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Desktops"), - DisplayOrder = 1, - } - } - }; - allProducts.Add(productBuildComputer); - productBuildComputer.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_Desktops_1.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productBuildComputer.Name)), - DisplayOrder = 1, - }); - productBuildComputer.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_Desktops_2.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productBuildComputer.Name)), - DisplayOrder = 2, - }); - _productRepository.Insert(productBuildComputer); - - - - - - var productDigitalStorm = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Digital Storm VANQUISH 3 Custom Performance PC", - Sku = "DS_VA3_PC", - ShortDescription = "Digital Storm Vanquish 3 Desktop PC", - FullDescription = "

    Blow the doors off todays most demanding games with maximum detail, speed, and power for an immersive gaming experience without breaking the bank.

    Stay ahead of the competition, VANQUISH 3 is fully equipped to easily handle future upgrades, keeping your system on the cutting edge for years to come.

    Each system is put through an extensive stress test, ensuring you experience zero bottlenecks and get the maximum performance from your hardware.

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "compaq-presario-sr1519x-pentium-4-desktop-pc-with-cdrw", - AllowCustomerReviews = true, - Price = 1259M, - IsShipEnabled = true, - Weight = 7, - Length = 7, - Width = 7, - Height = 7, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Desktops"), - DisplayOrder = 1, - } - } - }; - allProducts.Add(productDigitalStorm); - productDigitalStorm.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_DigitalStorm.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productDigitalStorm.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(productDigitalStorm); - - - - - - var productLenovoIdeaCentre = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Lenovo IdeaCentre 600 All-in-One PC", - Sku = "LE_IC_600", - ShortDescription = "", - FullDescription = "

    The A600 features a 21.5in screen, DVD or optional Blu-Ray drive, support for the full beans 1920 x 1080 HD, Dolby Home Cinema certification and an optional hybrid analogue/digital TV tuner.

    Connectivity is handled by 802.11a/b/g - 802.11n is optional - and an ethernet port. You also get four USB ports, a Firewire slot, a six-in-one card reader and a 1.3- or two-megapixel webcam.

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "hp-iq506-touchsmart-desktop-pc", - AllowCustomerReviews = true, - Price = 500M, - IsShipEnabled = true, - Weight = 7, - Length = 7, - Width = 7, - Height = 7, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Desktops"), - DisplayOrder = 1, - } - } - }; - allProducts.Add(productLenovoIdeaCentre); - productLenovoIdeaCentre.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_LenovoIdeaCentre.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productLenovoIdeaCentre.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(productLenovoIdeaCentre); - - - - - #endregion - - #region Notebooks - - var productAppleMacBookPro = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Apple MacBook Pro 13-inch", - Sku = "AP_MBP_13", - ShortDescription = "A groundbreaking Retina display. A new force-sensing trackpad. All-flash architecture. Powerful dual-core and quad-core Intel processors. Together, these features take the notebook to a new level of performance. And they will do the same for you in everything you create.", - FullDescription = "

    With fifth-generation Intel Core processors, the latest graphics, and faster flash storage, the incredibly advanced MacBook Pro with Retina display moves even further ahead in performance and battery life.* *Compared with the previous generation.

    Retina display with 2560-by-1600 resolution

    Fifth-generation dual-core Intel Core i5 processor

    Intel Iris Graphics

    Up to 9 hours of battery life1

    Faster flash storage2

    802.11ac Wi-Fi

    Two Thunderbolt 2 ports for connecting high-performance devices and transferring data at lightning speed

    Two USB 3 ports (compatible with USB 2 devices) and HDMI

    FaceTime HD camera

    Pages, Numbers, Keynote, iPhoto, iMovie, GarageBand included

    OS X, the world's most advanced desktop operating system

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "asus-eee-pc-1000ha-10-inch-netbook", - AllowCustomerReviews = true, - Price = 1800M, - IsShipEnabled = true, - IsFreeShipping = true, - Weight = 3, - Length = 3, - Width = 2, - Height = 2, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 2, - OrderMaximumQuantity = 10000, - Published = true, - ShowOnHomePage = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Notebooks"), - DisplayOrder = 1, - } - }, - ProductManufacturers = - { - new ProductManufacturer - { - Manufacturer = _manufacturerRepository.Table.Single(c => c.Name == "Apple"), - DisplayOrder = 2, - } - }, - ProductSpecificationAttributes = - { - new ProductSpecificationAttribute - { - AllowFiltering = false, - ShowOnProductPage = true, - DisplayOrder = 1, - SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Screensize").SpecificationAttributeOptions.Single(sao => sao.Name == "13.0''") - }, - new ProductSpecificationAttribute - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 2, - SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "CPU Type").SpecificationAttributeOptions.Single(sao => sao.Name == "Intel Core i5") - }, - new ProductSpecificationAttribute - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 3, - SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Memory").SpecificationAttributeOptions.Single(sao => sao.Name == "4 GB") - } - //new ProductSpecificationAttribute - //{ - // AllowFiltering = false, - // ShowOnProductPage = true, - // DisplayOrder = 4, - // SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Hardrive").SpecificationAttributeOptions.Single(sao => sao.Name == "160 GB") - //} - } - }; - allProducts.Add(productAppleMacBookPro); - productAppleMacBookPro.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_macbook_1.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productAppleMacBookPro.Name)), - DisplayOrder = 1, - }); - productAppleMacBookPro.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_macbook_2.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productAppleMacBookPro.Name)), - DisplayOrder = 2, - }); - _productRepository.Insert(productAppleMacBookPro); - - - - - - var productAsusN551JK = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Asus N551JK-XO076H Laptop", - Sku = "AS_551_LP", - ShortDescription = "Laptop Asus N551JK Intel Core i7-4710HQ 2.5 GHz, RAM 16GB, HDD 1TB, Video NVidia GTX 850M 4GB, BluRay, 15.6, Full HD, Win 8.1", - FullDescription = "

    The ASUS N550JX combines cutting-edge audio and visual technology to deliver an unsurpassed multimedia experience. A full HD wide-view IPS panel is tailor-made for watching movies and the intuitive touchscreen makes for easy, seamless navigation. ASUS has paired the N550JXs impressive display with SonicMaster Premium, co-developed with Bang & Olufsen ICEpower audio experts, for true surround sound. A quad-speaker array and external subwoofer combine for distinct vocals and a low bass that you can feel.

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "asus-eee-pc-900ha-89-inch-netbook-black", - AllowCustomerReviews = true, - Price = 1500M, - IsShipEnabled = true, - Weight = 7, - Length = 7, - Width = 7, - Height = 7, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Notebooks"), - DisplayOrder = 1, - } - }, - ProductSpecificationAttributes = - { - new ProductSpecificationAttribute - { - AllowFiltering = false, - ShowOnProductPage = true, - DisplayOrder = 1, - SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Screensize").SpecificationAttributeOptions.Single(sao => sao.Name == "15.6''") - }, - new ProductSpecificationAttribute - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 2, - SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "CPU Type").SpecificationAttributeOptions.Single(sao => sao.Name == "Intel Core i7") - }, - new ProductSpecificationAttribute - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 3, - SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Memory").SpecificationAttributeOptions.Single(sao => sao.Name == "16 GB") - }, - new ProductSpecificationAttribute - { - AllowFiltering = false, - ShowOnProductPage = true, - DisplayOrder = 4, - SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Hardrive").SpecificationAttributeOptions.Single(sao => sao.Name == "1 TB") - } - } - }; - allProducts.Add(productAsusN551JK); - productAsusN551JK.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_asuspc_N551JK.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productAsusN551JK.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(productAsusN551JK); - - - - - - var productSamsungSeries = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Samsung Series 9 NP900X4C Premium Ultrabook", - Sku = "SM_900_PU", - ShortDescription = "Samsung Series 9 NP900X4C-A06US 15-Inch Ultrabook (1.70 GHz Intel Core i5-3317U Processor, 8GB DDR3, 128GB SSD, Windows 8) Ash Black", - FullDescription = "

    Designed with mobility in mind, Samsung's durable, ultra premium, lightweight Series 9 laptop (model NP900X4C-A01US) offers mobile professionals and power users a sophisticated laptop equally suited for work and entertainment. Featuring a minimalist look that is both simple and sophisticated, its polished aluminum uni-body design offers an iconic look and feel that pushes the envelope with an edge just 0.58 inches thin. This Series 9 laptop also includes a brilliant 15-inch SuperBright Plus display with HD+ technology, 128 GB Solid State Drive (SSD), 8 GB of system memory, and up to 10 hours of battery life.

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "hp-pavilion-artist-edition-dv2890nr-141-inch-laptop", - AllowCustomerReviews = true, - Price = 1590M, - IsShipEnabled = true, - Weight = 7, - Length = 7, - Width = 7, - Height = 7, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - //ShowOnHomePage = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Notebooks"), - DisplayOrder = 1, - } - }, - ProductSpecificationAttributes = - { - new ProductSpecificationAttribute - { - AllowFiltering = false, - ShowOnProductPage = true, - DisplayOrder = 1, - SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Screensize").SpecificationAttributeOptions.Single(sao => sao.Name == "15.0''") - }, - new ProductSpecificationAttribute - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 2, - SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "CPU Type").SpecificationAttributeOptions.Single(sao => sao.Name == "Intel Core i5") - }, - new ProductSpecificationAttribute - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 3, - SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Memory").SpecificationAttributeOptions.Single(sao => sao.Name == "8 GB") - }, - new ProductSpecificationAttribute - { - AllowFiltering = false, - ShowOnProductPage = true, - DisplayOrder = 4, - SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Hardrive").SpecificationAttributeOptions.Single(sao => sao.Name == "128 GB") - } - } - }; - allProducts.Add(productSamsungSeries); - productSamsungSeries.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_SamsungNP900X4C.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productSamsungSeries.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(productSamsungSeries); - - - - - - var productHpSpectre = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "HP Spectre XT Pro UltraBook", - Sku = "HP_SPX_UB", - ShortDescription = "HP Spectre XT Pro UltraBook / Intel Core i5-2467M / 13.3 / 4GB / 128GB / Windows 7 Professional / Laptop", - FullDescription = "

    Introducing HP ENVY Spectre XT, the Ultrabook designed for those who want style without sacrificing substance. It's sleek. It's thin. And with Intel. Corer i5 processor and premium materials, it's designed to go anywhere from the bistro to the boardroom, it's unlike anything you've ever seen from HP.

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "hp-pavilion-elite-m9150f-desktop-pc", - AllowCustomerReviews = true, - Price = 1350M, - IsShipEnabled = true, - Weight = 7, - Length = 7, - Width = 7, - Height = 7, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Notebooks"), - DisplayOrder = 1, - } - }, - ProductManufacturers = - { - new ProductManufacturer - { - Manufacturer = _manufacturerRepository.Table.Single(c => c.Name == "HP"), - DisplayOrder = 3, - } - }, - ProductSpecificationAttributes = - { - new ProductSpecificationAttribute - { - AllowFiltering = false, - ShowOnProductPage = true, - DisplayOrder = 1, - SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Screensize").SpecificationAttributeOptions.Single(sao => sao.Name == "13.3''") - }, - new ProductSpecificationAttribute - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 2, - SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "CPU Type").SpecificationAttributeOptions.Single(sao => sao.Name == "Intel Core i5") - }, - new ProductSpecificationAttribute - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 3, - SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Memory").SpecificationAttributeOptions.Single(sao => sao.Name == "4 GB") - }, - new ProductSpecificationAttribute - { - AllowFiltering = false, - ShowOnProductPage = true, - DisplayOrder = 4, - SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Hardrive").SpecificationAttributeOptions.Single(sao => sao.Name == "128 GB") - } - } - }; - allProducts.Add(productHpSpectre); - productHpSpectre.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_HPSpectreXT_1.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productHpSpectre.Name)), - DisplayOrder = 1, - }); - productHpSpectre.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_HPSpectreXT_2.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productHpSpectre.Name)), - DisplayOrder = 2, - }); - _productRepository.Insert(productHpSpectre); - - - - var productHpEnvy = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "HP Envy 6-1180ca 15.6-Inch Sleekbook", - Sku = "HP_ESB_15", - ShortDescription = "HP ENVY 6-1202ea Ultrabook Beats Audio, 3rd generation Intel CoreTM i7-3517U processor, 8GB RAM, 500GB HDD, Microsoft Windows 8, AMD Radeon HD 8750M (2 GB DDR3 dedicated)", - FullDescription = "The UltrabookTM that's up for anything. Thin and light, the HP ENVY is the large screen UltrabookTM with Beats AudioTM. With a soft-touch base that makes it easy to grab and go, it's a laptop that's up for anything.

    Features

    - Windows 8 or other operating systems available

    Top performance. Stylish design. Take notice.

    - At just 19.8 mm (0.78 in) thin, the HP ENVY UltrabookTM is slim and light enough to take anywhere. It's the laptop that gets you noticed with the power to get it done.
    - With an eye-catching metal design, it's a laptop that you want to carry with you. The soft-touch, slip-resistant base gives you the confidence to carry it with ease.

    More entertaining. More gaming. More fun.

    - Own the UltrabookTM with Beats AudioTM, dual speakers, a subwoofer, and an awesome display. Your music, movies and photo slideshows will always look and sound their best.
    - Tons of video memory let you experience incredible gaming and multimedia without slowing down. Create and edit videos in a flash. And enjoy more of what you love to the fullest.
    - The HP ENVY UltrabookTM is loaded with the ports you'd expect on a world-class laptop, but on a Sleekbook instead. Like HDMI, USB, RJ-45, and a headphone jack. You get all the right connections without compromising size.

    Only from HP.

    - Life heats up. That's why there's HP CoolSense technology, which automatically adjusts your notebook's temperature based on usage and conditions. It stays cool. You stay comfortable.
    - With HP ProtectSmart, your notebook's data stays safe from accidental bumps and bruises. It senses motion and plans ahead, stopping your hard drive and protecting your entire digital life.
    - Keep playing even in dimly lit rooms or on red eye flights. The optional backlit keyboard[1] is full-size so you don't compromise comfort. Backlit keyboard. Another bright idea.

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "hp-pavilion-g60-230us-160-inch-laptop", - AllowCustomerReviews = true, - Price = 1460M, - IsShipEnabled = true, - Weight = 7, - Length = 7, - Width = 7, - Height = 7, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Notebooks"), - DisplayOrder = 1, - } - }, - ProductManufacturers = - { - new ProductManufacturer - { - Manufacturer = _manufacturerRepository.Table.Single(c => c.Name == "HP"), - DisplayOrder = 4, - } - }, - ProductSpecificationAttributes = - { - new ProductSpecificationAttribute - { - AllowFiltering = false, - ShowOnProductPage = true, - DisplayOrder = 1, - SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Screensize").SpecificationAttributeOptions.Single(sao => sao.Name == "15.6''") - }, - new ProductSpecificationAttribute - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 2, - SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "CPU Type").SpecificationAttributeOptions.Single(sao => sao.Name == "Intel Core i7") - }, - new ProductSpecificationAttribute - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 3, - SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Memory").SpecificationAttributeOptions.Single(sao => sao.Name == "8 GB") - }, - new ProductSpecificationAttribute - { - AllowFiltering = false, - ShowOnProductPage = true, - DisplayOrder = 4, - SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Hardrive").SpecificationAttributeOptions.Single(sao => sao.Name == "500 GB") - } - } - }; - allProducts.Add(productHpEnvy); - productHpEnvy.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_HpEnvy6.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productHpEnvy.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(productHpEnvy); - - - - - - var productLenovoThinkpad = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Lenovo Thinkpad X1 Carbon Laptop", - Sku = "LE_TX1_CL", - ShortDescription = "Lenovo Thinkpad X1 Carbon Touch Intel Core i7 14 Ultrabook", - FullDescription = "

    The X1 Carbon brings a new level of quality to the ThinkPad legacy of high standards and innovation. It starts with the durable, carbon fiber-reinforced roll cage, making for the best Ultrabook construction available, and adds a host of other new features on top of the old favorites. Because for 20 years, we haven't stopped innovating. And you shouldn't stop benefiting from that.

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "toshiba-satellite-a305-s6908-154-inch-laptop", - AllowCustomerReviews = true, - Price = 1360M, - IsShipEnabled = true, - Weight = 7, - Length = 7, - Width = 7, - Height = 7, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Notebooks"), - DisplayOrder = 1, - } - }, - ProductSpecificationAttributes = - { - new ProductSpecificationAttribute - { - AllowFiltering = false, - ShowOnProductPage = true, - DisplayOrder = 1, - SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Screensize").SpecificationAttributeOptions.Single(sao => sao.Name == "14.0''") - }, - new ProductSpecificationAttribute - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 2, - SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "CPU Type").SpecificationAttributeOptions.Single(sao => sao.Name == "Intel Core i7") - } - //new ProductSpecificationAttribute - //{ - // AllowFiltering = true, - // ShowOnProductPage = true, - // DisplayOrder = 3, - // SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Memory").SpecificationAttributeOptions.Single(sao => sao.Name == "1 GB") - //}, - //new ProductSpecificationAttribute - //{ - // AllowFiltering = false, - // ShowOnProductPage = true, - // DisplayOrder = 4, - // SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Hardrive").SpecificationAttributeOptions.Single(sao => sao.Name == "250 GB") - //} - } - }; - allProducts.Add(productLenovoThinkpad); - productLenovoThinkpad.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_LenovoThinkpad.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productLenovoThinkpad.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(productLenovoThinkpad); - - #endregion - - #region Software - - - var productAdobePhotoshop = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Adobe Photoshop CS4", - Sku = "AD_CS4_PH", - ShortDescription = "Easily find and view all your photos", - FullDescription = "

    Adobe Photoshop CS4 software combines power and simplicity so you can make ordinary photos extraordinary; tell engaging stories in beautiful, personalized creations for print and web; and easily find and view all your photos. New Photoshop.com membership* works with Photoshop CS4 so you can protect your photos with automatic online backup and 2 GB of storage; view your photos anywhere you are; and share your photos in fun, interactive ways with invitation-only Online Albums.

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "adobe-photoshop-elements-7", - AllowCustomerReviews = true, - Price = 75M, - IsShipEnabled = true, - Weight = 2, - Length = 2, - Width = 2, - Height = 3, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Software"), - DisplayOrder = 1, - } - } - }; - allProducts.Add(productAdobePhotoshop); - productAdobePhotoshop.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_AdobePhotoshop.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productAdobePhotoshop.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(productAdobePhotoshop); - - - - - - - var productWindows8Pro = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Windows 8 Pro", - Sku = "MS_WIN_8P", - ShortDescription = "Windows 8 is a Microsoft operating system that was released in 2012 as part of the company's Windows NT OS family. ", - FullDescription = "

    Windows 8 Pro is comparable to Windows 7 Professional and Ultimate and is targeted towards enthusiasts and business users; it includes all the features of Windows 8. Additional features include the ability to receive Remote Desktop connections, the ability to participate in a Windows Server domain, Encrypting File System, Hyper-V, and Virtual Hard Disk Booting, Group Policy as well as BitLocker and BitLocker To Go. Windows Media Center functionality is available only for Windows 8 Pro as a separate software package.

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "corel-paint-shop-pro-photo-x2", - AllowCustomerReviews = true, - Price = 65M, - IsShipEnabled = true, - Weight = 2, - Length = 2, - Width = 2, - Height = 3, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Software"), - DisplayOrder = 1, - } - } - }; - allProducts.Add(productWindows8Pro); - productWindows8Pro.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_Windows8.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productWindows8Pro.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(productWindows8Pro); - - - - - - var productSoundForge = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Sound Forge Pro 11 (recurring)", - Sku = "SF_PRO_11", - ShortDescription = "Advanced audio waveform editor.", - FullDescription = "

    Sound Forge Pro is the application of choice for a generation of creative and prolific artists, producers, and editors. Record audio quickly on a rock-solid platform, address sophisticated audio processing tasks with surgical precision, and render top-notch master files with ease. New features include one-touch recording, metering for the new critical standards, more repair and restoration tools, and exclusive round-trip interoperability with SpectraLayers Pro. Taken together, these enhancements make this edition of Sound Forge Pro the deepest and most advanced audio editing platform available.

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "major-league-baseball-2k9", - IsRecurring = true, - RecurringCycleLength = 30, - RecurringCyclePeriod = RecurringProductCyclePeriod.Months, - RecurringTotalCycles = 12, - AllowCustomerReviews = true, - Price = 54.99M, - IsShipEnabled = true, - Weight = 7, - Length = 7, - Width = 7, - Height = 7, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Software"), - DisplayOrder = 1, - } - } - }; - allProducts.Add(productSoundForge); - productSoundForge.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_SoundForge.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productSoundForge.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(productSoundForge); - - - - - #endregion - - #region Camera, Photo - - - //this one is a grouped product with two associated ones - var productNikonD5500DSLR = new Product - { - ProductType = ProductType.GroupedProduct, - VisibleIndividually = true, - Name = "Nikon D5500 DSLR", - Sku = "N5500DS_0", - ShortDescription = "Slim, lightweight Nikon D5500 packs a vari-angle touchscreen", - FullDescription = "Nikon has announced its latest DSLR, the D5500. A lightweight, compact DX-format camera with a 24.2MP sensor, its the first of its type to offer a vari-angle touchscreen. The D5500 replaces the D5300 in Nikons range, and while it offers much the same features the company says its a much slimmer and lighter prospect. Theres a deep grip for easier handling and built-in Wi-Fi that lets you transfer and share shots via your phone or tablet.", - ProductTemplateId = productTemplateGrouped.Id, - //SeName = "canon-digital-slr-camera", - AllowCustomerReviews = true, - Published = true, - Price = 670M, - IsShipEnabled = true, - Weight = 2, - Length = 2, - Width = 2, - Height = 2, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Camera & photo"), - DisplayOrder = 1, - } - } - }; - allProducts.Add(productNikonD5500DSLR); - productNikonD5500DSLR.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_NikonCamera_1.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productNikonD5500DSLR.Name)), - DisplayOrder = 1, - }); - productNikonD5500DSLR.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_NikonCamera_2.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productNikonD5500DSLR.Name)), - DisplayOrder = 2, - }); - _productRepository.Insert(productNikonD5500DSLR); - var productNikonD5500DSLR_associated_1 = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = false, //hide this products - ParentGroupedProductId = productNikonD5500DSLR.Id, - Name = "Nikon D5500 DSLR - Black", - Sku = "N5500DS_B", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "canon-digital-slr-camera-black", - AllowCustomerReviews = true, - Published = true, - Price = 670M, - IsShipEnabled = true, - Weight = 2, - Length = 2, - Width = 2, - Height = 2, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow - }; - allProducts.Add(productNikonD5500DSLR_associated_1); - productNikonD5500DSLR_associated_1.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_NikonCamera_black.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName("Canon Digital SLR Camera - Black")), - DisplayOrder = 1, - }); - _productRepository.Insert(productNikonD5500DSLR_associated_1); - var productNikonD5500DSLR_associated_2 = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = false, //hide this products - ParentGroupedProductId = productNikonD5500DSLR.Id, - Name = "Nikon D5500 DSLR - Red", - Sku = "N5500DS_R", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "canon-digital-slr-camera-silver", - AllowCustomerReviews = true, - Published = true, - Price = 630M, - IsShipEnabled = true, - Weight = 2, - Length = 2, - Width = 2, - Height = 2, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow - }; - allProducts.Add(productNikonD5500DSLR_associated_2); - productNikonD5500DSLR_associated_2.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_NikonCamera_red.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName("Canon Digital SLR Camera - Silver")), - DisplayOrder = 1, - }); - _productRepository.Insert(productNikonD5500DSLR_associated_2); - - - - - - var productLeica = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Leica T Mirrorless Digital Camera", - Sku = "LT_MIR_DC", - ShortDescription = "Leica T (Typ 701) Silver", - FullDescription = "

    The new Leica T offers a minimalist design that's crafted from a single block of aluminum. Made in Germany and assembled by hand, this 16.3 effective mega pixel camera is easy to use. With a massive 3.7 TFT LCD intuitive touch screen control, the user is able to configure and save their own menu system. The Leica T has outstanding image quality and also has 16GB of built in memory. This is Leica's first system camera to use Wi-Fi. Add the T-App to your portable iOS device and be able to transfer and share your images (free download from the Apple App Store)

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "canon-vixia-hf100-camcorder", - AllowCustomerReviews = true, - Price = 530M, - IsShipEnabled = true, - Weight = 7, - Length = 7, - Width = 7, - Height = 7, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Camera & photo"), - DisplayOrder = 3, - } - } - }; - allProducts.Add(productLeica); - productLeica.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_LeicaT.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productLeica.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(productLeica); - - - - - - - var productAppleICam = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Apple iCam", - Sku = "APPLE_CAM", - ShortDescription = "Photography becomes smart", - FullDescription = "

    A few months ago we featured the amazing WVIL camera, by many considered the future of digital photography. This is another very good looking concept, iCam is the vision of Italian designer Antonio DeRosa, the idea is to have a device that attaches to the iPhone 5, which then allows the user to have a camera with interchangeable lenses. The device would also feature a front-touch screen and a projector. Would be great if apple picked up on this and made it reality.

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "panasonic-hdc-sdt750k-high-definition-3d-camcorder", - AllowCustomerReviews = true, - Price = 1300M, - IsShipEnabled = true, - Weight = 7, - Length = 7, - Width = 7, - Height = 7, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Camera & photo"), - DisplayOrder = 2, - } - }, - ProductManufacturers = - { - new ProductManufacturer - { - Manufacturer = _manufacturerRepository.Table.Single(c => c.Name == "Apple"), - DisplayOrder = 1, - } - } - }; - allProducts.Add(productAppleICam); - productAppleICam.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_iCam.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productAppleICam.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(productAppleICam); - - - - - #endregion - - #region Cell Phone - - var productHtcOne = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "HTC One M8 Android L 5.0 Lollipop", - Sku = "M8_HTC_5L", - ShortDescription = "HTC - One (M8) 4G LTE Cell Phone with 32GB Memory - Gunmetal (Sprint)", - FullDescription = "

    HTC One (M8) Cell Phone for Sprint: With its brushed-metal design and wrap-around unibody frame, the HTC One (M8) is designed to fit beautifully in your hand. It's fun to use with amped up sound and a large Full HD touch screen, and intuitive gesture controls make it seem like your phone almost knows what you need before you do.

    Sprint Easy Pay option available in store.

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "blackberry-bold-9000-phone-black-att", - AllowCustomerReviews = true, - Price = 245M, - IsShipEnabled = true, - Weight = 2, - Length = 2, - Width = 2, - Height = 2, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - ShowOnHomePage = true, - MarkAsNew = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Cell phones"), - DisplayOrder = 1, - } - } - }; - allProducts.Add(productHtcOne); - productHtcOne.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_HTC_One_M8.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productHtcOne.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(productHtcOne); - - - - - - - var productHtcOneMini = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "HTC One Mini Blue", - Sku = "OM_HTC_BL", - ShortDescription = "HTC One and HTC One Mini now available in bright blue hue", - FullDescription = "

    HTC One mini smartphone with 4.30-inch 720x1280 display powered by 1.4GHz processor alongside 1GB RAM and 4-Ultrapixel rear camera.

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "samsung-rugby-a837-phone-black-att", - AllowCustomerReviews = true, - Price = 100M, - IsShipEnabled = true, - Weight = 7, - Length = 7, - Width = 7, - Height = 7, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - MarkAsNew = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Cell phones"), - DisplayOrder = 1, - } - } - }; - allProducts.Add(productHtcOneMini); - productHtcOneMini.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_HTC_One_Mini_1.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productHtcOneMini.Name)), - DisplayOrder = 1, - }); - productHtcOneMini.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_HTC_One_Mini_2.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productHtcOneMini.Name)), - DisplayOrder = 2, - }); - _productRepository.Insert(productHtcOneMini); - - - - - - - var productNokiaLumia = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Nokia Lumia 1020", - Sku = "N_1020_LU", - ShortDescription = "Nokia Lumia 1020 4G Cell Phone (Unlocked)", - FullDescription = "

    Capture special moments for friends and family with this Nokia Lumia 1020 32GB WHITE cell phone that features an easy-to-use 41.0MP rear-facing camera and a 1.2MP front-facing camera. The AMOLED touch screen offers 768 x 1280 resolution for crisp visuals.

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "sony-dcr-sr85-1mp-60gb-hard-drive-handycam-camcorder", - AllowCustomerReviews = true, - Price = 349M, - IsShipEnabled = true, - Weight = 7, - Length = 7, - Width = 7, - Height = 7, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Cell phones"), - DisplayOrder = 1, - } - } - }; - allProducts.Add(productNokiaLumia); - productNokiaLumia.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_Lumia1020.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productNokiaLumia.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(productNokiaLumia); - - - #endregion - - #region Others - - - - var productBeatsPill = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Beats Pill 2.0 Wireless Speaker", - Sku = "BP_20_WSP", - ShortDescription = "Pill 2.0 Portable Bluetooth Speaker (1-Piece): Watch your favorite movies and listen to music with striking sound quality. This lightweight, portable speaker is easy to take with you as you travel to any destination, keeping you entertained wherever you are. ", - FullDescription = "
    • Pair and play with your Bluetooth device with 30 foot range
    • Built-in speakerphone
    • 7 hour rechargeable battery
    • Power your other devices with USB charge out
    • Tap two Beats Pills together for twice the sound with Beats Bond
    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "acer-aspire-one-89-mini-notebook-case-black", - AllowCustomerReviews = true, - Price = 79.99M, - IsShipEnabled = true, - IsFreeShipping = true, - Weight = 2, - Length = 2, - Width = 2, - Height = 3, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - MarkAsNew = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - TierPrices = - { - new TierPrice - { - Quantity = 2, - Price = 19 - }, - new TierPrice - { - Quantity = 5, - Price = 17 - }, - new TierPrice - { - Quantity = 10, - Price = 15, - StartDateTimeUtc = DateTime.UtcNow.AddDays(-7), - EndDateTimeUtc = DateTime.UtcNow.AddDays(7) - } - }, - HasTierPrices = true, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Others"), - DisplayOrder = 1, - } - } - }; - allProducts.Add(productBeatsPill); - productBeatsPill.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_PillBeats_1.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productBeatsPill.Name)), - DisplayOrder = 1, - }); - productBeatsPill.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_PillBeats_2.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productBeatsPill.Name)), - DisplayOrder = 2, - }); - _productRepository.Insert(productBeatsPill); - - - - - - var productUniversalTabletCover = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Universal 7-8 Inch Tablet Cover", - Sku = "TC_78I_UN", - ShortDescription = "Universal protection for 7-inch & 8-inch tablets", - FullDescription = "

    Made of durable polyurethane, our Universal Cover is slim, lightweight, and strong, with protective corners that stretch to hold most 7 and 8-inch tablets securely. This tough case helps protects your tablet from bumps, scuffs, and dings.

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "apc-back-ups-rs-800va-ups-800-va-ups-battery-lead-acid-br800blk", - AllowCustomerReviews = true, - Price = 39M, - IsShipEnabled = true, - Weight = 2, - Length = 2, - Width = 2, - Height = 3, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Others"), - DisplayOrder = 1, - } - } - }; - allProducts.Add(productUniversalTabletCover); - productUniversalTabletCover.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_TabletCover.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productUniversalTabletCover.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(productUniversalTabletCover); - - - - - var productPortableSoundSpeakers = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Portable Sound Speakers", - Sku = "PT_SPK_SN", - ShortDescription = "Universall portable sound speakers", - FullDescription = "

    Your phone cut the cord, now it's time for you to set your music free and buy a Bluetooth speaker. Thankfully, there's one suited for everyone out there.

    Some Bluetooth speakers excel at packing in as much functionality as the unit can handle while keeping the price down. Other speakers shuck excess functionality in favor of premium build materials instead. Whatever path you choose to go down, you'll be greeted with many options to suit your personal tastes.

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "microsoft-bluetooth-notebook-mouse-5000-macwindows", - AllowCustomerReviews = true, - Price = 37M, - IsShipEnabled = true, - Weight = 7, - Length = 7, - Width = 7, - Height = 7, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Others"), - DisplayOrder = 1, - } - } - }; - allProducts.Add(productPortableSoundSpeakers); - productPortableSoundSpeakers.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_Speakers.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productPortableSoundSpeakers.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(productPortableSoundSpeakers); - - - #endregion - - #region Shoes - - - var productNikeFloral = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Nike Floral Roshe Customized Running Shoes", - Sku = "NK_FRC_RS", - ShortDescription = "When you ran across these shoes, you will immediately fell in love and needed a pair of these customized beauties.", - FullDescription = "

    Each Rosh Run is personalized and exclusive, handmade in our workshop Custom. Run Your Rosh creations born from the hand of an artist specialized in sneakers, more than 10 years of experience.

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "adidas-womens-supernova-csh-7-running-shoe", - AllowCustomerReviews = true, - Price = 40M, - IsShipEnabled = true, - Weight = 2, - Length = 2, - Width = 2, - Height = 2, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Apparel").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductAttributeMappings = - { - new ProductAttributeMapping - { - ProductAttribute = _productAttributeRepository.Table.Single(x => x.Name == "Size"), - AttributeControlType = AttributeControlType.DropdownList, - IsRequired = true, - ProductAttributeValues = - { - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "8", - DisplayOrder = 1, - }, - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "9", - DisplayOrder = 2, - }, - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "10", - DisplayOrder = 3, - }, - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "11", - DisplayOrder = 4, - } - } - }, - new ProductAttributeMapping - { - ProductAttribute = _productAttributeRepository.Table.Single(x => x.Name == "Color"), - AttributeControlType = AttributeControlType.DropdownList, - IsRequired = true, - ProductAttributeValues = - { - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "White/Blue", - DisplayOrder = 1, - }, - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "White/Black", - DisplayOrder = 2, - }, - } - }, - new ProductAttributeMapping - { - ProductAttribute = _productAttributeRepository.Table.Single(x => x.Name == "Print"), - AttributeControlType = AttributeControlType.ImageSquares, - IsRequired = true, - ProductAttributeValues = - { - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "Natural", - DisplayOrder = 1, - ImageSquaresPictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "p_attribute_print_2.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName("Natural Print")).Id, - }, - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "Fresh", - DisplayOrder = 2, - ImageSquaresPictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "p_attribute_print_1.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName("Fresh Print")).Id, - }, - } - } - }, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Shoes"), - DisplayOrder = 1, - } - }, - ProductManufacturers = - { - new ProductManufacturer - { - Manufacturer = _manufacturerRepository.Table.Single(c => c.Name == "Nike"), - DisplayOrder = 2, - } - }, - ProductSpecificationAttributes = - { - new ProductSpecificationAttribute - { - AllowFiltering = true, - ShowOnProductPage = false, - DisplayOrder = 1, - SpecificationAttributeOption = - _specificationAttributeRepository.Table.Single(sa => sa.Name == "Color") - .SpecificationAttributeOptions.Single(sao => sao.Name == "Grey") - } - } - }; - allProducts.Add(productNikeFloral); - productNikeFloral.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_NikeFloralShoe_1.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName(productNikeFloral.Name)), - DisplayOrder = 1, - }); - productNikeFloral.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_NikeFloralShoe_2.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName(productNikeFloral.Name)), - DisplayOrder = 2, - }); - _productRepository.Insert(productNikeFloral); - - productNikeFloral.ProductAttributeMappings.First(x => x.ProductAttribute.Name == "Print").ProductAttributeValues.First(x => x.Name == "Natural").PictureId = productNikeFloral.ProductPictures.ElementAt(0).PictureId; - productNikeFloral.ProductAttributeMappings.First(x => x.ProductAttribute.Name == "Print").ProductAttributeValues.First(x => x.Name == "Fresh").PictureId = productNikeFloral.ProductPictures.ElementAt(1).PictureId; - _productRepository.Update(productNikeFloral); - - - - var productAdidas = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "adidas Consortium Campus 80s Running Shoes", - Sku = "AD_C80_RS", - ShortDescription = "adidas Consortium Campus 80s Primeknit Light Maroon/Running Shoes", - FullDescription = "

    One of three colorways of the adidas Consortium Campus 80s Primeknit set to drop alongside each other. This pair comes in light maroon and running white. Featuring a maroon-based primeknit upper with white accents. A limited release, look out for these at select adidas Consortium accounts worldwide.

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "etnies-mens-digit-sneaker", - AllowCustomerReviews = true, - Price = 27.56M, - IsShipEnabled = true, - Weight = 2, - Length = 2, - Width = 2, - Height = 2, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Apparel").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - //ShowOnHomePage = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductAttributeMappings = - { - new ProductAttributeMapping - { - ProductAttribute = _productAttributeRepository.Table.Single(x => x.Name == "Size"), - AttributeControlType = AttributeControlType.DropdownList, - IsRequired = true, - ProductAttributeValues = - { - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "8", - DisplayOrder = 1, - }, - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "9", - DisplayOrder = 2, - }, - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "10", - DisplayOrder = 3, - }, - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "11", - DisplayOrder = 4, - } - } - }, - new ProductAttributeMapping - { - ProductAttribute = _productAttributeRepository.Table.Single(x => x.Name == "Color"), - AttributeControlType = AttributeControlType.ColorSquares, - IsRequired = true, - ProductAttributeValues = - { - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "Red", - IsPreSelected = true, - ColorSquaresRgb = "#663030", - DisplayOrder = 1, - }, - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "Blue", - ColorSquaresRgb = "#363656", - DisplayOrder = 2, - }, - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "Silver", - ColorSquaresRgb = "#c5c5d5", - DisplayOrder = 3, - } - } - } - }, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Shoes"), - DisplayOrder = 1, - } - }, - ProductSpecificationAttributes = - { - new ProductSpecificationAttribute - { - AllowFiltering = true, - ShowOnProductPage = false, - DisplayOrder = 1, - SpecificationAttributeOption = - _specificationAttributeRepository.Table.Single(sa => sa.Name == "Color") - .SpecificationAttributeOptions.Single(sao => sao.Name == "Grey") - }, - new ProductSpecificationAttribute - { - AllowFiltering = true, - ShowOnProductPage = false, - DisplayOrder = 2, - SpecificationAttributeOption = - _specificationAttributeRepository.Table.Single(sa => sa.Name == "Color") - .SpecificationAttributeOptions.Single(sao => sao.Name == "Red") - }, - new ProductSpecificationAttribute - { - AllowFiltering = true, - ShowOnProductPage = false, - DisplayOrder = 3, - SpecificationAttributeOption = - _specificationAttributeRepository.Table.Single(sa => sa.Name == "Color") - .SpecificationAttributeOptions.Single(sao => sao.Name == "Blue") - }, - } - }; - allProducts.Add(productAdidas); - productAdidas.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_adidas.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName(productAdidas.Name)), - DisplayOrder = 1, - }); - productAdidas.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_adidas_2.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName(productAdidas.Name)), - DisplayOrder = 2, - }); - productAdidas.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_adidas_3.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName(productAdidas.Name)), - DisplayOrder = 3, - }); - - - _productRepository.Insert(productAdidas); - - productAdidas.ProductAttributeMappings.First(x => x.ProductAttribute.Name == "Color").ProductAttributeValues.First(x => x.Name == "Red").PictureId = productAdidas.ProductPictures.ElementAt(0).PictureId; - productAdidas.ProductAttributeMappings.First(x => x.ProductAttribute.Name == "Color").ProductAttributeValues.First(x => x.Name == "Blue").PictureId = productAdidas.ProductPictures.ElementAt(1).PictureId; - productAdidas.ProductAttributeMappings.First(x => x.ProductAttribute.Name == "Color").ProductAttributeValues.First(x => x.Name == "Silver").PictureId = productAdidas.ProductPictures.ElementAt(2).PictureId; - _productRepository.Update(productAdidas); - - - - - var productNikeZoom = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Nike SB Zoom Stefan Janoski \"Medium Mint\"", - Sku = "NK_ZSJ_MM", - ShortDescription = "Nike SB Zoom Stefan Janoski Dark Grey Medium Mint Teal ...", - FullDescription = "The newly Nike SB Zoom Stefan Janoski gets hit with a \"Medium Mint\" accents that sits atop a Dark Grey suede. Expected to drop in October.", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "v-blue-juniors-cuffed-denim-short-with-rhinestones", - AllowCustomerReviews = true, - Price = 30M, - IsShipEnabled = true, - Weight = 2, - Length = 2, - Width = 2, - Height = 2, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Apparel").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Shoes"), - DisplayOrder = 1, - } - }, - ProductManufacturers = - { - new ProductManufacturer - { - Manufacturer = _manufacturerRepository.Table.Single(c => c.Name == "Nike"), - DisplayOrder = 2, - } - }, - ProductSpecificationAttributes = - { - new ProductSpecificationAttribute - { - AllowFiltering = true, - ShowOnProductPage = false, - DisplayOrder = 1, - SpecificationAttributeOption = - _specificationAttributeRepository.Table.Single(sa => sa.Name == "Color") - .SpecificationAttributeOptions.Single(sao => sao.Name == "Grey") - } - } - }; - - allProducts.Add(productNikeZoom); - productNikeZoom.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_NikeZoom.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName(productNikeZoom.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(productNikeZoom); - - - #endregion - - #region Clothing - - var productNikeTailwind = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Nike Tailwind Loose Short-Sleeve Running Shirt", - Sku = "NK_TLS_RS", - ShortDescription = "", - FullDescription = "

    Boost your adrenaline with the Nike Women's Tailwind Running Shirt. The lightweight, slouchy fit is great for layering, and moisture-wicking fabrics keep you feeling at your best. This tee has a notched hem for an enhanced range of motion, while flat seams with reinforcement tape lessen discomfort and irritation over longer distances. Put your keys and card in the side zip pocket and take off in your Nike running t-shirt.

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "50s-rockabilly-polka-dot-top-jr-plus-size", - AllowCustomerReviews = true, - Published = true, - Price = 15M, - IsShipEnabled = true, - Weight = 1, - Length = 2, - Width = 3, - Height = 3, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Apparel").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductAttributeMappings = - { - new ProductAttributeMapping - { - ProductAttribute = _productAttributeRepository.Table.Single(x => x.Name == "Size"), - AttributeControlType = AttributeControlType.DropdownList, - IsRequired = true, - ProductAttributeValues = - { - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "Small", - DisplayOrder = 1, - }, - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "1X", - DisplayOrder = 2, - }, - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "2X", - DisplayOrder = 3, - }, - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "3X", - DisplayOrder = 4, - }, - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "4X", - DisplayOrder = 5, - }, - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "5X", - DisplayOrder = 6, - } - } - } - }, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Clothing"), - DisplayOrder = 1, - } - }, - ProductManufacturers = - { - new ProductManufacturer - { - Manufacturer = _manufacturerRepository.Table.Single(c => c.Name == "Nike"), - DisplayOrder = 2, - } - } - }; - allProducts.Add(productNikeTailwind); - productNikeTailwind.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_NikeShirt.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName(productNikeTailwind.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(productNikeTailwind); - - - - - var productOversizedWomenTShirt = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Oversized Women T-Shirt", - Sku = "WM_OVR_TS", - ShortDescription = "", - FullDescription = "

    This oversized women t-Shirt needs minimum ironing. It is a great product at a great value!

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "arrow-mens-wrinkle-free-pinpoint-solid-long-sleeve", - AllowCustomerReviews = true, - Price = 24M, - IsShipEnabled = true, - Weight = 4, - Length = 3, - Width = 3, - Height = 3, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Apparel").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - TierPrices = - { - new TierPrice - { - Quantity = 3, - Price = 21 - }, - new TierPrice - { - Quantity = 7, - Price = 19 - }, - new TierPrice - { - Quantity = 10, - Price = 16 - } - }, - HasTierPrices = true, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Clothing"), - DisplayOrder = 1, - } - } - }; - allProducts.Add(productOversizedWomenTShirt); - productOversizedWomenTShirt.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_WomenTShirt.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName(productOversizedWomenTShirt.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(productOversizedWomenTShirt); - - - - - var productCustomTShirt = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Custom T-Shirt", - Sku = "CS_TSHIRT", - ShortDescription = "T-Shirt - Add Your Content", - FullDescription = "

    Comfort comes in all shapes and forms, yet this tee out does it all. Rising above the rest, our classic cotton crew provides the simple practicality you need to make it through the day. Tag-free, relaxed fit wears well under dress shirts or stands alone in laid-back style. Reinforced collar and lightweight feel give way to long-lasting shape and breathability. One less thing to worry about, rely on this tee to provide comfort and ease with every wear.

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "custom-t-shirt", - AllowCustomerReviews = true, - Price = 15M, - IsShipEnabled = true, - Weight = 4, - Length = 3, - Width = 3, - Height = 3, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Apparel").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductAttributeMappings = - { - new ProductAttributeMapping - { - ProductAttribute = _productAttributeRepository.Table.Single(x => x.Name == "Custom Text"), - TextPrompt = "Enter your text:", - AttributeControlType = AttributeControlType.TextBox, - IsRequired = true, - } - }, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Clothing"), - DisplayOrder = 1, - } - } - }; - allProducts.Add(productCustomTShirt); - productCustomTShirt.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_CustomTShirt.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productCustomTShirt.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(productCustomTShirt); - - - - - - - var productLeviJeans = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Levi's 511 Jeans", - Sku = "LV_511_JN", - ShortDescription = "Levi's Faded Black 511 Jeans ", - FullDescription = "

    Between a skinny and straight fit, our 511™ slim fit jeans are cut close without being too restricting. Slim throughout the thigh and leg opening for a long and lean look.

    • Slouch1y at top; sits below the waist
    • Slim through the leg, close at the thigh and straight to the ankle
    • Stretch for added comfort
    • Classic five-pocket styling
    • 99% Cotton, 1% Spandex, 11.2 oz. - Imported
    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "levis-skinny-511-jeans", - AllowCustomerReviews = true, - Price = 43.5M, - OldPrice = 55M, - IsShipEnabled = true, - Weight = 2, - Length = 2, - Width = 2, - Height = 2, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Apparel").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - TierPrices = - { - new TierPrice - { - Quantity = 3, - Price = 40 - }, - new TierPrice - { - Quantity = 6, - Price = 38 - }, - new TierPrice - { - Quantity = 10, - Price = 35 - } - }, - HasTierPrices = true, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Clothing"), - DisplayOrder = 1, - } - } - }; - allProducts.Add(productLeviJeans); - - productLeviJeans.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_LeviJeans_1.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName(productLeviJeans.Name)), - DisplayOrder = 1, - }); - productLeviJeans.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_LeviJeans_2.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName(productLeviJeans.Name)), - DisplayOrder = 2, - }); - _productRepository.Insert(productLeviJeans); - - - #endregion - - #region Accessories - - - var productObeyHat = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Obey Propaganda Hat", - Sku = "OB_HAT_PR", - ShortDescription = "", - FullDescription = "

    Printed poplin 5 panel camp hat with debossed leather patch and web closure

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "indiana-jones-shapeable-wool-hat", - AllowCustomerReviews = true, - Price = 30M, - IsShipEnabled = true, - Weight = 2, - Length = 2, - Width = 2, - Height = 2, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Apparel").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductAttributeMappings = - { - new ProductAttributeMapping - { - ProductAttribute = _productAttributeRepository.Table.Single(x => x.Name == "Size"), - AttributeControlType = AttributeControlType.DropdownList, - IsRequired = true, - ProductAttributeValues = - { - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "Small", - DisplayOrder = 1, - }, - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "Medium", - DisplayOrder = 2, - }, - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "Large", - DisplayOrder = 3, - }, - new ProductAttributeValue - { - AttributeValueType = AttributeValueType.Simple, - Name = "X-Large", - DisplayOrder = 4, - } - } - } - }, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Accessories"), - DisplayOrder = 1, - } - } - }; - allProducts.Add(productObeyHat); - productObeyHat.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_hat.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName(productObeyHat.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(productObeyHat); - - - - - - - - var productBelt = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Reversible Horseferry Check Belt", - Sku = "RH_CHK_BL", - ShortDescription = "Reversible belt in Horseferry check with smooth leather trim", - FullDescription = "

    Reversible belt in Horseferry check with smooth leather trim

    Leather lining, polished metal buckle

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "nike-golf-casual-belt", - AllowCustomerReviews = true, - Price = 45M, - IsShipEnabled = true, - Weight = 7, - Length = 7, - Width = 7, - Height = 7, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Apparel").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - ProductAvailabilityRangeId = productAvailabilityRange.Id, - StockQuantity = 0, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Accessories"), - DisplayOrder = 1, - } - } - }; - allProducts.Add(productBelt); - productBelt.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_Belt.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productBelt.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(productBelt); - - - - - - - var productSunglasses = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Ray Ban Aviator Sunglasses", - Sku = "RB_AVR_SG", - ShortDescription = "Aviator sunglasses are one of the first widely popularized styles of modern day sunwear.", - FullDescription = "

    Since 1937, Ray-Ban can genuinely claim the title as the world's leading sunglasses and optical eyewear brand. Combining the best of fashion and sports performance, the Ray-Ban line of Sunglasses delivers a truly classic style that will have you looking great today and for years to come.

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "ray-ban-aviator-sunglasses-rb-3025", - AllowCustomerReviews = true, - Price = 25M, - IsShipEnabled = true, - Weight = 7, - Length = 7, - Width = 7, - Height = 7, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Apparel").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Accessories"), - DisplayOrder = 1, - } - } - }; - allProducts.Add(productSunglasses); - productSunglasses.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_Sunglasses.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName(productSunglasses.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(productSunglasses); - - #endregion - - #region Digital Downloads - - - var downloadNightVision1 = new Download - { - DownloadGuid = Guid.NewGuid(), - ContentType = MimeTypes.ApplicationXZipCo, - DownloadBinary = File.ReadAllBytes(sampleDownloadsPath + "product_NightVision_1.zip"), - Extension = ".zip", - Filename = "Night_Vision_1", - IsNew = true, - }; - downloadService.InsertDownload(downloadNightVision1); - var downloadNightVision2 = new Download - { - DownloadGuid = Guid.NewGuid(), - ContentType = MimeTypes.TextPlain, - DownloadBinary = File.ReadAllBytes(sampleDownloadsPath + "product_NightVision_2.txt"), - Extension = ".txt", - Filename = "Night_Vision_1", - IsNew = true, - }; - downloadService.InsertDownload(downloadNightVision2); - var productNightVision = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Night Visions", - Sku = "NIGHT_VSN", - ShortDescription = "Night Visions is the debut studio album by American rock band Imagine Dragons.", - FullDescription = "

    Original Release Date: September 4, 2012

    Release Date: September 4, 2012

    Genre - Alternative rock, indie rock, electronic rock

    Label - Interscope/KIDinaKORNER

    Copyright: (C) 2011 Interscope Records

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "poker-face", - AllowCustomerReviews = true, - Price = 2.8M, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Downloadable Products").Id, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - IsDownload = true, - DownloadId = downloadNightVision1.Id, - DownloadActivationType = DownloadActivationType.WhenOrderIsPaid, - UnlimitedDownloads = true, - HasUserAgreement = false, - HasSampleDownload = true, - SampleDownloadId = downloadNightVision2.Id, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Digital downloads"), - DisplayOrder = 1, - } - } - }; - allProducts.Add(productNightVision); - productNightVision.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_NightVisions.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productNightVision.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(productNightVision); - - - - - - var downloadIfYouWait1 = new Download - { - DownloadGuid = Guid.NewGuid(), - ContentType = MimeTypes.ApplicationXZipCo, - DownloadBinary = File.ReadAllBytes(sampleDownloadsPath + "product_IfYouWait_1.zip"), - Extension = ".zip", - Filename = "If_You_Wait_1", - IsNew = true, - }; - downloadService.InsertDownload(downloadIfYouWait1); - var downloadIfYouWait2 = new Download - { - DownloadGuid = Guid.NewGuid(), - ContentType = MimeTypes.TextPlain, - DownloadBinary = File.ReadAllBytes(sampleDownloadsPath + "product_IfYouWait_2.txt"), - Extension = ".txt", - Filename = "If_You_Wait_1", - IsNew = true, - }; - downloadService.InsertDownload(downloadIfYouWait2); - var productIfYouWait = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "If You Wait (donation)", - Sku = "IF_YOU_WT", - ShortDescription = "If You Wait is the debut studio album by English indie pop band London Grammar", - FullDescription = "

    Original Release Date: September 6, 2013

    Genre - Electronica, dream pop downtempo, pop

    Label - Metal & Dust/Ministry of Sound

    Producer - Tim Bran, Roy Kerr London, Grammar

    Length - 43:22

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "single-ladies-put-a-ring-on-it", - CustomerEntersPrice = true, - MinimumCustomerEnteredPrice = 0.5M, - MaximumCustomerEnteredPrice = 100M, - AllowCustomerReviews = true, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Downloadable Products").Id, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - IsDownload = true, - DownloadId = downloadIfYouWait1.Id, - DownloadActivationType = DownloadActivationType.WhenOrderIsPaid, - UnlimitedDownloads = true, - HasUserAgreement = false, - HasSampleDownload = true, - SampleDownloadId = downloadIfYouWait2.Id, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Digital downloads"), - DisplayOrder = 1, - } - } - }; - allProducts.Add(productIfYouWait); - - productIfYouWait.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_IfYouWait.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productIfYouWait.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(productIfYouWait); - - - - - - var downloadScienceAndFaith = new Download - { - DownloadGuid = Guid.NewGuid(), - ContentType = MimeTypes.ApplicationXZipCo, - DownloadBinary = File.ReadAllBytes(sampleDownloadsPath + "product_ScienceAndFaith_1.zip"), - Extension = ".zip", - Filename = "Science_And_Faith", - IsNew = true, - }; - downloadService.InsertDownload(downloadScienceAndFaith); - var productScienceAndFaith = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Science & Faith", - Sku = "SCI_FAITH", - ShortDescription = "Science & Faith is the second studio album by Irish pop rock band The Script.", - FullDescription = "

    # Original Release Date: September 10, 2010
    # Label: RCA, Epic/Phonogenic(America)
    # Copyright: 2010 RCA Records.

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "the-battle-of-los-angeles", - AllowCustomerReviews = true, - CustomerEntersPrice = true, - MinimumCustomerEnteredPrice = 0.5M, - MaximumCustomerEnteredPrice = 1000M, - Price = decimal.Zero, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Downloadable Products").Id, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - IsDownload = true, - DownloadId = downloadScienceAndFaith.Id, - DownloadActivationType = DownloadActivationType.WhenOrderIsPaid, - UnlimitedDownloads = true, - HasUserAgreement = false, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Digital downloads"), - DisplayOrder = 1, - } - } - }; - allProducts.Add(productScienceAndFaith); - productScienceAndFaith.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_ScienceAndFaith.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productScienceAndFaith.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(productScienceAndFaith); - - - - #endregion - - #region Books - - var productFahrenheit = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Fahrenheit 451 by Ray Bradbury", - Sku = "FR_451_RB", - ShortDescription = "Fahrenheit 451 is a dystopian novel by Ray Bradbury published in 1953. It is regarded as one of his best works.", - FullDescription = "

    The novel presents a future American society where books are outlawed and firemen burn any that are found. The title refers to the temperature that Bradbury understood to be the autoignition point of paper.

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "best-grilling-recipes", - AllowCustomerReviews = true, - Price = 27M, - OldPrice = 30M, - IsShipEnabled = true, - IsFreeShipping = true, - Weight = 2, - Length = 2, - Width = 2, - Height = 2, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Books").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Books"), - DisplayOrder = 1, - } - } - }; - allProducts.Add(productFahrenheit); - productFahrenheit.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_Fahrenheit451.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productFahrenheit.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(productFahrenheit); - - - - var productFirstPrizePies = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "First Prize Pies", - Sku = "FIRST_PRP", - ShortDescription = "Allison Kave made pies as a hobby, until one day her boyfriend convinced her to enter a Brooklyn pie-making contest. She won. In fact, her pies were such a hit that she turned pro.", - FullDescription = "

    First Prize Pies, a boutique, made-to-order pie business that originated on New York's Lower East Side, has become synonymous with tempting and unusual confections. For the home baker who is passionate about seasonal ingredients and loves a creative approach to recipes, First Prize Pies serves up 52 weeks of seasonal and eclectic pastries in an interesting pie-a-week format. Clear instructions, technical tips and creative encouragement guide novice bakers as well as pie mavens. With its nostalgia-evoking photos of homemade pies fresh out of the oven, First Prize Pies will be as giftable as it is practical.

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "eatingwell-in-season", - AllowCustomerReviews = true, - Price = 51M, - OldPrice = 67M, - IsShipEnabled = true, - Weight = 2, - Length = 2, - Width = 2, - Height = 2, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Books").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Books"), - DisplayOrder = 1, - } - } - }; - allProducts.Add(productFirstPrizePies); - productFirstPrizePies.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_FirstPrizePies.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productFirstPrizePies.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(productFirstPrizePies); - - - - - - - - var productPrideAndPrejudice = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Pride and Prejudice", - Sku = "PRIDE_PRJ", - ShortDescription = "Pride and Prejudice is a novel of manners by Jane Austen, first published in 1813.", - FullDescription = "

    Set in England in the early 19th century, Pride and Prejudice tells the story of Mr and Mrs Bennet's five unmarried daughters after the rich and eligible Mr Bingley and his status-conscious friend, Mr Darcy, have moved into their neighbourhood. While Bingley takes an immediate liking to the eldest Bennet daughter, Jane, Darcy has difficulty adapting to local society and repeatedly clashes with the second-eldest Bennet daughter, Elizabeth.

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "the-best-skillet-recipes", - AllowCustomerReviews = true, - Price = 24M, - OldPrice = 35M, - IsShipEnabled = true, - Weight = 2, - Length = 2, - Width = 2, - Height = 2, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Books").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Books"), - DisplayOrder = 1, - } - } - }; - allProducts.Add(productPrideAndPrejudice); - productPrideAndPrejudice.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_PrideAndPrejudice.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productPrideAndPrejudice.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(productPrideAndPrejudice); - - - - #endregion - - #region Jewelry - - - - var productElegantGemstoneNecklace = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Elegant Gemstone Necklace (rental)", - Sku = "EG_GEM_NL", - ShortDescription = "Classic and elegant gemstone necklace now available in our store", - FullDescription = "

    For those who like jewelry, creating their ownelegant jewelry from gemstone beads provides an economical way to incorporate genuine gemstones into your jewelry wardrobe. Manufacturers create beads from all kinds of precious gemstones and semi-precious gemstones, which are available in bead shops, craft stores, and online marketplaces.

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "diamond-pave-earrings", - AllowCustomerReviews = true, - IsRental = true, - RentalPriceLength = 1, - RentalPricePeriod = RentalPricePeriod.Days, - Price = 30M, - IsShipEnabled = true, - Weight = 2, - Length = 2, - Width = 2, - Height = 2, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Jewelry").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - MarkAsNew = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Jewelry"), - DisplayOrder = 1, - } - } - }; - allProducts.Add(productElegantGemstoneNecklace); - productElegantGemstoneNecklace.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_GemstoneNecklaces.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName(productElegantGemstoneNecklace.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(productElegantGemstoneNecklace); - - - - - - var productFlowerGirlBracelet = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Flower Girl Bracelet", - Sku = "FL_GIRL_B", - ShortDescription = "Personalised Flower Braceled", - FullDescription = "

    This is a great gift for your flower girl to wear on your wedding day. A delicate bracelet that is made with silver plated soldered cable chain, gives this bracelet a dainty look for young wrist. A Swarovski heart, shown in Rose, hangs off a silver plated flower. Hanging alongside the heart is a silver plated heart charm with Flower Girl engraved on both sides. This is a great style for the younger flower girl.

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "diamond-tennis-bracelet", - AllowCustomerReviews = true, - Price = 360M, - IsShipEnabled = true, - IsFreeShipping = true, - Weight = 2, - Length = 2, - Width = 2, - Height = 2, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Jewelry").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Jewelry"), - DisplayOrder = 1, - } - } - }; - allProducts.Add(productFlowerGirlBracelet); - productFlowerGirlBracelet.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_FlowerBracelet.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName(productFlowerGirlBracelet.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(productFlowerGirlBracelet); - - - - - - - - - var productEngagementRing = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Vintage Style Engagement Ring", - Sku = "VS_ENG_RN", - ShortDescription = "1.24 Carat (ctw) in 14K White Gold (Certified)", - FullDescription = "

    Dazzle her with this gleaming 14 karat white gold vintage proposal. A ravishing collection of 11 decadent diamonds come together to invigorate a superbly ornate gold shank. Total diamond weight on this antique style engagement ring equals 1 1/4 carat (ctw). Item includes diamond certificate.

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "vintage-style-three-stone-diamond-engagement-ring", - AllowCustomerReviews = true, - Price = 2100M, - IsShipEnabled = true, - Weight = 2, - Length = 2, - Width = 2, - Height = 2, - TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Jewelry").Id, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - DisplayStockAvailability = true, - LowStockActivity = LowStockActivity.DisableBuyButton, - BackorderMode = BackorderMode.NoBackorders, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Jewelry"), - DisplayOrder = 1, - } - } - }; - allProducts.Add(productEngagementRing); - productEngagementRing.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_EngagementRing_1.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName(productEngagementRing.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(productEngagementRing); - - - - #endregion - - #region Gift Cards - - - var product25GiftCard = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "$25 Virtual Gift Card", - Sku = "VG_CR_025", - ShortDescription = "$25 Gift Card. Gift Cards must be redeemed through our site Web site toward the purchase of eligible products.", - FullDescription = "

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

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "25-virtual-gift-card", - AllowCustomerReviews = true, - Price = 25M, - IsGiftCard = true, - GiftCardType = GiftCardType.Virtual, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - Published = true, - ShowOnHomePage = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Gift Cards"), - DisplayOrder = 2, - } - } - }; - allProducts.Add(product25GiftCard); - product25GiftCard.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_25giftcart.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(product25GiftCard.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(product25GiftCard); - - - - - - var product50GiftCard = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "$50 Physical Gift Card", - Sku = "PG_CR_050", - ShortDescription = "$50 Gift Card. Gift Cards must be redeemed through our site Web site toward the purchase of eligible products.", - FullDescription = "

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

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "50-physical-gift-card", - AllowCustomerReviews = true, - Price = 50M, - IsGiftCard = true, - GiftCardType = GiftCardType.Physical, - IsShipEnabled = true, - IsFreeShipping = true, - DeliveryDateId = deliveryDate.Id, - Weight = 1, - Length = 1, - Width = 1, - Height = 1, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - Published = true, - MarkAsNew = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Gift Cards"), - DisplayOrder = 3, - } - } - }; - allProducts.Add(product50GiftCard); - product50GiftCard.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_50giftcart.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(product50GiftCard.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(product50GiftCard); - - - - - - var product100GiftCard = new Product - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "$100 Physical Gift Card", - Sku = "PG_CR_100", - ShortDescription = "$100 Gift Card. Gift Cards must be redeemed through our site Web site toward the purchase of eligible products.", - FullDescription = "

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

    ", - ProductTemplateId = productTemplateSimple.Id, - //SeName = "100-physical-gift-card", - AllowCustomerReviews = true, - Price = 100M, - IsGiftCard = true, - GiftCardType = GiftCardType.Physical, - IsShipEnabled = true, - DeliveryDateId = deliveryDate.Id, - Weight = 1, - Length = 1, - Width = 1, - Height = 1, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - Published = true, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - ProductCategories = - { - new ProductCategory - { - Category = _categoryRepository.Table.Single(c => c.Name == "Gift Cards"), - DisplayOrder = 4, - } - } - }; - allProducts.Add(product100GiftCard); - product100GiftCard.ProductPictures.Add(new ProductPicture - { - Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_100giftcart.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(product100GiftCard.Name)), - DisplayOrder = 1, - }); - _productRepository.Insert(product100GiftCard); - - #endregion - - - - //search engine names - foreach (var product in allProducts) - { - _urlRecordRepository.Insert(new UrlRecord - { - EntityId = product.Id, - EntityName = "Product", - LanguageId = 0, - IsActive = true, - Slug = product.ValidateSeName("", product.Name, true) - }); - } - - - #region Related Products - - //related products - var relatedProducts = new List - { - new RelatedProduct - { - ProductId1 = productFlowerGirlBracelet.Id, - ProductId2 = productEngagementRing.Id, - }, - new RelatedProduct - { - ProductId1 = productFlowerGirlBracelet.Id, - ProductId2 = productElegantGemstoneNecklace.Id, - }, - new RelatedProduct - { - ProductId1 = productEngagementRing.Id, - ProductId2 = productFlowerGirlBracelet.Id, - }, - new RelatedProduct - { - ProductId1 = productEngagementRing.Id, - ProductId2 = productElegantGemstoneNecklace.Id, - }, - new RelatedProduct - { - ProductId1 = productElegantGemstoneNecklace.Id, - ProductId2 = productFlowerGirlBracelet.Id, - }, - new RelatedProduct - { - ProductId1 = productElegantGemstoneNecklace.Id, - ProductId2 = productEngagementRing.Id, - }, - new RelatedProduct - { - ProductId1 = productIfYouWait.Id, - ProductId2 = productNightVision.Id, - }, - new RelatedProduct - { - ProductId1 = productIfYouWait.Id, - ProductId2 = productScienceAndFaith.Id, - }, - new RelatedProduct - { - ProductId1 = productNightVision.Id, - ProductId2 = productIfYouWait.Id, - }, - new RelatedProduct - { - ProductId1 = productNightVision.Id, - ProductId2 = productScienceAndFaith.Id, - }, - new RelatedProduct - { - ProductId1 = productPrideAndPrejudice.Id, - ProductId2 = productFirstPrizePies.Id, - }, - new RelatedProduct - { - ProductId1 = productPrideAndPrejudice.Id, - ProductId2 = productFahrenheit.Id, - }, - new RelatedProduct - { - ProductId1 = productFirstPrizePies.Id, - ProductId2 = productPrideAndPrejudice.Id, - }, - new RelatedProduct - { - ProductId1 = productFirstPrizePies.Id, - ProductId2 = productFahrenheit.Id, - }, - new RelatedProduct - { - ProductId1 = productFahrenheit.Id, - ProductId2 = productFirstPrizePies.Id, - }, - new RelatedProduct - { - ProductId1 = productFahrenheit.Id, - ProductId2 = productPrideAndPrejudice.Id, - }, - new RelatedProduct - { - ProductId1 = productAsusN551JK.Id, - ProductId2 = productLenovoThinkpad.Id, - }, - new RelatedProduct - { - ProductId1 = productAsusN551JK.Id, - ProductId2 = productAppleMacBookPro.Id, - }, - new RelatedProduct - { - ProductId1 = productAsusN551JK.Id, - ProductId2 = productSamsungSeries.Id, - }, - new RelatedProduct - { - ProductId1 = productAsusN551JK.Id, - ProductId2 = productHpSpectre.Id, - }, - new RelatedProduct - { - ProductId1 = productLenovoThinkpad.Id, - ProductId2 = productAsusN551JK.Id, - }, - new RelatedProduct - { - ProductId1 = productLenovoThinkpad.Id, - ProductId2 = productAppleMacBookPro.Id, - }, - new RelatedProduct - { - ProductId1 = productLenovoThinkpad.Id, - ProductId2 = productSamsungSeries.Id, - }, - new RelatedProduct - { - ProductId1 = productLenovoThinkpad.Id, - ProductId2 = productHpEnvy.Id, - }, - new RelatedProduct - { - ProductId1 = productAppleMacBookPro.Id, - ProductId2 = productLenovoThinkpad.Id, - }, - new RelatedProduct - { - ProductId1 = productAppleMacBookPro.Id, - ProductId2 = productSamsungSeries.Id, - }, - new RelatedProduct - { - ProductId1 = productAppleMacBookPro.Id, - ProductId2 = productAsusN551JK.Id, - }, - new RelatedProduct - { - ProductId1 = productAppleMacBookPro.Id, - ProductId2 = productHpSpectre.Id, - }, - new RelatedProduct - { - ProductId1 = productHpSpectre.Id, - ProductId2 = productLenovoThinkpad.Id, - }, - new RelatedProduct - { - ProductId1 = productHpSpectre.Id, - ProductId2 = productSamsungSeries.Id, - }, - new RelatedProduct - { - ProductId1 = productHpSpectre.Id, - ProductId2 = productAsusN551JK.Id, - }, - new RelatedProduct - { - ProductId1 = productHpSpectre.Id, - ProductId2 = productHpEnvy.Id, - }, - new RelatedProduct - { - ProductId1 = productHpEnvy.Id, - ProductId2 = productAsusN551JK.Id, - }, - new RelatedProduct - { - ProductId1 = productHpEnvy.Id, - ProductId2 = productAppleMacBookPro.Id, - }, - new RelatedProduct - { - ProductId1 = productHpEnvy.Id, - ProductId2 = productHpSpectre.Id, - }, - new RelatedProduct - { - ProductId1 = productHpEnvy.Id, - ProductId2 = productSamsungSeries.Id, - }, - new RelatedProduct - { - ProductId1 = productSamsungSeries.Id, - ProductId2 = productAsusN551JK.Id, - }, - new RelatedProduct - { - ProductId1 = productSamsungSeries.Id, - ProductId2 = productAppleMacBookPro.Id, - }, - new RelatedProduct - { - ProductId1 = productSamsungSeries.Id, - ProductId2 = productHpEnvy.Id, - }, - new RelatedProduct - { - ProductId1 = productSamsungSeries.Id, - ProductId2 = productHpSpectre.Id, - }, - new RelatedProduct - { - ProductId1 = productLeica.Id, - ProductId2 = productHtcOneMini.Id, - }, - new RelatedProduct - { - ProductId1 = productLeica.Id, - ProductId2 = productNikonD5500DSLR.Id, - }, - new RelatedProduct - { - ProductId1 = productLeica.Id, - ProductId2 = productAppleICam.Id, - }, - new RelatedProduct - { - ProductId1 = productLeica.Id, - ProductId2 = productNokiaLumia.Id, - }, - new RelatedProduct - { - ProductId1 = productHtcOne.Id, - ProductId2 = productHtcOneMini.Id, - }, - new RelatedProduct - { - ProductId1 = productHtcOne.Id, - ProductId2 = productNokiaLumia.Id, - }, - new RelatedProduct - { - ProductId1 = productHtcOne.Id, - ProductId2 = productBeatsPill.Id, - }, - new RelatedProduct - { - ProductId1 = productHtcOne.Id, - ProductId2 = productPortableSoundSpeakers.Id, - }, - new RelatedProduct - { - ProductId1 = productHtcOneMini.Id, - ProductId2 = productHtcOne.Id, - }, - new RelatedProduct - { - ProductId1 = productHtcOneMini.Id, - ProductId2 = productNokiaLumia.Id, - }, - new RelatedProduct - { - ProductId1 = productHtcOneMini.Id, - ProductId2 = productBeatsPill.Id, - }, - new RelatedProduct - { - ProductId1 = productHtcOneMini.Id, - ProductId2 = productPortableSoundSpeakers.Id, - }, - new RelatedProduct - { - ProductId1 = productNokiaLumia.Id, - ProductId2 = productHtcOne.Id, - }, - new RelatedProduct - { - ProductId1 = productNokiaLumia.Id, - ProductId2 = productHtcOneMini.Id, - }, - new RelatedProduct - { - ProductId1 = productNokiaLumia.Id, - ProductId2 = productBeatsPill.Id, - }, - new RelatedProduct - { - ProductId1 = productNokiaLumia.Id, - ProductId2 = productPortableSoundSpeakers.Id, - }, - new RelatedProduct - { - ProductId1 = productAdidas.Id, - ProductId2 = productLeviJeans.Id, - }, - new RelatedProduct - { - ProductId1 = productAdidas.Id, - ProductId2 = productNikeFloral.Id, - }, - new RelatedProduct - { - ProductId1 = productAdidas.Id, - ProductId2 = productNikeZoom.Id, - }, - new RelatedProduct - { - ProductId1 = productAdidas.Id, - ProductId2 = productNikeTailwind.Id, - }, - new RelatedProduct - { - ProductId1 = productLeviJeans.Id, - ProductId2 = productAdidas.Id, - }, - new RelatedProduct - { - ProductId1 = productLeviJeans.Id, - ProductId2 = productNikeFloral.Id, - }, - new RelatedProduct - { - ProductId1 = productLeviJeans.Id, - ProductId2 = productNikeZoom.Id, - }, - new RelatedProduct - { - ProductId1 = productLeviJeans.Id, - ProductId2 = productNikeTailwind.Id, - }, - - new RelatedProduct - { - ProductId1 = productCustomTShirt.Id, - ProductId2 = productLeviJeans.Id, - }, - new RelatedProduct - { - ProductId1 = productCustomTShirt.Id, - ProductId2 = productNikeTailwind.Id, - }, - new RelatedProduct - { - ProductId1 = productCustomTShirt.Id, - ProductId2 = productOversizedWomenTShirt.Id, - }, - new RelatedProduct - { - ProductId1 = productCustomTShirt.Id, - ProductId2 = productObeyHat.Id, - }, - new RelatedProduct - { - ProductId1 = productDigitalStorm.Id, - ProductId2 = productBuildComputer.Id, - }, - new RelatedProduct - { - ProductId1 = productDigitalStorm.Id, - ProductId2 = productLenovoIdeaCentre.Id, - }, - new RelatedProduct - { - ProductId1 = productDigitalStorm.Id, - ProductId2 = productLenovoThinkpad.Id, - }, - new RelatedProduct - { - ProductId1 = productDigitalStorm.Id, - ProductId2 = productAppleMacBookPro.Id, - }, - - - new RelatedProduct - { - ProductId1 = productLenovoIdeaCentre.Id, - ProductId2 = productBuildComputer.Id, - }, - new RelatedProduct - { - ProductId1 = productLenovoIdeaCentre.Id, - ProductId2 = productDigitalStorm.Id, - }, - new RelatedProduct - { - ProductId1 = productLenovoIdeaCentre.Id, - ProductId2 = productLenovoThinkpad.Id, - }, - new RelatedProduct - { - ProductId1 = productLenovoIdeaCentre.Id, - ProductId2 = productAppleMacBookPro.Id, - }, - }; - _relatedProductRepository.Insert(relatedProducts); - - #endregion - - #region Product Tags - - //product tags - AddProductTag(product25GiftCard, "nice"); - AddProductTag(product25GiftCard, "gift"); - AddProductTag(productNikeTailwind, "cool"); - AddProductTag(productNikeTailwind, "apparel"); - AddProductTag(productNikeTailwind, "shirt"); - AddProductTag(productBeatsPill, "computer"); - AddProductTag(productBeatsPill, "cool"); - AddProductTag(productNikeFloral, "cool"); - AddProductTag(productNikeFloral, "shoes"); - AddProductTag(productNikeFloral, "apparel"); - AddProductTag(productAdobePhotoshop, "computer"); - AddProductTag(productAdobePhotoshop, "awesome"); - AddProductTag(productUniversalTabletCover, "computer"); - AddProductTag(productUniversalTabletCover, "cool"); - AddProductTag(productOversizedWomenTShirt, "cool"); - AddProductTag(productOversizedWomenTShirt, "apparel"); - AddProductTag(productOversizedWomenTShirt, "shirt"); - AddProductTag(productAppleMacBookPro, "compact"); - AddProductTag(productAppleMacBookPro, "awesome"); - AddProductTag(productAppleMacBookPro, "computer"); - AddProductTag(productAsusN551JK, "compact"); - AddProductTag(productAsusN551JK, "awesome"); - AddProductTag(productAsusN551JK, "computer"); - AddProductTag(productFahrenheit, "awesome"); - AddProductTag(productFahrenheit, "book"); - AddProductTag(productFahrenheit, "nice"); - AddProductTag(productHtcOne, "cell"); - AddProductTag(productHtcOne, "compact"); - AddProductTag(productHtcOne, "awesome"); - AddProductTag(productBuildComputer, "awesome"); - AddProductTag(productBuildComputer, "computer"); - AddProductTag(productNikonD5500DSLR, "cool"); - AddProductTag(productNikonD5500DSLR, "camera"); - AddProductTag(productLeica, "camera"); - AddProductTag(productLeica, "cool"); - AddProductTag(productDigitalStorm, "cool"); - AddProductTag(productDigitalStorm, "computer"); - AddProductTag(productWindows8Pro, "awesome"); - AddProductTag(productWindows8Pro, "computer"); - AddProductTag(productCustomTShirt, "cool"); - AddProductTag(productCustomTShirt, "shirt"); - AddProductTag(productCustomTShirt, "apparel"); - AddProductTag(productElegantGemstoneNecklace, "jewelry"); - AddProductTag(productElegantGemstoneNecklace, "awesome"); - AddProductTag(productFlowerGirlBracelet, "awesome"); - AddProductTag(productFlowerGirlBracelet, "jewelry"); - AddProductTag(productFirstPrizePies, "book"); - AddProductTag(productAdidas, "cool"); - AddProductTag(productAdidas, "shoes"); - AddProductTag(productAdidas, "apparel"); - AddProductTag(productLenovoIdeaCentre, "awesome"); - AddProductTag(productLenovoIdeaCentre, "computer"); - AddProductTag(productSamsungSeries, "nice"); - AddProductTag(productSamsungSeries, "computer"); - AddProductTag(productSamsungSeries, "compact"); - AddProductTag(productHpSpectre, "nice"); - AddProductTag(productHpSpectre, "computer"); - AddProductTag(productHpEnvy, "computer"); - AddProductTag(productHpEnvy, "cool"); - AddProductTag(productHpEnvy, "compact"); - AddProductTag(productObeyHat, "apparel"); - AddProductTag(productObeyHat, "cool"); - AddProductTag(productLeviJeans, "cool"); - AddProductTag(productLeviJeans, "jeans"); - AddProductTag(productLeviJeans, "apparel"); - AddProductTag(productSoundForge, "game"); - AddProductTag(productSoundForge, "computer"); - AddProductTag(productSoundForge, "cool"); - AddProductTag(productNightVision, "awesome"); - AddProductTag(productNightVision, "digital"); - AddProductTag(productSunglasses, "apparel"); - AddProductTag(productSunglasses, "cool"); - AddProductTag(productHtcOneMini, "awesome"); - AddProductTag(productHtcOneMini, "compact"); - AddProductTag(productHtcOneMini, "cell"); - AddProductTag(productIfYouWait, "digital"); - AddProductTag(productIfYouWait, "awesome"); - AddProductTag(productNokiaLumia, "awesome"); - AddProductTag(productNokiaLumia, "cool"); - AddProductTag(productNokiaLumia, "camera"); - AddProductTag(productScienceAndFaith, "digital"); - AddProductTag(productScienceAndFaith, "awesome"); - AddProductTag(productPrideAndPrejudice, "book"); - AddProductTag(productLenovoThinkpad, "awesome"); - AddProductTag(productLenovoThinkpad, "computer"); - AddProductTag(productLenovoThinkpad, "compact"); - AddProductTag(productNikeZoom, "jeans"); - AddProductTag(productNikeZoom, "cool"); - AddProductTag(productNikeZoom, "apparel"); - AddProductTag(productEngagementRing, "jewelry"); - AddProductTag(productEngagementRing, "awesome"); - - - #endregion - - #region Reviews - - //reviews - var random = new Random(); - foreach (var product in allProducts) - { - if (product.ProductType != ProductType.SimpleProduct) - continue; - - //only 3 of 4 products will have reviews - if (random.Next(4) == 3) - continue; - - //rating from 4 to 5 - var rating = random.Next(4, 6); - product.ProductReviews.Add(new ProductReview - { - CustomerId = defaultCustomer.Id, - ProductId = product.Id, - StoreId = defaultStore.Id, - IsApproved = true, - Title = "Some sample review", - ReviewText = string.Format("This sample review is for the {0}. I've been waiting for this product to be available. It is priced just right.", product.Name), - //random (4 or 5) - Rating = rating, - HelpfulYesTotal = 0, - HelpfulNoTotal = 0, - CreatedOnUtc = DateTime.UtcNow - }); - product.ApprovedRatingSum = rating; - product.ApprovedTotalReviews = product.ProductReviews.Count; - - } - _productRepository.Update(allProducts); - - #endregion - - #region Stock quantity history - - foreach (var product in allProducts) - { - if (product.StockQuantity > 0) - _stockQuantityHistoryRepository.Insert(new StockQuantityHistory - { - ProductId = product.Id, - WarehouseId = product.WarehouseId > 0 ? (int?)product.WarehouseId : null, - QuantityAdjustment = product.StockQuantity, - StockQuantity = product.StockQuantity, - Message = "The stock quantity has been edited", - CreatedOnUtc = DateTime.UtcNow - }); - } - - #endregion - } - - protected virtual void InstallForums() - { - var forumGroup = new ForumGroup - { - Name = "General", - DisplayOrder = 5, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - }; - - _forumGroupRepository.Insert(forumGroup); - - var newProductsForum = new Forum - { - ForumGroup = forumGroup, - Name = "New Products", - Description = "Discuss new products and industry trends", - NumTopics = 0, - NumPosts = 0, - LastPostCustomerId = 0, - LastPostTime = null, - DisplayOrder = 1, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - }; - _forumRepository.Insert(newProductsForum); - - var mobileDevicesForum = new Forum - { - ForumGroup = forumGroup, - Name = "Mobile Devices Forum", - Description = "Discuss the mobile phone market", - NumTopics = 0, - NumPosts = 0, - LastPostCustomerId = 0, - LastPostTime = null, - DisplayOrder = 10, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - }; - _forumRepository.Insert(mobileDevicesForum); - - var packagingShippingForum = new Forum - { - ForumGroup = forumGroup, - Name = "Packaging & Shipping", - Description = "Discuss packaging & shipping", - NumTopics = 0, - NumPosts = 0, - LastPostTime = null, - DisplayOrder = 20, - CreatedOnUtc = DateTime.UtcNow, - UpdatedOnUtc = DateTime.UtcNow, - }; - _forumRepository.Insert(packagingShippingForum); - } - - protected virtual void InstallDiscounts() - { - var discounts = new List - { - new Discount - { - Name = "Sample discount with coupon code", - DiscountType = DiscountType.AssignedToSkus, - DiscountLimitation = DiscountLimitationType.Unlimited, - UsePercentage = false, - DiscountAmount = 10, - RequiresCouponCode = true, - CouponCode = "123", - }, - new Discount - { - Name = "'20% order total' discount", - DiscountType = DiscountType.AssignedToOrderTotal, - DiscountLimitation = DiscountLimitationType.Unlimited, - UsePercentage = true, - DiscountPercentage = 20, - StartDateUtc = new DateTime(2010,1,1), - EndDateUtc = new DateTime(2020,1,1), - RequiresCouponCode = true, - CouponCode = "456", - }, - }; - _discountRepository.Insert(discounts); - } - - protected virtual void InstallBlogPosts(string defaultUserEmail) - { - var defaultLanguage = _languageRepository.Table.FirstOrDefault(); - - var blogPosts = new List - { - new BlogPost - { - AllowComments = true, - Language = defaultLanguage, - Title = "How a blog can help your growing e-Commerce business", - BodyOverview = "

    When you start an online business, your main aim is to sell the products, right? As a business owner, you want to showcase your store to more audience. So, you decide to go on social media, why? Because everyone is doing it, then why shouldn’t you? It is tempting as everyone is aware of the hype that it is the best way to market your brand.

    Do you know having a blog for your online store can be very helpful? Many businesses do not understand the importance of having a blog because they don’t have time to post quality content.

    Today, we will talk about how a blog can play an important role for the growth of your e-Commerce business. Later, we will also discuss some tips that will be helpful to you for writing business related blog posts.

    ", - Body = "

    When you start an online business, your main aim is to sell the products, right? As a business owner, you want to showcase your store to more audience. So, you decide to go on social media, why? Because everyone is doing it, then why shouldn’t you? It is tempting as everyone is aware of the hype that it is the best way to market your brand.

    Do you know having a blog for your online store can be very helpful? Many businesses do not understand the importance of having a blog because they don’t have time to post quality content.

    Today, we will talk about how a blog can play an important role for the growth of your e-Commerce business. Later, we will also discuss some tips that will be helpful to you for writing business related blog posts.

    1) Blog is useful in educating your customers

    Blogging is one of the best way by which you can educate your customers about your products/services that you offer. This helps you as a business owner to bring more value to your brand. When you provide useful information to the customers about your products, they are more likely to buy products from you. You can use your blog for providing tutorials in regard to the use of your products.

    For example: If you have an online store that offers computer parts. You can write tutorials about how to build a computer or how to make your computer’s performance better. While talking about these things, you can mention products in the tutorials and provide link to your products within the blog post from your website. Your potential customers might get different ideas of using your product and will likely to buy products from your online store.

    2) Blog helps your business in Search Engine Optimization (SEO)

    Blog posts create more internal links to your website which helps a lot in SEO. Blog is a great way to have quality content on your website related to your products/services which is indexed by all major search engines like Google, Bing and Yahoo. The more original content you write in your blog post, the better ranking you will get in search engines. SEO is an on-going process and posting blog posts regularly keeps your site active all the time which is beneficial when it comes to search engine optimization.

    For example: Let’s say you sell “Sony Television Model XYZ” and you regularly publish blog posts about your product. Now, whenever someone searches for “Sony Television Model XYZ”, Google will crawl on your website knowing that you have something to do with this particular product. Hence, your website will show up on the search result page whenever this item is being searched.

    3) Blog helps in boosting your sales by convincing the potential customers to buy

    If you own an online business, there are so many ways you can share different stories with your audience in regard your products/services that you offer. Talk about how you started your business, share stories that educate your audience about what’s new in your industry, share stories about how your product/service was beneficial to someone or share anything that you think your audience might find interesting (it does not have to be related to your product). This kind of blogging shows that you are an expert in your industry and interested in educating your audience. It sets you apart in the competitive market. This gives you an opportunity to showcase your expertise by educating the visitors and it can turn your audience into buyers.

    Fun Fact: Did you know that 92% of companies who decided to blog acquired customers through their blog?

    nopCommerce is great e-Commerce solution that also offers a variety of CMS features including blog. A store owner has full access for managing the blog posts and related comments.

    ", - Tags = "e-commerce, blog, moey", - CreatedOnUtc = DateTime.UtcNow, - }, - new BlogPost - { - AllowComments = true, - Language = defaultLanguage, - Title = "Why your online store needs a wish list", - BodyOverview = "

    What comes to your mind, when you hear the term” wish list”? The application of this feature is exactly how it sounds like: a list of things that you wish to get. As an online store owner, would you like your customers to be able to save products in a wish list so that they review or buy them later? Would you like your customers to be able to share their wish list with friends and family for gift giving?

    Offering your customers a feature of wish list as part of shopping cart is a great way to build loyalty to your store site. Having the feature of wish list on a store site allows online businesses to engage with their customers in a smart way as it allows the shoppers to create a list of what they desire and their preferences for future purchase.

    ", - Body = "

    What comes to your mind, when you hear the term” wish list”? The application of this feature is exactly how it sounds like: a list of things that you wish to get. As an online store owner, would you like your customers to be able to save products in a wish list so that they review or buy them later? Would you like your customers to be able to share their wish list with friends and family for gift giving?

    Offering your customers a feature of wish list as part of shopping cart is a great way to build loyalty to your store site. Having the feature of wish list on a store site allows online businesses to engage with their customers in a smart way as it allows the shoppers to create a list of what they desire and their preferences for future purchase.

    Does every e-Commerce store needs a wish list? The answer to this question in most cases is yes, because of the following reasons:

    Understanding the needs of your customers - A wish list is a great way to know what is in your customer’s mind. Try to think the purchase history as a small portion of the customer’s preferences. But, the wish list is like a wide open door that can give any online business a lot of valuable information about their customer and what they like or desire.

    Shoppers like to share their wish list with friends and family - Providing your customers a way to email their wish list to their friends and family is a pleasant way to make online shopping enjoyable for the shoppers. It is always a good idea to make the wish list sharable by a unique link so that it can be easily shared though different channels like email or on social media sites.

    Wish list can be a great marketing tool – Another way to look at wish list is a great marketing tool because it is extremely targeted and the recipients are always motivated to use it. For example: when your younger brother tells you that his wish list is on a certain e-Commerce store. What is the first thing you are going to do? You are most likely to visit the e-Commerce store, check out the wish list and end up buying something for your younger brother.

    So, how a wish list is a marketing tool? The reason is quite simple, it introduce your online store to new customers just how it is explained in the above example.

    Encourage customers to return to the store site – Having a feature of wish list on the store site can increase the return traffic because it encourages customers to come back and buy later. Allowing the customers to save the wish list to their online accounts gives them a reason return to the store site and login to the account at any time to view or edit the wish list items.

    Wish list can be used for gifts for different occasions like weddings or birthdays. So, what kind of benefits a gift-giver gets from a wish list?

    • It gives them a surety that they didn’t buy a wrong gift
    • It guarantees that the recipient will like the gift
    • It avoids any awkward moments when the recipient unwraps the gift and as a gift-giver you got something that the recipient do not want

    Wish list is a great feature to have on a store site – So, what kind of benefits a business owner gets from a wish list

    • It is a great way to advertise an online store as many people do prefer to shop where their friend or family shop online
    • It allows the current customers to return to the store site and open doors for the new customers
    • It allows store admins to track what’s in customers wish list and run promotions accordingly to target specific customer segments

    nopCommerce offers the feature of wish list that allows customers to create a list of products that they desire or planning to buy in future.

    ", - Tags = "e-commerce, nopCommerce, sample tag, money", - CreatedOnUtc = DateTime.UtcNow.AddSeconds(1), - }, - }; - _blogPostRepository.Insert(blogPosts); - - //search engine names - foreach (var blogPost in blogPosts) - { - _urlRecordRepository.Insert(new UrlRecord - { - EntityId = blogPost.Id, - EntityName = "BlogPost", - LanguageId = blogPost.LanguageId, - IsActive = true, - Slug = blogPost.ValidateSeName("", blogPost.Title, true) - }); - } - - //comments - var defaultCustomer = _customerRepository.Table.FirstOrDefault(x => x.Email == defaultUserEmail); - if (defaultCustomer == null) - throw new Exception("Cannot load default customer"); - - //default store - var defaultStore = _storeRepository.Table.FirstOrDefault(); - if (defaultStore == null) - throw new Exception("No default store could be loaded"); - - foreach (var blogPost in blogPosts) - { - blogPost.BlogComments.Add(new BlogComment - { - BlogPostId = blogPost.Id, - CustomerId = defaultCustomer.Id, - CommentText = "This is a sample comment for this blog post", - IsApproved = true, - StoreId = defaultStore.Id, - CreatedOnUtc = DateTime.UtcNow - }); - } - _blogPostRepository.Update(blogPosts); - } - - protected virtual void InstallNews(string defaultUserEmail) - { - var defaultLanguage = _languageRepository.Table.FirstOrDefault(); - - var news = new List - { - new NewsItem - { - AllowComments = true, - Language = defaultLanguage, - Title = "About nopCommerce", - Short = "It's stable and highly usable. From downloads to documentation, www.nopCommerce.com offers a comprehensive base of information, resources, and support to the nopCommerce community.", - Full = "

    For full feature list go to nopCommerce.com

    Providing outstanding custom search engine optimization, web development services and e-commerce development solutions to our clients at a fair price in a professional manner.

    ", - Published = true, - CreatedOnUtc = DateTime.UtcNow, - }, - new NewsItem - { - AllowComments = true, - Language = defaultLanguage, - Title = "nopCommerce new release!", - Short = "nopCommerce includes everything you need to begin your e-commerce online store. We have thought of everything and it's all included! nopCommerce is a fully customizable shopping cart", - Full = "

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

    ", - Published = true, - CreatedOnUtc = DateTime.UtcNow.AddSeconds(1), - }, - new NewsItem - { - AllowComments = true, - Language = defaultLanguage, - Title = "New online store is open!", - Short = "The new nopCommerce store is open now! We are very excited to offer our new range of products. We will be constantly adding to our range so please register on our site.", - Full = "

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

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

    ", - Published = true, - CreatedOnUtc = DateTime.UtcNow.AddSeconds(2), - }, - - }; - _newsItemRepository.Insert(news); - - //search engine names - foreach (var newsItem in news) - { - _urlRecordRepository.Insert(new UrlRecord - { - EntityId = newsItem.Id, - EntityName = "NewsItem", - LanguageId = newsItem.LanguageId, - IsActive = true, - Slug = newsItem.ValidateSeName("", newsItem.Title, true) - }); - } - - //comments - var defaultCustomer = _customerRepository.Table.FirstOrDefault(x => x.Email == defaultUserEmail); - if (defaultCustomer == null) - throw new Exception("Cannot load default customer"); - - //default store - var defaultStore = _storeRepository.Table.FirstOrDefault(); - if (defaultStore == null) - throw new Exception("No default store could be loaded"); - - foreach (var newsItem in news) - { - newsItem.NewsComments.Add(new NewsComment - { - NewsItemId = newsItem.Id, - CustomerId = defaultCustomer.Id, - CommentTitle = "Sample comment title", - CommentText = "This is a sample comment...", - IsApproved = true, - StoreId = defaultStore.Id, - CreatedOnUtc = DateTime.UtcNow - }); - } - _newsItemRepository.Update(news); - } - - protected virtual void InstallPolls() - { - var defaultLanguage = _languageRepository.Table.FirstOrDefault(); - var poll1 = new Poll - { - Language = defaultLanguage, - Name = "Do you like nopCommerce?", - SystemKeyword = "", - Published = true, - ShowOnHomePage = true, - DisplayOrder = 1, - }; - poll1.PollAnswers.Add(new PollAnswer - { - Name = "Excellent", - DisplayOrder = 1, - }); - poll1.PollAnswers.Add(new PollAnswer - { - Name = "Good", - DisplayOrder = 2, - }); - poll1.PollAnswers.Add(new PollAnswer - { - Name = "Poor", - DisplayOrder = 3, - }); - poll1.PollAnswers.Add(new PollAnswer - { - Name = "Very bad", - DisplayOrder = 4, - }); - _pollRepository.Insert(poll1); - } - - protected virtual void InstallActivityLogTypes() - { - var activityLogTypes = new List - { - //admin area activities - new ActivityLogType - { - SystemKeyword = "AddNewAddressAttribute", - Enabled = true, - Name = "Add a new address attribute" - }, - new ActivityLogType - { - SystemKeyword = "AddNewAddressAttributeValue", - Enabled = true, - Name = "Add a new address attribute value" - }, - new ActivityLogType - { - SystemKeyword = "AddNewAffiliate", - Enabled = true, - Name = "Add a new affiliate" - }, - new ActivityLogType - { - SystemKeyword = "AddNewBlogPost", - Enabled = true, - Name = "Add a new blog post" - }, - new ActivityLogType - { - SystemKeyword = "AddNewCampaign", - Enabled = true, - Name = "Add a new campaign" - }, - new ActivityLogType - { - SystemKeyword = "AddNewCategory", - Enabled = true, - Name = "Add a new category" - }, - new ActivityLogType - { - SystemKeyword = "AddNewCheckoutAttribute", - Enabled = true, - Name = "Add a new checkout attribute" - }, - new ActivityLogType - { - SystemKeyword = "AddNewCountry", - Enabled = true, - Name = "Add a new country" - }, - new ActivityLogType - { - SystemKeyword = "AddNewCurrency", - Enabled = true, - Name = "Add a new currency" - }, - new ActivityLogType - { - SystemKeyword = "AddNewCustomer", - Enabled = true, - Name = "Add a new customer" - }, - new ActivityLogType - { - SystemKeyword = "AddNewCustomerAttribute", - Enabled = true, - Name = "Add a new customer attribute" - }, - new ActivityLogType - { - SystemKeyword = "AddNewCustomerAttributeValue", - Enabled = true, - Name = "Add a new customer attribute value" - }, - new ActivityLogType - { - SystemKeyword = "AddNewCustomerRole", - Enabled = true, - Name = "Add a new customer role" - }, - new ActivityLogType - { - SystemKeyword = "AddCustomerRewardPoints", - Enabled = true, - Name = "Add customer reward points" - }, - new ActivityLogType - { - SystemKeyword = "AddNewDiscount", - Enabled = true, - Name = "Add a new discount" - }, - new ActivityLogType - { - SystemKeyword = "AddNewEmailAccount", - Enabled = true, - Name = "Add a new email account" - }, - new ActivityLogType - { - SystemKeyword = "AddNewGiftCard", - Enabled = true, - Name = "Add a new gift card" - }, - new ActivityLogType - { - SystemKeyword = "AddNewLanguage", - Enabled = true, - Name = "Add a new language" - }, - new ActivityLogType - { - SystemKeyword = "AddNewManufacturer", - Enabled = true, - Name = "Add a new manufacturer" - }, - new ActivityLogType - { - SystemKeyword = "AddNewMeasureDimension", - Enabled = true, - Name = "Add a new measure dimension" - }, - new ActivityLogType - { - SystemKeyword = "AddNewMeasureWeight", - Enabled = true, - Name = "Add a new measure weight" - }, - new ActivityLogType - { - SystemKeyword = "AddNewNews", - Enabled = true, - Name = "Add a new news" - }, - new ActivityLogType - { - SystemKeyword = "AddNewProduct", - Enabled = true, - Name = "Add a new product" - }, - new ActivityLogType - { - SystemKeyword = "AddNewProductAttribute", - Enabled = true, - Name = "Add a new product attribute" - }, - new ActivityLogType - { - SystemKeyword = "AddNewSetting", - Enabled = true, - Name = "Add a new setting" - }, - new ActivityLogType - { - SystemKeyword = "AddNewSpecAttribute", - Enabled = true, - Name = "Add a new specification attribute" - }, - new ActivityLogType - { - SystemKeyword = "AddNewStateProvince", - Enabled = true, - Name = "Add a new state or province" - }, - new ActivityLogType - { - SystemKeyword = "AddNewStore", - Enabled = true, - Name = "Add a new store" - }, - new ActivityLogType - { - SystemKeyword = "AddNewTopic", - Enabled = true, - Name = "Add a new topic" - }, - new ActivityLogType - { - SystemKeyword = "AddNewVendor", - Enabled = true, - Name = "Add a new vendor" - }, - new ActivityLogType - { - SystemKeyword = "AddNewWarehouse", - Enabled = true, - Name = "Add a new warehouse" - }, - new ActivityLogType - { - SystemKeyword = "AddNewWidget", - Enabled = true, - Name = "Add a new widget" - }, - new ActivityLogType - { - SystemKeyword = "DeleteActivityLog", - Enabled = true, - Name = "Delete activity log" - }, - new ActivityLogType - { - SystemKeyword = "DeleteAddressAttribute", - Enabled = true, - Name = "Delete an address attribute" - }, - new ActivityLogType - { - SystemKeyword = "DeleteAddressAttributeValue", - Enabled = true, - Name = "Delete an address attribute value" - }, - new ActivityLogType - { - SystemKeyword = "DeleteAffiliate", - Enabled = true, - Name = "Delete an affiliate" - }, - new ActivityLogType - { - SystemKeyword = "DeleteBlogPost", - Enabled = true, - Name = "Delete a blog post" - }, - new ActivityLogType - { - SystemKeyword = "DeleteBlogPostComment", - Enabled = true, - Name = "Delete a blog post comment" - }, - new ActivityLogType - { - SystemKeyword = "DeleteCampaign", - Enabled = true, - Name = "Delete a campaign" - }, - new ActivityLogType - { - SystemKeyword = "DeleteCategory", - Enabled = true, - Name = "Delete category" - }, - new ActivityLogType - { - SystemKeyword = "DeleteCheckoutAttribute", - Enabled = true, - Name = "Delete a checkout attribute" - }, - new ActivityLogType - { - SystemKeyword = "DeleteCountry", - Enabled = true, - Name = "Delete a country" - }, - new ActivityLogType - { - SystemKeyword = "DeleteCurrency", - Enabled = true, - Name = "Delete a currency" - }, - new ActivityLogType - { - SystemKeyword = "DeleteCustomer", - Enabled = true, - Name = "Delete a customer" - }, - new ActivityLogType - { - SystemKeyword = "DeleteCustomerAttribute", - Enabled = true, - Name = "Delete a customer attribute" - }, - new ActivityLogType - { - SystemKeyword = "DeleteCustomerAttributeValue", - Enabled = true, - Name = "Delete a customer attribute value" - }, - new ActivityLogType - { - SystemKeyword = "DeleteCustomerRole", - Enabled = true, - Name = "Delete a customer role" - }, - new ActivityLogType - { - SystemKeyword = "DeleteDiscount", - Enabled = true, - Name = "Delete a discount" - }, - new ActivityLogType - { - SystemKeyword = "DeleteEmailAccount", - Enabled = true, - Name = "Delete an email account" - }, - new ActivityLogType - { - SystemKeyword = "DeleteGiftCard", - Enabled = true, - Name = "Delete a gift card" - }, - new ActivityLogType - { - SystemKeyword = "DeleteLanguage", - Enabled = true, - Name = "Delete a language" - }, - new ActivityLogType - { - SystemKeyword = "DeleteManufacturer", - Enabled = true, - Name = "Delete a manufacturer" - }, - new ActivityLogType - { - SystemKeyword = "DeleteMeasureDimension", - Enabled = true, - Name = "Delete a measure dimension" - }, - new ActivityLogType - { - SystemKeyword = "DeleteMeasureWeight", - Enabled = true, - Name = "Delete a measure weight" - }, - new ActivityLogType - { - SystemKeyword = "DeleteMessageTemplate", - Enabled = true, - Name = "Delete a message template" - }, - new ActivityLogType - { - SystemKeyword = "DeleteNews", - Enabled = true, - Name = "Delete a news" - }, - new ActivityLogType - { - SystemKeyword = "DeleteNewsComment", - Enabled = true, - Name = "Delete a news comment" - }, - new ActivityLogType - { - SystemKeyword = "DeleteOrder", - Enabled = true, - Name = "Delete an order" - }, - new ActivityLogType - { - SystemKeyword = "DeleteProduct", - Enabled = true, - Name = "Delete a product" - }, - new ActivityLogType - { - SystemKeyword = "DeleteProductAttribute", - Enabled = true, - Name = "Delete a product attribute" - }, - new ActivityLogType - { - SystemKeyword = "DeleteProductReview", - Enabled = true, - Name = "Delete a product review" - }, - new ActivityLogType - { - SystemKeyword = "DeleteReturnRequest", - Enabled = true, - Name = "Delete a return request" - }, - new ActivityLogType - { - SystemKeyword = "DeleteSetting", - Enabled = true, - Name = "Delete a setting" - }, - new ActivityLogType - { - SystemKeyword = "DeleteSpecAttribute", - Enabled = true, - Name = "Delete a specification attribute" - }, - new ActivityLogType - { - SystemKeyword = "DeleteStateProvince", - Enabled = true, - Name = "Delete a state or province" - }, - new ActivityLogType - { - SystemKeyword = "DeleteStore", - Enabled = true, - Name = "Delete a store" - }, - new ActivityLogType - { - SystemKeyword = "DeleteTopic", - Enabled = true, - Name = "Delete a topic" - }, - new ActivityLogType - { - SystemKeyword = "DeleteVendor", - Enabled = true, - Name = "Delete a vendor" - }, - new ActivityLogType - { - SystemKeyword = "DeleteWarehouse", - Enabled = true, - Name = "Delete a warehouse" - }, - new ActivityLogType - { - SystemKeyword = "DeleteWidget", - Enabled = true, - Name = "Delete a widget" - }, - new ActivityLogType - { - SystemKeyword = "EditActivityLogTypes", - Enabled = true, - Name = "Edit activity log types" - }, - new ActivityLogType - { - SystemKeyword = "EditAddressAttribute", - Enabled = true, - Name = "Edit an address attribute" - }, - new ActivityLogType - { - SystemKeyword = "EditAddressAttributeValue", - Enabled = true, - Name = "Edit an address attribute value" - }, - new ActivityLogType - { - SystemKeyword = "EditAffiliate", - Enabled = true, - Name = "Edit an affiliate" - }, - new ActivityLogType - { - SystemKeyword = "EditBlogPost", - Enabled = true, - Name = "Edit a blog post" - }, - new ActivityLogType - { - SystemKeyword = "EditCampaign", - Enabled = true, - Name = "Edit a campaign" - }, - new ActivityLogType - { - SystemKeyword = "EditCategory", - Enabled = true, - Name = "Edit category" - }, - new ActivityLogType - { - SystemKeyword = "EditCheckoutAttribute", - Enabled = true, - Name = "Edit a checkout attribute" - }, - new ActivityLogType - { - SystemKeyword = "EditCountry", - Enabled = true, - Name = "Edit a country" - }, - new ActivityLogType - { - SystemKeyword = "EditCurrency", - Enabled = true, - Name = "Edit a currency" - }, - new ActivityLogType - { - SystemKeyword = "EditCustomer", - Enabled = true, - Name = "Edit a customer" - }, - new ActivityLogType - { - SystemKeyword = "EditCustomerAttribute", - Enabled = true, - Name = "Edit a customer attribute" - }, - new ActivityLogType - { - SystemKeyword = "EditCustomerAttributeValue", - Enabled = true, - Name = "Edit a customer attribute value" - }, - new ActivityLogType - { - SystemKeyword = "EditCustomerRole", - Enabled = true, - Name = "Edit a customer role" - }, - new ActivityLogType - { - SystemKeyword = "EditCustomerRewardPoints", - Enabled = true, - Name = "Edit customer reward points" - }, - new ActivityLogType - { - SystemKeyword = "EditDiscount", - Enabled = true, - Name = "Edit a discount" - }, - new ActivityLogType - { - SystemKeyword = "EditEmailAccount", - Enabled = true, - Name = "Edit an email account" - }, - new ActivityLogType - { - SystemKeyword = "EditGiftCard", - Enabled = true, - Name = "Edit a gift card" - }, - new ActivityLogType - { - SystemKeyword = "EditLanguage", - Enabled = true, - Name = "Edit a language" - }, - new ActivityLogType - { - SystemKeyword = "EditManufacturer", - Enabled = true, - Name = "Edit a manufacturer" - }, - new ActivityLogType - { - SystemKeyword = "EditMeasureDimension", - Enabled = true, - Name = "Edit a measure dimension" - }, - new ActivityLogType - { - SystemKeyword = "EditMeasureWeight", - Enabled = true, - Name = "Edit a measure weight" - }, - new ActivityLogType - { - SystemKeyword = "EditMessageTemplate", - Enabled = true, - Name = "Edit a message template" - }, - new ActivityLogType - { - SystemKeyword = "EditNews", - Enabled = true, - Name = "Edit a news" - }, - new ActivityLogType - { - SystemKeyword = "EditOrder", - Enabled = true, - Name = "Edit an order" - }, - new ActivityLogType - { - SystemKeyword = "EditPlugin", - Enabled = true, - Name = "Edit a plugin" - }, - new ActivityLogType - { - SystemKeyword = "EditProduct", - Enabled = true, - Name = "Edit a product" - }, - new ActivityLogType - { - SystemKeyword = "EditProductAttribute", - Enabled = true, - Name = "Edit a product attribute" - }, - new ActivityLogType - { - SystemKeyword = "EditProductReview", - Enabled = true, - Name = "Edit a product review" - }, - new ActivityLogType - { - SystemKeyword = "EditPromotionProviders", - Enabled = true, - Name = "Edit promotion providers" - }, - new ActivityLogType - { - SystemKeyword = "EditReturnRequest", - Enabled = true, - Name = "Edit a return request" - }, - new ActivityLogType - { - SystemKeyword = "EditSettings", - Enabled = true, - Name = "Edit setting(s)" - }, - new ActivityLogType - { - SystemKeyword = "EditStateProvince", - Enabled = true, - Name = "Edit a state or province" - }, - new ActivityLogType - { - SystemKeyword = "EditStore", - Enabled = true, - Name = "Edit a store" - }, - new ActivityLogType - { - SystemKeyword = "EditTask", - Enabled = true, - Name = "Edit a task" - }, - new ActivityLogType - { - SystemKeyword = "EditSpecAttribute", - Enabled = true, - Name = "Edit a specification attribute" - }, - new ActivityLogType - { - SystemKeyword = "EditVendor", - Enabled = true, - Name = "Edit a vendor" - }, - new ActivityLogType - { - SystemKeyword = "EditWarehouse", - Enabled = true, - Name = "Edit a warehouse" - }, - new ActivityLogType - { - SystemKeyword = "EditTopic", - Enabled = true, - Name = "Edit a topic" - }, - new ActivityLogType - { - SystemKeyword = "EditWidget", - Enabled = true, - Name = "Edit a widget" - }, - new ActivityLogType - { - SystemKeyword = "Impersonation.Started", - Enabled = true, - Name = "Customer impersonation session. Started" - }, - new ActivityLogType - { - SystemKeyword = "Impersonation.Finished", - Enabled = true, - Name = "Customer impersonation session. Finished" - }, - new ActivityLogType - { - SystemKeyword = "ImportCategories", - Enabled = true, - Name = "Categories were imported" - }, - new ActivityLogType - { - SystemKeyword = "ImportManufacturers", - Enabled = true, - Name = "Manufacturers were imported" - }, - new ActivityLogType - { - SystemKeyword = "ImportProducts", - Enabled = true, - Name = "Products were imported" - }, - new ActivityLogType - { - SystemKeyword = "ImportStates", - Enabled = true, - Name = "States were imported" - }, - new ActivityLogType - { - SystemKeyword = "InstallNewPlugin", - Enabled = true, - Name = "Install a new plugin" - }, - new ActivityLogType - { - SystemKeyword = "UninstallPlugin", - Enabled = true, - Name = "Uninstall a plugin" - }, - //public store activities - new ActivityLogType - { - SystemKeyword = "PublicStore.ViewCategory", - Enabled = false, - Name = "Public store. View a category" - }, - new ActivityLogType - { - SystemKeyword = "PublicStore.ViewManufacturer", - Enabled = false, - Name = "Public store. View a manufacturer" - }, - new ActivityLogType - { - SystemKeyword = "PublicStore.ViewProduct", - Enabled = false, - Name = "Public store. View a product" - }, - new ActivityLogType - { - SystemKeyword = "PublicStore.PlaceOrder", - Enabled = false, - Name = "Public store. Place an order" - }, - new ActivityLogType - { - SystemKeyword = "PublicStore.SendPM", - Enabled = false, - Name = "Public store. Send PM" - }, - new ActivityLogType - { - SystemKeyword = "PublicStore.ContactUs", - Enabled = false, - Name = "Public store. Use contact us form" - }, - new ActivityLogType - { - SystemKeyword = "PublicStore.AddToCompareList", - Enabled = false, - Name = "Public store. Add to compare list" - }, - new ActivityLogType - { - SystemKeyword = "PublicStore.AddToShoppingCart", - Enabled = false, - Name = "Public store. Add to shopping cart" - }, - new ActivityLogType - { - SystemKeyword = "PublicStore.AddToWishlist", - Enabled = false, - Name = "Public store. Add to wishlist" - }, - new ActivityLogType - { - SystemKeyword = "PublicStore.Login", - Enabled = false, - Name = "Public store. Login" - }, - new ActivityLogType - { - SystemKeyword = "PublicStore.Logout", - Enabled = false, - Name = "Public store. Logout" - }, - new ActivityLogType - { - SystemKeyword = "PublicStore.AddProductReview", - Enabled = false, - Name = "Public store. Add product review" - }, - new ActivityLogType - { - SystemKeyword = "PublicStore.AddNewsComment", - Enabled = false, - Name = "Public store. Add news comment" - }, - new ActivityLogType - { - SystemKeyword = "PublicStore.AddBlogComment", - Enabled = false, - Name = "Public store. Add blog comment" - }, - new ActivityLogType - { - SystemKeyword = "PublicStore.AddForumTopic", - Enabled = false, - Name = "Public store. Add forum topic" - }, - new ActivityLogType - { - SystemKeyword = "PublicStore.EditForumTopic", - Enabled = false, - Name = "Public store. Edit forum topic" - }, - new ActivityLogType - { - SystemKeyword = "PublicStore.DeleteForumTopic", - Enabled = false, - Name = "Public store. Delete forum topic" - }, - new ActivityLogType - { - SystemKeyword = "PublicStore.AddForumPost", - Enabled = false, - Name = "Public store. Add forum post" - }, - new ActivityLogType - { - SystemKeyword = "PublicStore.EditForumPost", - Enabled = false, - Name = "Public store. Edit forum post" - }, - new ActivityLogType - { - SystemKeyword = "PublicStore.DeleteForumPost", - Enabled = false, - Name = "Public store. Delete forum post" - } - }; - _activityLogTypeRepository.Insert(activityLogTypes); - } - - protected virtual void InstallProductTemplates() - { - var productTemplates = new List - { - new ProductTemplate - { - Name = "Simple product", - ViewPath = "ProductTemplate.Simple", - DisplayOrder = 10, - IgnoredProductTypes = ((int)ProductType.GroupedProduct).ToString() - }, - new ProductTemplate - { - Name = "Grouped product (with variants)", - ViewPath = "ProductTemplate.Grouped", - DisplayOrder = 100, - IgnoredProductTypes = ((int)ProductType.SimpleProduct).ToString() - } - }; - _productTemplateRepository.Insert(productTemplates); - } - - protected virtual void InstallCategoryTemplates() - { - var categoryTemplates = new List - { - new CategoryTemplate - { - Name = "Products in Grid or Lines", - ViewPath = "CategoryTemplate.ProductsInGridOrLines", - DisplayOrder = 1 - }, - }; - _categoryTemplateRepository.Insert(categoryTemplates); - } - - protected virtual void InstallManufacturerTemplates() - { - var manufacturerTemplates = new List - { - new ManufacturerTemplate - { - Name = "Products in Grid or Lines", - ViewPath = "ManufacturerTemplate.ProductsInGridOrLines", - DisplayOrder = 1 - }, - }; - _manufacturerTemplateRepository.Insert(manufacturerTemplates); - } - - protected virtual void InstallTopicTemplates() - { - var topicTemplates = new List - { - new TopicTemplate - { - Name = "Default template", - ViewPath = "TopicDetails", - DisplayOrder = 1 - }, - }; - _topicTemplateRepository.Insert(topicTemplates); - } - - protected virtual void InstallScheduleTasks() - { - var tasks = new List - { - new ScheduleTask - { - Name = "Send emails", - Seconds = 60, - Type = "Nop.Services.Messages.QueuedMessagesSendTask, Nop.Services", - Enabled = true, - StopOnError = false, - }, - new ScheduleTask - { - Name = "Keep alive", - Seconds = 300, - Type = "Nop.Services.Common.KeepAliveTask, Nop.Services", - Enabled = true, - StopOnError = false, - }, - new ScheduleTask - { - Name = "Delete guests", - Seconds = 600, - Type = "Nop.Services.Customers.DeleteGuestsTask, Nop.Services", - Enabled = true, - StopOnError = false, - }, - new ScheduleTask - { - Name = "Clear cache", - Seconds = 600, - Type = "Nop.Services.Caching.ClearCacheTask, Nop.Services", - Enabled = false, - StopOnError = false, - }, - new ScheduleTask - { - Name = "Clear log", - //60 minutes - Seconds = 3600, - Type = "Nop.Services.Logging.ClearLogTask, Nop.Services", - Enabled = false, - StopOnError = false, - }, - new ScheduleTask - { - Name = "Update currency exchange rates", - //60 minutes - Seconds = 3600, - Type = "Nop.Services.Directory.UpdateExchangeRateTask, Nop.Services", - Enabled = true, - StopOnError = false, - }, - }; - - _scheduleTaskRepository.Insert(tasks); - } - - protected virtual void InstallReturnRequestReasons() - { - var returnRequestReasons = new List - { - new ReturnRequestReason - { - Name = "Received Wrong Product", - DisplayOrder = 1 - }, - new ReturnRequestReason - { - Name = "Wrong Product Ordered", - DisplayOrder = 2 - }, - new ReturnRequestReason - { - Name = "There Was A Problem With The Product", - DisplayOrder = 3 - } - }; - _returnRequestReasonRepository.Insert(returnRequestReasons); - } - protected virtual void InstallReturnRequestActions() - { - var returnRequestActions = new List - { - new ReturnRequestAction - { - Name = "Repair", - DisplayOrder = 1 - }, - new ReturnRequestAction - { - Name = "Replacement", - DisplayOrder = 2 - }, - new ReturnRequestAction - { - Name = "Store Credit", - DisplayOrder = 3 - } - }; - _returnRequestActionRepository.Insert(returnRequestActions); - } - - protected virtual void InstallWarehouses() - { - var warehouse1address = new Address - { - Address1 = "21 West 52nd Street", - City = "New York", - StateProvince = _stateProvinceRepository.Table.FirstOrDefault(sp => sp.Name == "New York"), - Country = _countryRepository.Table.FirstOrDefault(c => c.ThreeLetterIsoCode == "USA"), - ZipPostalCode = "10021", - CreatedOnUtc = DateTime.UtcNow, - }; - _addressRepository.Insert(warehouse1address); - var warehouse2address = new Address - { - Address1 = "300 South Spring Stree", - City = "Los Angeles", - StateProvince = _stateProvinceRepository.Table.FirstOrDefault(sp => sp.Name == "California"), - Country = _countryRepository.Table.FirstOrDefault(c => c.ThreeLetterIsoCode == "USA"), - ZipPostalCode = "90013", - CreatedOnUtc = DateTime.UtcNow, - }; - _addressRepository.Insert(warehouse2address); - var warehouses = new List - { - new Warehouse - { - Name = "Warehouse 1 (New York)", - AddressId = warehouse1address.Id - }, - new Warehouse - { - Name = "Warehouse 2 (Los Angeles)", - AddressId = warehouse2address.Id - } - }; - - _warehouseRepository.Insert(warehouses); - } - - protected virtual void InstallVendors() - { - var vendors = new List - { - new Vendor - { - Name = "Vendor 1", - Email = "vendor1email@gmail.com", - Description = "Some description...", - AdminComment = "", - PictureId = 0, - Active = true, - DisplayOrder = 1, - PageSize = 6, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "6, 3, 9, 18", - }, - new Vendor - { - Name = "Vendor 2", - Email = "vendor2email@gmail.com", - Description = "Some description...", - AdminComment = "", - PictureId = 0, - Active = true, - DisplayOrder = 2, - PageSize = 6, - AllowCustomersToSelectPageSize = true, - PageSizeOptions = "6, 3, 9, 18", - } - }; - - _vendorRepository.Insert(vendors); - - //search engine names - foreach (var vendor in vendors) - { - _urlRecordRepository.Insert(new UrlRecord - { - EntityId = vendor.Id, - EntityName = "Vendor", - LanguageId = 0, - IsActive = true, - Slug = vendor.ValidateSeName("", vendor.Name, true) - }); - } - } - - protected virtual void InstallAffiliates() - { - var affiliateAddress = new Address - { - FirstName = "John", - LastName = "Smith", - Email = "affiliate_email@gmail.com", - Company = "Company name here...", - City = "New York", - Address1 = "21 West 52nd Street", - ZipPostalCode = "10021", - PhoneNumber = "123456789", - StateProvince = _stateProvinceRepository.Table.FirstOrDefault(sp => sp.Name == "New York"), - Country = _countryRepository.Table.FirstOrDefault(c => c.ThreeLetterIsoCode == "USA"), - CreatedOnUtc = DateTime.UtcNow, - }; - _addressRepository.Insert(affiliateAddress); - var affilate = new Affiliate - { - Active = true, - Address = affiliateAddress - }; - _affiliateRepository.Insert(affilate); - } - - private void AddProductTag(Product product, string tag) - { - var productTag = _productTagRepository.Table.FirstOrDefault(pt => pt.Name == tag); - if (productTag == null) - { - productTag = new ProductTag - { - Name = tag, - }; - } - product.ProductTags.Add(productTag); - _productRepository.Update(product); - } - - #endregion - - #region Methods - - public virtual void InstallData(string defaultUserEmail, - string defaultUserPassword, bool installSampleData = true) - { - InstallStores(); - InstallMeasures(); - InstallTaxCategories(); - InstallLanguages(); - InstallCurrencies(); - InstallCountriesAndStates(); - InstallShippingMethods(); - InstallDeliveryDates(); - InstallProductAvailabilityRanges(); - InstallCustomersAndUsers(defaultUserEmail, defaultUserPassword); - InstallEmailAccounts(); - InstallMessageTemplates(); - InstallSettings(installSampleData); - InstallTopicTemplates(); - InstallTopics(); - InstallLocaleResources(); - InstallActivityLogTypes(); - InstallProductTemplates(); - InstallCategoryTemplates(); - InstallManufacturerTemplates(); - InstallScheduleTasks(); - InstallReturnRequestReasons(); - InstallReturnRequestActions(); - - if (installSampleData) - { - InstallCheckoutAttributes(); - InstallSpecificationAttributes(); - InstallProductAttributes(); - InstallCategories(); - InstallManufacturers(); - InstallProducts(defaultUserEmail); - InstallForums(); - InstallDiscounts(); - InstallBlogPosts(defaultUserEmail); - InstallNews(defaultUserEmail); - InstallPolls(); - InstallWarehouses(); - InstallVendors(); - InstallAffiliates(); - InstallOrders(); - InstallActivityLog(defaultUserEmail); - InstallSearchTerms(); - } - } - - #endregion - } +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Nop.Core; +using Nop.Core.Data; +using Nop.Core.Domain; +using Nop.Core.Domain.Affiliates; +using Nop.Core.Domain.Blogs; +using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Cms; +using Nop.Core.Domain.Common; +using Nop.Core.Domain.Customers; +using Nop.Core.Domain.Directory; +using Nop.Core.Domain.Discounts; +using Nop.Core.Domain.Forums; +using Nop.Core.Domain.Localization; +using Nop.Core.Domain.Logging; +using Nop.Core.Domain.Media; +using Nop.Core.Domain.Messages; +using Nop.Core.Domain.News; +using Nop.Core.Domain.Orders; +using Nop.Core.Domain.Payments; +using Nop.Core.Domain.Polls; +using Nop.Core.Domain.Security; +using Nop.Core.Domain.Seo; +using Nop.Core.Domain.Shipping; +using Nop.Core.Domain.Stores; +using Nop.Core.Domain.Tasks; +using Nop.Core.Domain.Tax; +using Nop.Core.Domain.Topics; +using Nop.Core.Domain.Vendors; +using Nop.Core.Infrastructure; +using Nop.Services.Common; +using Nop.Services.Configuration; +using Nop.Services.Customers; +using Nop.Services.Helpers; +using Nop.Services.Localization; +using Nop.Services.Media; +using Nop.Services.Seo; + +namespace Nop.Services.Installation +{ + public partial class CodeFirstInstallationService : IInstallationService + { + #region Fields + + private readonly IRepository _storeRepository; + private readonly IRepository _measureDimensionRepository; + private readonly IRepository _measureWeightRepository; + private readonly IRepository _taxCategoryRepository; + private readonly IRepository _languageRepository; + private readonly IRepository _currencyRepository; + private readonly IRepository _customerRepository; + private readonly IRepository _customerPasswordRepository; + private readonly IRepository _customerRoleRepository; + private readonly IRepository _specificationAttributeRepository; + private readonly IRepository _checkoutAttributeRepository; + private readonly IRepository _productAttributeRepository; + private readonly IRepository _categoryRepository; + private readonly IRepository _manufacturerRepository; + private readonly IRepository _productRepository; + private readonly IRepository _urlRecordRepository; + private readonly IRepository _relatedProductRepository; + private readonly IRepository _emailAccountRepository; + private readonly IRepository _messageTemplateRepository; + private readonly IRepository _forumGroupRepository; + private readonly IRepository _forumRepository; + private readonly IRepository _countryRepository; + private readonly IRepository _stateProvinceRepository; + private readonly IRepository _discountRepository; + private readonly IRepository _blogPostRepository; + private readonly IRepository _topicRepository; + private readonly IRepository _newsItemRepository; + private readonly IRepository _pollRepository; + private readonly IRepository _shippingMethodRepository; + private readonly IRepository _deliveryDateRepository; + private readonly IRepository _productAvailabilityRangeRepository; + private readonly IRepository _activityLogTypeRepository; + private readonly IRepository _activityLogRepository; + private readonly IRepository _productTagRepository; + private readonly IRepository _productTemplateRepository; + private readonly IRepository _categoryTemplateRepository; + private readonly IRepository _manufacturerTemplateRepository; + private readonly IRepository _topicTemplateRepository; + private readonly IRepository _scheduleTaskRepository; + private readonly IRepository _returnRequestReasonRepository; + private readonly IRepository _returnRequestActionRepository; + private readonly IRepository
    _addressRepository; + private readonly IRepository _warehouseRepository; + private readonly IRepository _vendorRepository; + private readonly IRepository _affiliateRepository; + private readonly IRepository _orderRepository; + private readonly IRepository _orderItemRepository; + private readonly IRepository _orderNoteRepository; + private readonly IRepository _giftCardRepository; + private readonly IRepository _shipmentRepository; + private readonly IRepository _searchTermRepository; + private readonly IRepository _shipmentItemRepository; + private readonly IRepository _stockQuantityHistoryRepository; + private readonly IGenericAttributeService _genericAttributeService; + private readonly IWebHelper _webHelper; + + #endregion + + #region Ctor + + public CodeFirstInstallationService(IRepository storeRepository, + IRepository measureDimensionRepository, + IRepository measureWeightRepository, + IRepository taxCategoryRepository, + IRepository languageRepository, + IRepository currencyRepository, + IRepository customerRepository, + IRepository customerPasswordRepository, + IRepository customerRoleRepository, + IRepository specificationAttributeRepository, + IRepository checkoutAttributeRepository, + IRepository productAttributeRepository, + IRepository categoryRepository, + IRepository manufacturerRepository, + IRepository productRepository, + IRepository urlRecordRepository, + IRepository relatedProductRepository, + IRepository emailAccountRepository, + IRepository messageTemplateRepository, + IRepository forumGroupRepository, + IRepository forumRepository, + IRepository countryRepository, + IRepository stateProvinceRepository, + IRepository discountRepository, + IRepository blogPostRepository, + IRepository topicRepository, + IRepository newsItemRepository, + IRepository pollRepository, + IRepository shippingMethodRepository, + IRepository deliveryDateRepository, + IRepository productAvailabilityRangeRepository, + IRepository activityLogTypeRepository, + IRepository activityLogRepository, + IRepository productTagRepository, + IRepository productTemplateRepository, + IRepository categoryTemplateRepository, + IRepository manufacturerTemplateRepository, + IRepository topicTemplateRepository, + IRepository scheduleTaskRepository, + IRepository returnRequestReasonRepository, + IRepository returnRequestActionRepository, + IRepository
    addressRepository, + IRepository warehouseRepository, + IRepository vendorRepository, + IRepository affiliateRepository, + IRepository orderRepository, + IRepository orderItemRepository, + IRepository orderNoteRepository, + IRepository giftCardRepository, + IRepository shipmentRepository, + IRepository shipmentItemRepository, + IRepository searchTermRepository, + IRepository stockQuantityHistoryRepository, + IGenericAttributeService genericAttributeService, + IWebHelper webHelper) + { + this._storeRepository = storeRepository; + this._measureDimensionRepository = measureDimensionRepository; + this._measureWeightRepository = measureWeightRepository; + this._taxCategoryRepository = taxCategoryRepository; + this._languageRepository = languageRepository; + this._currencyRepository = currencyRepository; + this._customerRepository = customerRepository; + this._customerPasswordRepository = customerPasswordRepository; + this._customerRoleRepository = customerRoleRepository; + this._specificationAttributeRepository = specificationAttributeRepository; + this._checkoutAttributeRepository = checkoutAttributeRepository; + this._productAttributeRepository = productAttributeRepository; + this._categoryRepository = categoryRepository; + this._manufacturerRepository = manufacturerRepository; + this._productRepository = productRepository; + this._urlRecordRepository = urlRecordRepository; + this._relatedProductRepository = relatedProductRepository; + this._emailAccountRepository = emailAccountRepository; + this._messageTemplateRepository = messageTemplateRepository; + this._forumGroupRepository = forumGroupRepository; + this._forumRepository = forumRepository; + this._countryRepository = countryRepository; + this._stateProvinceRepository = stateProvinceRepository; + this._discountRepository = discountRepository; + this._blogPostRepository = blogPostRepository; + this._topicRepository = topicRepository; + this._newsItemRepository = newsItemRepository; + this._pollRepository = pollRepository; + this._shippingMethodRepository = shippingMethodRepository; + this._deliveryDateRepository = deliveryDateRepository; + this._productAvailabilityRangeRepository = productAvailabilityRangeRepository; + this._activityLogTypeRepository = activityLogTypeRepository; + this._activityLogRepository = activityLogRepository; + this._productTagRepository = productTagRepository; + this._productTemplateRepository = productTemplateRepository; + this._categoryTemplateRepository = categoryTemplateRepository; + this._manufacturerTemplateRepository = manufacturerTemplateRepository; + this._topicTemplateRepository = topicTemplateRepository; + this._scheduleTaskRepository = scheduleTaskRepository; + this._returnRequestReasonRepository = returnRequestReasonRepository; + this._returnRequestActionRepository = returnRequestActionRepository; + this._addressRepository = addressRepository; + this._warehouseRepository = warehouseRepository; + this._vendorRepository = vendorRepository; + this._affiliateRepository = affiliateRepository; + this._orderRepository = orderRepository; + this._orderItemRepository = orderItemRepository; + this._orderNoteRepository = orderNoteRepository; + this._giftCardRepository = giftCardRepository; + this._shipmentRepository = shipmentRepository; + this._shipmentItemRepository = shipmentItemRepository; + this._searchTermRepository = searchTermRepository; + this._stockQuantityHistoryRepository = stockQuantityHistoryRepository; + this._genericAttributeService = genericAttributeService; + this._webHelper = webHelper; + } + + #endregion + + #region Utilities + + protected virtual void InstallStores() + { + //var storeUrl = "http://www.yourStore.com/"; + var storeUrl = _webHelper.GetStoreLocation(false); + var stores = new List + { + new Store + { + Name = "Your store name", + Url = storeUrl, + SslEnabled = false, + Hosts = "yourstore.com,www.yourstore.com", + DisplayOrder = 1, + //should we set some default company info? + CompanyName = "Your company name", + CompanyAddress = "your company country, state, zip, street, etc", + CompanyPhoneNumber = "(123) 456-78901", + CompanyVat = null, + }, + }; + + _storeRepository.Insert(stores); + } + + protected virtual void InstallMeasures() + { + var measureDimensions = new List + { + new MeasureDimension + { + Name = "inch(es)", + SystemKeyword = "inches", + Ratio = 1M, + DisplayOrder = 1, + }, + new MeasureDimension + { + Name = "feet", + SystemKeyword = "feet", + Ratio = 0.08333333M, + DisplayOrder = 2, + }, + new MeasureDimension + { + Name = "meter(s)", + SystemKeyword = "meters", + Ratio = 0.0254M, + DisplayOrder = 3, + }, + new MeasureDimension + { + Name = "millimetre(s)", + SystemKeyword = "millimetres", + Ratio = 25.4M, + DisplayOrder = 4, + } + }; + + _measureDimensionRepository.Insert(measureDimensions); + + var measureWeights = new List + { + new MeasureWeight + { + Name = "ounce(s)", + SystemKeyword = "ounce", + Ratio = 16M, + DisplayOrder = 1, + }, + new MeasureWeight + { + Name = "lb(s)", + SystemKeyword = "lb", + Ratio = 1M, + DisplayOrder = 2, + }, + new MeasureWeight + { + Name = "kg(s)", + SystemKeyword = "kg", + Ratio = 0.45359237M, + DisplayOrder = 3, + }, + new MeasureWeight + { + Name = "gram(s)", + SystemKeyword = "grams", + Ratio = 453.59237M, + DisplayOrder = 4, + } + }; + + _measureWeightRepository.Insert(measureWeights); + } + + protected virtual void InstallTaxCategories() + { + var taxCategories = new List + { + new TaxCategory + { + Name = "Books", + DisplayOrder = 1, + }, + new TaxCategory + { + Name = "Electronics & Software", + DisplayOrder = 5, + }, + new TaxCategory + { + Name = "Downloadable Products", + DisplayOrder = 10, + }, + new TaxCategory + { + Name = "Jewelry", + DisplayOrder = 15, + }, + new TaxCategory + { + Name = "Apparel", + DisplayOrder = 20, + }, + }; + _taxCategoryRepository.Insert(taxCategories); + + } + + protected virtual void InstallLanguages() + { + var language = new Language + { + Name = "English", + LanguageCulture = "en-US", + UniqueSeoCode = "en", + FlagImageFileName = "us.png", + Published = true, + DisplayOrder = 1 + }; + _languageRepository.Insert(language); + } + + protected virtual void InstallLocaleResources() + { + //'English' language + var language = _languageRepository.Table.Single(l => l.Name == "English"); + + //save resources + foreach (var filePath in System.IO.Directory.EnumerateFiles(CommonHelper.MapPath("~/App_Data/Localization/"), "*.nopres.xml", SearchOption.TopDirectoryOnly)) + { + var localesXml = File.ReadAllText(filePath); + var localizationService = EngineContext.Current.Resolve(); + localizationService.ImportResourcesFromXml(language, localesXml); + } + + } + + protected virtual void InstallCurrencies() + { + var currencies = new List + { + new Currency + { + Name = "US Dollar", + CurrencyCode = "USD", + Rate = 1, + DisplayLocale = "en-US", + CustomFormatting = "", + Published = true, + DisplayOrder = 1, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + RoundingType = RoundingType.Rounding001 + }, + new Currency + { + Name = "Australian Dollar", + CurrencyCode = "AUD", + Rate = 1.36M, + DisplayLocale = "en-AU", + CustomFormatting = "", + Published = false, + DisplayOrder = 2, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + RoundingType = RoundingType.Rounding001 + }, + new Currency + { + Name = "British Pound", + CurrencyCode = "GBP", + Rate = 0.82M, + DisplayLocale = "en-GB", + CustomFormatting = "", + Published = false, + DisplayOrder = 3, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + RoundingType = RoundingType.Rounding001 + }, + new Currency + { + Name = "Canadian Dollar", + CurrencyCode = "CAD", + Rate = 1.32M, + DisplayLocale = "en-CA", + CustomFormatting = "", + Published = false, + DisplayOrder = 4, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + RoundingType = RoundingType.Rounding001 + }, + new Currency + { + Name = "Chinese Yuan Renminbi", + CurrencyCode = "CNY", + Rate = 6.93M, + DisplayLocale = "zh-CN", + CustomFormatting = "", + Published = false, + DisplayOrder = 5, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + RoundingType = RoundingType.Rounding001 + }, + new Currency + { + Name = "Euro", + CurrencyCode = "EUR", + Rate = 0.95M, + DisplayLocale = "", + //CustomFormatting = "0.00", + CustomFormatting = string.Format("{0}0.00", "\u20ac"), + Published = true, + DisplayOrder = 6, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + RoundingType = RoundingType.Rounding001 + }, + new Currency + { + Name = "Hong Kong Dollar", + CurrencyCode = "HKD", + Rate = 7.75M, + DisplayLocale = "zh-HK", + CustomFormatting = "", + Published = false, + DisplayOrder = 7, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + RoundingType = RoundingType.Rounding001 + }, + new Currency + { + Name = "Japanese Yen", + CurrencyCode = "JPY", + Rate = 116.64M, + DisplayLocale = "ja-JP", + CustomFormatting = "", + Published = false, + DisplayOrder = 8, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + RoundingType = RoundingType.Rounding001 + }, + new Currency + { + Name = "Russian Rouble", + CurrencyCode = "RUB", + Rate = 59.75M, + DisplayLocale = "ru-RU", + CustomFormatting = "", + Published = false, + DisplayOrder = 9, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + RoundingType = RoundingType.Rounding001 + }, + new Currency + { + Name = "Swedish Krona", + CurrencyCode = "SEK", + Rate = 9.08M, + DisplayLocale = "sv-SE", + CustomFormatting = "", + Published = false, + DisplayOrder = 10, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + RoundingType = RoundingType.Rounding1 + }, + new Currency + { + Name = "Romanian Leu", + CurrencyCode = "RON", + Rate = 4.28M, + DisplayLocale = "ro-RO", + CustomFormatting = "", + Published = false, + DisplayOrder = 11, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + RoundingType = RoundingType.Rounding001 + }, + new Currency + { + Name = "Indian Rupee", + CurrencyCode = "INR", + Rate = 68.17M, + DisplayLocale = "en-IN", + CustomFormatting = "", + Published = false, + DisplayOrder = 12, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + RoundingType = RoundingType.Rounding001 + }, + }; + _currencyRepository.Insert(currencies); + } + + protected virtual void InstallCountriesAndStates() + { + var cUsa = new Country + { + Name = "United States", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "US", + ThreeLetterIsoCode = "USA", + NumericIsoCode = 840, + SubjectToVat = false, + DisplayOrder = 1, + Published = true, + }; + cUsa.StateProvinces.Add(new StateProvince + { + Name = "AA (Armed Forces Americas)", + Abbreviation = "AA", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "AE (Armed Forces Europe)", + Abbreviation = "AE", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Alabama", + Abbreviation = "AL", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Alaska", + Abbreviation = "AK", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "American Samoa", + Abbreviation = "AS", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "AP (Armed Forces Pacific)", + Abbreviation = "AP", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Arizona", + Abbreviation = "AZ", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Arkansas", + Abbreviation = "AR", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "California", + Abbreviation = "CA", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Colorado", + Abbreviation = "CO", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Connecticut", + Abbreviation = "CT", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Delaware", + Abbreviation = "DE", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "District of Columbia", + Abbreviation = "DC", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Federated States of Micronesia", + Abbreviation = "FM", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Florida", + Abbreviation = "FL", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Georgia", + Abbreviation = "GA", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Guam", + Abbreviation = "GU", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Hawaii", + Abbreviation = "HI", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Idaho", + Abbreviation = "ID", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Illinois", + Abbreviation = "IL", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Indiana", + Abbreviation = "IN", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Iowa", + Abbreviation = "IA", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Kansas", + Abbreviation = "KS", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Kentucky", + Abbreviation = "KY", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Louisiana", + Abbreviation = "LA", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Maine", + Abbreviation = "ME", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Marshall Islands", + Abbreviation = "MH", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Maryland", + Abbreviation = "MD", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Massachusetts", + Abbreviation = "MA", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Michigan", + Abbreviation = "MI", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Minnesota", + Abbreviation = "MN", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Mississippi", + Abbreviation = "MS", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Missouri", + Abbreviation = "MO", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Montana", + Abbreviation = "MT", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Nebraska", + Abbreviation = "NE", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Nevada", + Abbreviation = "NV", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "New Hampshire", + Abbreviation = "NH", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "New Jersey", + Abbreviation = "NJ", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "New Mexico", + Abbreviation = "NM", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "New York", + Abbreviation = "NY", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "North Carolina", + Abbreviation = "NC", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "North Dakota", + Abbreviation = "ND", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Northern Mariana Islands", + Abbreviation = "MP", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Ohio", + Abbreviation = "OH", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Oklahoma", + Abbreviation = "OK", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Oregon", + Abbreviation = "OR", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Palau", + Abbreviation = "PW", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Pennsylvania", + Abbreviation = "PA", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Puerto Rico", + Abbreviation = "PR", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Rhode Island", + Abbreviation = "RI", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "South Carolina", + Abbreviation = "SC", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "South Dakota", + Abbreviation = "SD", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Tennessee", + Abbreviation = "TN", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Texas", + Abbreviation = "TX", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Utah", + Abbreviation = "UT", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Vermont", + Abbreviation = "VT", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Virgin Islands", + Abbreviation = "VI", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Virginia", + Abbreviation = "VA", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Washington", + Abbreviation = "WA", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "West Virginia", + Abbreviation = "WV", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Wisconsin", + Abbreviation = "WI", + Published = true, + DisplayOrder = 1, + }); + cUsa.StateProvinces.Add(new StateProvince + { + Name = "Wyoming", + Abbreviation = "WY", + Published = true, + DisplayOrder = 1, + }); + var cCanada = new Country + { + Name = "Canada", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "CA", + ThreeLetterIsoCode = "CAN", + NumericIsoCode = 124, + SubjectToVat = false, + DisplayOrder = 100, + Published = true, + }; + cCanada.StateProvinces.Add(new StateProvince + { + Name = "Alberta", + Abbreviation = "AB", + Published = true, + DisplayOrder = 1, + }); + cCanada.StateProvinces.Add(new StateProvince + { + Name = "British Columbia", + Abbreviation = "BC", + Published = true, + DisplayOrder = 1, + }); + cCanada.StateProvinces.Add(new StateProvince + { + Name = "Manitoba", + Abbreviation = "MB", + Published = true, + DisplayOrder = 1, + }); + cCanada.StateProvinces.Add(new StateProvince + { + Name = "New Brunswick", + Abbreviation = "NB", + Published = true, + DisplayOrder = 1, + }); + cCanada.StateProvinces.Add(new StateProvince + { + Name = "Newfoundland and Labrador", + Abbreviation = "NL", + Published = true, + DisplayOrder = 1, + }); + cCanada.StateProvinces.Add(new StateProvince + { + Name = "Northwest Territories", + Abbreviation = "NT", + Published = true, + DisplayOrder = 1, + }); + cCanada.StateProvinces.Add(new StateProvince + { + Name = "Nova Scotia", + Abbreviation = "NS", + Published = true, + DisplayOrder = 1, + }); + cCanada.StateProvinces.Add(new StateProvince + { + Name = "Nunavut", + Abbreviation = "NU", + Published = true, + DisplayOrder = 1, + }); + cCanada.StateProvinces.Add(new StateProvince + { + Name = "Ontario", + Abbreviation = "ON", + Published = true, + DisplayOrder = 1, + }); + cCanada.StateProvinces.Add(new StateProvince + { + Name = "Prince Edward Island", + Abbreviation = "PE", + Published = true, + DisplayOrder = 1, + }); + cCanada.StateProvinces.Add(new StateProvince + { + Name = "Quebec", + Abbreviation = "QC", + Published = true, + DisplayOrder = 1, + }); + cCanada.StateProvinces.Add(new StateProvince + { + Name = "Saskatchewan", + Abbreviation = "SK", + Published = true, + DisplayOrder = 1, + }); + cCanada.StateProvinces.Add(new StateProvince + { + Name = "Yukon Territory", + Abbreviation = "YT", + Published = true, + DisplayOrder = 1, + }); + var countries = new List + { + cUsa, + cCanada, + //other countries + new Country + { + Name = "Argentina", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "AR", + ThreeLetterIsoCode = "ARG", + NumericIsoCode = 32, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Armenia", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "AM", + ThreeLetterIsoCode = "ARM", + NumericIsoCode = 51, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Aruba", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "AW", + ThreeLetterIsoCode = "ABW", + NumericIsoCode = 533, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Australia", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "AU", + ThreeLetterIsoCode = "AUS", + NumericIsoCode = 36, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Austria", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "AT", + ThreeLetterIsoCode = "AUT", + NumericIsoCode = 40, + SubjectToVat = true, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Azerbaijan", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "AZ", + ThreeLetterIsoCode = "AZE", + NumericIsoCode = 31, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Bahamas", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "BS", + ThreeLetterIsoCode = "BHS", + NumericIsoCode = 44, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Bangladesh", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "BD", + ThreeLetterIsoCode = "BGD", + NumericIsoCode = 50, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Belarus", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "BY", + ThreeLetterIsoCode = "BLR", + NumericIsoCode = 112, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Belgium", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "BE", + ThreeLetterIsoCode = "BEL", + NumericIsoCode = 56, + SubjectToVat = true, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Belize", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "BZ", + ThreeLetterIsoCode = "BLZ", + NumericIsoCode = 84, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Bermuda", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "BM", + ThreeLetterIsoCode = "BMU", + NumericIsoCode = 60, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Bolivia", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "BO", + ThreeLetterIsoCode = "BOL", + NumericIsoCode = 68, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Bosnia and Herzegowina", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "BA", + ThreeLetterIsoCode = "BIH", + NumericIsoCode = 70, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Brazil", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "BR", + ThreeLetterIsoCode = "BRA", + NumericIsoCode = 76, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Bulgaria", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "BG", + ThreeLetterIsoCode = "BGR", + NumericIsoCode = 100, + SubjectToVat = true, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Cayman Islands", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "KY", + ThreeLetterIsoCode = "CYM", + NumericIsoCode = 136, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Chile", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "CL", + ThreeLetterIsoCode = "CHL", + NumericIsoCode = 152, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "China", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "CN", + ThreeLetterIsoCode = "CHN", + NumericIsoCode = 156, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Colombia", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "CO", + ThreeLetterIsoCode = "COL", + NumericIsoCode = 170, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Costa Rica", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "CR", + ThreeLetterIsoCode = "CRI", + NumericIsoCode = 188, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Croatia", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "HR", + ThreeLetterIsoCode = "HRV", + NumericIsoCode = 191, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Cuba", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "CU", + ThreeLetterIsoCode = "CUB", + NumericIsoCode = 192, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Cyprus", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "CY", + ThreeLetterIsoCode = "CYP", + NumericIsoCode = 196, + SubjectToVat = true, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Czech Republic", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "CZ", + ThreeLetterIsoCode = "CZE", + NumericIsoCode = 203, + SubjectToVat = true, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Denmark", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "DK", + ThreeLetterIsoCode = "DNK", + NumericIsoCode = 208, + SubjectToVat = true, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Dominican Republic", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "DO", + ThreeLetterIsoCode = "DOM", + NumericIsoCode = 214, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "East Timor", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "TL", + ThreeLetterIsoCode = "TLS", + NumericIsoCode = 626, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Ecuador", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "EC", + ThreeLetterIsoCode = "ECU", + NumericIsoCode = 218, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Egypt", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "EG", + ThreeLetterIsoCode = "EGY", + NumericIsoCode = 818, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Finland", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "FI", + ThreeLetterIsoCode = "FIN", + NumericIsoCode = 246, + SubjectToVat = true, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "France", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "FR", + ThreeLetterIsoCode = "FRA", + NumericIsoCode = 250, + SubjectToVat = true, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Georgia", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "GE", + ThreeLetterIsoCode = "GEO", + NumericIsoCode = 268, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Germany", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "DE", + ThreeLetterIsoCode = "DEU", + NumericIsoCode = 276, + SubjectToVat = true, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Gibraltar", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "GI", + ThreeLetterIsoCode = "GIB", + NumericIsoCode = 292, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Greece", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "GR", + ThreeLetterIsoCode = "GRC", + NumericIsoCode = 300, + SubjectToVat = true, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Guatemala", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "GT", + ThreeLetterIsoCode = "GTM", + NumericIsoCode = 320, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Hong Kong", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "HK", + ThreeLetterIsoCode = "HKG", + NumericIsoCode = 344, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Hungary", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "HU", + ThreeLetterIsoCode = "HUN", + NumericIsoCode = 348, + SubjectToVat = true, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "India", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "IN", + ThreeLetterIsoCode = "IND", + NumericIsoCode = 356, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Indonesia", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "ID", + ThreeLetterIsoCode = "IDN", + NumericIsoCode = 360, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Ireland", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "IE", + ThreeLetterIsoCode = "IRL", + NumericIsoCode = 372, + SubjectToVat = true, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Israel", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "IL", + ThreeLetterIsoCode = "ISR", + NumericIsoCode = 376, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Italy", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "IT", + ThreeLetterIsoCode = "ITA", + NumericIsoCode = 380, + SubjectToVat = true, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Jamaica", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "JM", + ThreeLetterIsoCode = "JAM", + NumericIsoCode = 388, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Japan", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "JP", + ThreeLetterIsoCode = "JPN", + NumericIsoCode = 392, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Jordan", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "JO", + ThreeLetterIsoCode = "JOR", + NumericIsoCode = 400, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Kazakhstan", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "KZ", + ThreeLetterIsoCode = "KAZ", + NumericIsoCode = 398, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Korea, Democratic People's Republic of", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "KP", + ThreeLetterIsoCode = "PRK", + NumericIsoCode = 408, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Kuwait", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "KW", + ThreeLetterIsoCode = "KWT", + NumericIsoCode = 414, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Malaysia", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "MY", + ThreeLetterIsoCode = "MYS", + NumericIsoCode = 458, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Mexico", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "MX", + ThreeLetterIsoCode = "MEX", + NumericIsoCode = 484, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Netherlands", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "NL", + ThreeLetterIsoCode = "NLD", + NumericIsoCode = 528, + SubjectToVat = true, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "New Zealand", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "NZ", + ThreeLetterIsoCode = "NZL", + NumericIsoCode = 554, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Norway", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "NO", + ThreeLetterIsoCode = "NOR", + NumericIsoCode = 578, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Pakistan", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "PK", + ThreeLetterIsoCode = "PAK", + NumericIsoCode = 586, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Palestine", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "PS", + ThreeLetterIsoCode = "PSE", + NumericIsoCode = 275, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Paraguay", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "PY", + ThreeLetterIsoCode = "PRY", + NumericIsoCode = 600, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Peru", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "PE", + ThreeLetterIsoCode = "PER", + NumericIsoCode = 604, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Philippines", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "PH", + ThreeLetterIsoCode = "PHL", + NumericIsoCode = 608, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Poland", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "PL", + ThreeLetterIsoCode = "POL", + NumericIsoCode = 616, + SubjectToVat = true, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Portugal", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "PT", + ThreeLetterIsoCode = "PRT", + NumericIsoCode = 620, + SubjectToVat = true, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Puerto Rico", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "PR", + ThreeLetterIsoCode = "PRI", + NumericIsoCode = 630, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Qatar", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "QA", + ThreeLetterIsoCode = "QAT", + NumericIsoCode = 634, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Romania", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "RO", + ThreeLetterIsoCode = "ROM", + NumericIsoCode = 642, + SubjectToVat = true, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Russian Federation", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "RU", + ThreeLetterIsoCode = "RUS", + NumericIsoCode = 643, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Saudi Arabia", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "SA", + ThreeLetterIsoCode = "SAU", + NumericIsoCode = 682, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Singapore", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "SG", + ThreeLetterIsoCode = "SGP", + NumericIsoCode = 702, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Slovakia (Slovak Republic)", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "SK", + ThreeLetterIsoCode = "SVK", + NumericIsoCode = 703, + SubjectToVat = true, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Slovenia", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "SI", + ThreeLetterIsoCode = "SVN", + NumericIsoCode = 705, + SubjectToVat = true, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "South Africa", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "ZA", + ThreeLetterIsoCode = "ZAF", + NumericIsoCode = 710, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Spain", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "ES", + ThreeLetterIsoCode = "ESP", + NumericIsoCode = 724, + SubjectToVat = true, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Sweden", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "SE", + ThreeLetterIsoCode = "SWE", + NumericIsoCode = 752, + SubjectToVat = true, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Switzerland", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "CH", + ThreeLetterIsoCode = "CHE", + NumericIsoCode = 756, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Taiwan", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "TW", + ThreeLetterIsoCode = "TWN", + NumericIsoCode = 158, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Thailand", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "TH", + ThreeLetterIsoCode = "THA", + NumericIsoCode = 764, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Turkey", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "TR", + ThreeLetterIsoCode = "TUR", + NumericIsoCode = 792, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Ukraine", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "UA", + ThreeLetterIsoCode = "UKR", + NumericIsoCode = 804, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "United Arab Emirates", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "AE", + ThreeLetterIsoCode = "ARE", + NumericIsoCode = 784, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "United Kingdom", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "GB", + ThreeLetterIsoCode = "GBR", + NumericIsoCode = 826, + SubjectToVat = true, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "United States minor outlying islands", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "UM", + ThreeLetterIsoCode = "UMI", + NumericIsoCode = 581, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Uruguay", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "UY", + ThreeLetterIsoCode = "URY", + NumericIsoCode = 858, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Uzbekistan", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "UZ", + ThreeLetterIsoCode = "UZB", + NumericIsoCode = 860, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Venezuela", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "VE", + ThreeLetterIsoCode = "VEN", + NumericIsoCode = 862, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Serbia", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "RS", + ThreeLetterIsoCode = "SRB", + NumericIsoCode = 688, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Afghanistan", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "AF", + ThreeLetterIsoCode = "AFG", + NumericIsoCode = 4, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Albania", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "AL", + ThreeLetterIsoCode = "ALB", + NumericIsoCode = 8, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Algeria", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "DZ", + ThreeLetterIsoCode = "DZA", + NumericIsoCode = 12, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "American Samoa", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "AS", + ThreeLetterIsoCode = "ASM", + NumericIsoCode = 16, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Andorra", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "AD", + ThreeLetterIsoCode = "AND", + NumericIsoCode = 20, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Angola", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "AO", + ThreeLetterIsoCode = "AGO", + NumericIsoCode = 24, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Anguilla", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "AI", + ThreeLetterIsoCode = "AIA", + NumericIsoCode = 660, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Antarctica", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "AQ", + ThreeLetterIsoCode = "ATA", + NumericIsoCode = 10, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Antigua and Barbuda", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "AG", + ThreeLetterIsoCode = "ATG", + NumericIsoCode = 28, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Bahrain", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "BH", + ThreeLetterIsoCode = "BHR", + NumericIsoCode = 48, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Barbados", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "BB", + ThreeLetterIsoCode = "BRB", + NumericIsoCode = 52, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Benin", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "BJ", + ThreeLetterIsoCode = "BEN", + NumericIsoCode = 204, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Bhutan", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "BT", + ThreeLetterIsoCode = "BTN", + NumericIsoCode = 64, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Botswana", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "BW", + ThreeLetterIsoCode = "BWA", + NumericIsoCode = 72, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Bouvet Island", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "BV", + ThreeLetterIsoCode = "BVT", + NumericIsoCode = 74, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "British Indian Ocean Territory", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "IO", + ThreeLetterIsoCode = "IOT", + NumericIsoCode = 86, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Brunei Darussalam", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "BN", + ThreeLetterIsoCode = "BRN", + NumericIsoCode = 96, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Burkina Faso", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "BF", + ThreeLetterIsoCode = "BFA", + NumericIsoCode = 854, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Burundi", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "BI", + ThreeLetterIsoCode = "BDI", + NumericIsoCode = 108, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Cambodia", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "KH", + ThreeLetterIsoCode = "KHM", + NumericIsoCode = 116, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Cameroon", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "CM", + ThreeLetterIsoCode = "CMR", + NumericIsoCode = 120, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Cape Verde", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "CV", + ThreeLetterIsoCode = "CPV", + NumericIsoCode = 132, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Central African Republic", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "CF", + ThreeLetterIsoCode = "CAF", + NumericIsoCode = 140, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Chad", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "TD", + ThreeLetterIsoCode = "TCD", + NumericIsoCode = 148, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Christmas Island", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "CX", + ThreeLetterIsoCode = "CXR", + NumericIsoCode = 162, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Cocos (Keeling) Islands", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "CC", + ThreeLetterIsoCode = "CCK", + NumericIsoCode = 166, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Comoros", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "KM", + ThreeLetterIsoCode = "COM", + NumericIsoCode = 174, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Congo", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "CG", + ThreeLetterIsoCode = "COG", + NumericIsoCode = 178, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Congo (Democratic Republic of the)", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "CD", + ThreeLetterIsoCode = "COD", + NumericIsoCode = 180, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Cook Islands", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "CK", + ThreeLetterIsoCode = "COK", + NumericIsoCode = 184, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Cote D'Ivoire", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "CI", + ThreeLetterIsoCode = "CIV", + NumericIsoCode = 384, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Djibouti", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "DJ", + ThreeLetterIsoCode = "DJI", + NumericIsoCode = 262, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Dominica", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "DM", + ThreeLetterIsoCode = "DMA", + NumericIsoCode = 212, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "El Salvador", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "SV", + ThreeLetterIsoCode = "SLV", + NumericIsoCode = 222, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Equatorial Guinea", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "GQ", + ThreeLetterIsoCode = "GNQ", + NumericIsoCode = 226, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Eritrea", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "ER", + ThreeLetterIsoCode = "ERI", + NumericIsoCode = 232, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Estonia", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "EE", + ThreeLetterIsoCode = "EST", + NumericIsoCode = 233, + SubjectToVat = true, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Ethiopia", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "ET", + ThreeLetterIsoCode = "ETH", + NumericIsoCode = 231, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Falkland Islands (Malvinas)", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "FK", + ThreeLetterIsoCode = "FLK", + NumericIsoCode = 238, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Faroe Islands", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "FO", + ThreeLetterIsoCode = "FRO", + NumericIsoCode = 234, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Fiji", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "FJ", + ThreeLetterIsoCode = "FJI", + NumericIsoCode = 242, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "French Guiana", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "GF", + ThreeLetterIsoCode = "GUF", + NumericIsoCode = 254, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "French Polynesia", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "PF", + ThreeLetterIsoCode = "PYF", + NumericIsoCode = 258, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "French Southern Territories", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "TF", + ThreeLetterIsoCode = "ATF", + NumericIsoCode = 260, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Gabon", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "GA", + ThreeLetterIsoCode = "GAB", + NumericIsoCode = 266, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Gambia", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "GM", + ThreeLetterIsoCode = "GMB", + NumericIsoCode = 270, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Ghana", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "GH", + ThreeLetterIsoCode = "GHA", + NumericIsoCode = 288, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Greenland", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "GL", + ThreeLetterIsoCode = "GRL", + NumericIsoCode = 304, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Grenada", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "GD", + ThreeLetterIsoCode = "GRD", + NumericIsoCode = 308, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Guadeloupe", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "GP", + ThreeLetterIsoCode = "GLP", + NumericIsoCode = 312, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Guam", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "GU", + ThreeLetterIsoCode = "GUM", + NumericIsoCode = 316, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Guinea", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "GN", + ThreeLetterIsoCode = "GIN", + NumericIsoCode = 324, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Guinea-bissau", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "GW", + ThreeLetterIsoCode = "GNB", + NumericIsoCode = 624, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Guyana", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "GY", + ThreeLetterIsoCode = "GUY", + NumericIsoCode = 328, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Haiti", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "HT", + ThreeLetterIsoCode = "HTI", + NumericIsoCode = 332, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Heard and Mc Donald Islands", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "HM", + ThreeLetterIsoCode = "HMD", + NumericIsoCode = 334, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Honduras", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "HN", + ThreeLetterIsoCode = "HND", + NumericIsoCode = 340, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Iceland", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "IS", + ThreeLetterIsoCode = "ISL", + NumericIsoCode = 352, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Iran (Islamic Republic of)", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "IR", + ThreeLetterIsoCode = "IRN", + NumericIsoCode = 364, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Iraq", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "IQ", + ThreeLetterIsoCode = "IRQ", + NumericIsoCode = 368, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Kenya", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "KE", + ThreeLetterIsoCode = "KEN", + NumericIsoCode = 404, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Kiribati", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "KI", + ThreeLetterIsoCode = "KIR", + NumericIsoCode = 296, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Korea", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "KR", + ThreeLetterIsoCode = "KOR", + NumericIsoCode = 410, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Kyrgyzstan", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "KG", + ThreeLetterIsoCode = "KGZ", + NumericIsoCode = 417, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Lao People's Democratic Republic", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "LA", + ThreeLetterIsoCode = "LAO", + NumericIsoCode = 418, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Latvia", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "LV", + ThreeLetterIsoCode = "LVA", + NumericIsoCode = 428, + SubjectToVat = true, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Lebanon", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "LB", + ThreeLetterIsoCode = "LBN", + NumericIsoCode = 422, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Lesotho", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "LS", + ThreeLetterIsoCode = "LSO", + NumericIsoCode = 426, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Liberia", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "LR", + ThreeLetterIsoCode = "LBR", + NumericIsoCode = 430, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Libyan Arab Jamahiriya", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "LY", + ThreeLetterIsoCode = "LBY", + NumericIsoCode = 434, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Liechtenstein", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "LI", + ThreeLetterIsoCode = "LIE", + NumericIsoCode = 438, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Lithuania", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "LT", + ThreeLetterIsoCode = "LTU", + NumericIsoCode = 440, + SubjectToVat = true, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Luxembourg", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "LU", + ThreeLetterIsoCode = "LUX", + NumericIsoCode = 442, + SubjectToVat = true, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Macau", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "MO", + ThreeLetterIsoCode = "MAC", + NumericIsoCode = 446, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Macedonia", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "MK", + ThreeLetterIsoCode = "MKD", + NumericIsoCode = 807, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Madagascar", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "MG", + ThreeLetterIsoCode = "MDG", + NumericIsoCode = 450, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Malawi", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "MW", + ThreeLetterIsoCode = "MWI", + NumericIsoCode = 454, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Maldives", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "MV", + ThreeLetterIsoCode = "MDV", + NumericIsoCode = 462, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Mali", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "ML", + ThreeLetterIsoCode = "MLI", + NumericIsoCode = 466, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Malta", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "MT", + ThreeLetterIsoCode = "MLT", + NumericIsoCode = 470, + SubjectToVat = true, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Marshall Islands", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "MH", + ThreeLetterIsoCode = "MHL", + NumericIsoCode = 584, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Martinique", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "MQ", + ThreeLetterIsoCode = "MTQ", + NumericIsoCode = 474, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Mauritania", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "MR", + ThreeLetterIsoCode = "MRT", + NumericIsoCode = 478, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Mauritius", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "MU", + ThreeLetterIsoCode = "MUS", + NumericIsoCode = 480, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Mayotte", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "YT", + ThreeLetterIsoCode = "MYT", + NumericIsoCode = 175, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Micronesia", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "FM", + ThreeLetterIsoCode = "FSM", + NumericIsoCode = 583, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Moldova", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "MD", + ThreeLetterIsoCode = "MDA", + NumericIsoCode = 498, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Monaco", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "MC", + ThreeLetterIsoCode = "MCO", + NumericIsoCode = 492, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Mongolia", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "MN", + ThreeLetterIsoCode = "MNG", + NumericIsoCode = 496, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Montenegro", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "ME", + ThreeLetterIsoCode = "MNE", + NumericIsoCode = 499, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Montserrat", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "MS", + ThreeLetterIsoCode = "MSR", + NumericIsoCode = 500, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Morocco", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "MA", + ThreeLetterIsoCode = "MAR", + NumericIsoCode = 504, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Mozambique", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "MZ", + ThreeLetterIsoCode = "MOZ", + NumericIsoCode = 508, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Myanmar", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "MM", + ThreeLetterIsoCode = "MMR", + NumericIsoCode = 104, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Namibia", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "NA", + ThreeLetterIsoCode = "NAM", + NumericIsoCode = 516, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Nauru", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "NR", + ThreeLetterIsoCode = "NRU", + NumericIsoCode = 520, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Nepal", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "NP", + ThreeLetterIsoCode = "NPL", + NumericIsoCode = 524, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Netherlands Antilles", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "AN", + ThreeLetterIsoCode = "ANT", + NumericIsoCode = 530, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "New Caledonia", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "NC", + ThreeLetterIsoCode = "NCL", + NumericIsoCode = 540, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Nicaragua", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "NI", + ThreeLetterIsoCode = "NIC", + NumericIsoCode = 558, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Niger", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "NE", + ThreeLetterIsoCode = "NER", + NumericIsoCode = 562, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Nigeria", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "NG", + ThreeLetterIsoCode = "NGA", + NumericIsoCode = 566, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Niue", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "NU", + ThreeLetterIsoCode = "NIU", + NumericIsoCode = 570, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Norfolk Island", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "NF", + ThreeLetterIsoCode = "NFK", + NumericIsoCode = 574, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Northern Mariana Islands", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "MP", + ThreeLetterIsoCode = "MNP", + NumericIsoCode = 580, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Oman", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "OM", + ThreeLetterIsoCode = "OMN", + NumericIsoCode = 512, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Palau", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "PW", + ThreeLetterIsoCode = "PLW", + NumericIsoCode = 585, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Panama", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "PA", + ThreeLetterIsoCode = "PAN", + NumericIsoCode = 591, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Papua New Guinea", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "PG", + ThreeLetterIsoCode = "PNG", + NumericIsoCode = 598, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Pitcairn", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "PN", + ThreeLetterIsoCode = "PCN", + NumericIsoCode = 612, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Reunion", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "RE", + ThreeLetterIsoCode = "REU", + NumericIsoCode = 638, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Rwanda", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "RW", + ThreeLetterIsoCode = "RWA", + NumericIsoCode = 646, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Saint Kitts and Nevis", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "KN", + ThreeLetterIsoCode = "KNA", + NumericIsoCode = 659, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Saint Lucia", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "LC", + ThreeLetterIsoCode = "LCA", + NumericIsoCode = 662, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Saint Vincent and the Grenadines", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "VC", + ThreeLetterIsoCode = "VCT", + NumericIsoCode = 670, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Samoa", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "WS", + ThreeLetterIsoCode = "WSM", + NumericIsoCode = 882, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "San Marino", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "SM", + ThreeLetterIsoCode = "SMR", + NumericIsoCode = 674, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Sao Tome and Principe", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "ST", + ThreeLetterIsoCode = "STP", + NumericIsoCode = 678, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Senegal", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "SN", + ThreeLetterIsoCode = "SEN", + NumericIsoCode = 686, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Seychelles", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "SC", + ThreeLetterIsoCode = "SYC", + NumericIsoCode = 690, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Sierra Leone", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "SL", + ThreeLetterIsoCode = "SLE", + NumericIsoCode = 694, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Solomon Islands", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "SB", + ThreeLetterIsoCode = "SLB", + NumericIsoCode = 90, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Somalia", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "SO", + ThreeLetterIsoCode = "SOM", + NumericIsoCode = 706, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "South Georgia & South Sandwich Islands", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "GS", + ThreeLetterIsoCode = "SGS", + NumericIsoCode = 239, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "South Sudan", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "SS", + ThreeLetterIsoCode = "SSD", + NumericIsoCode = 728, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Sri Lanka", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "LK", + ThreeLetterIsoCode = "LKA", + NumericIsoCode = 144, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "St. Helena", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "SH", + ThreeLetterIsoCode = "SHN", + NumericIsoCode = 654, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "St. Pierre and Miquelon", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "PM", + ThreeLetterIsoCode = "SPM", + NumericIsoCode = 666, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Sudan", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "SD", + ThreeLetterIsoCode = "SDN", + NumericIsoCode = 736, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Suriname", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "SR", + ThreeLetterIsoCode = "SUR", + NumericIsoCode = 740, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Svalbard and Jan Mayen Islands", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "SJ", + ThreeLetterIsoCode = "SJM", + NumericIsoCode = 744, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Swaziland", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "SZ", + ThreeLetterIsoCode = "SWZ", + NumericIsoCode = 748, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Syrian Arab Republic", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "SY", + ThreeLetterIsoCode = "SYR", + NumericIsoCode = 760, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Tajikistan", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "TJ", + ThreeLetterIsoCode = "TJK", + NumericIsoCode = 762, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Tanzania", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "TZ", + ThreeLetterIsoCode = "TZA", + NumericIsoCode = 834, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Togo", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "TG", + ThreeLetterIsoCode = "TGO", + NumericIsoCode = 768, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Tokelau", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "TK", + ThreeLetterIsoCode = "TKL", + NumericIsoCode = 772, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Tonga", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "TO", + ThreeLetterIsoCode = "TON", + NumericIsoCode = 776, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Trinidad and Tobago", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "TT", + ThreeLetterIsoCode = "TTO", + NumericIsoCode = 780, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Tunisia", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "TN", + ThreeLetterIsoCode = "TUN", + NumericIsoCode = 788, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Turkmenistan", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "TM", + ThreeLetterIsoCode = "TKM", + NumericIsoCode = 795, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Turks and Caicos Islands", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "TC", + ThreeLetterIsoCode = "TCA", + NumericIsoCode = 796, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Tuvalu", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "TV", + ThreeLetterIsoCode = "TUV", + NumericIsoCode = 798, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Uganda", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "UG", + ThreeLetterIsoCode = "UGA", + NumericIsoCode = 800, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Vanuatu", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "VU", + ThreeLetterIsoCode = "VUT", + NumericIsoCode = 548, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Vatican City State (Holy See)", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "VA", + ThreeLetterIsoCode = "VAT", + NumericIsoCode = 336, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Viet Nam", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "VN", + ThreeLetterIsoCode = "VNM", + NumericIsoCode = 704, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Virgin Islands (British)", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "VG", + ThreeLetterIsoCode = "VGB", + NumericIsoCode = 92, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Virgin Islands (U.S.)", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "VI", + ThreeLetterIsoCode = "VIR", + NumericIsoCode = 850, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Wallis and Futuna Islands", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "WF", + ThreeLetterIsoCode = "WLF", + NumericIsoCode = 876, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Western Sahara", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "EH", + ThreeLetterIsoCode = "ESH", + NumericIsoCode = 732, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Yemen", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "YE", + ThreeLetterIsoCode = "YEM", + NumericIsoCode = 887, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Zambia", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "ZM", + ThreeLetterIsoCode = "ZMB", + NumericIsoCode = 894, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + new Country + { + Name = "Zimbabwe", + AllowsBilling = true, + AllowsShipping = true, + TwoLetterIsoCode = "ZW", + ThreeLetterIsoCode = "ZWE", + NumericIsoCode = 716, + SubjectToVat = false, + DisplayOrder = 100, + Published = true + }, + }; + _countryRepository.Insert(countries); + } + + protected virtual void InstallShippingMethods() + { + var shippingMethods = new List + { + new ShippingMethod + { + Name = "Ground", + Description ="Compared to other shipping methods, ground shipping is carried out closer to the earth", + DisplayOrder = 1 + }, + new ShippingMethod + { + Name = "Next Day Air", + Description ="The one day air shipping", + DisplayOrder = 3 + }, + new ShippingMethod + { + Name = "2nd Day Air", + Description ="The two day air shipping", + DisplayOrder = 3 + } + }; + _shippingMethodRepository.Insert(shippingMethods); + } + + protected virtual void InstallDeliveryDates() + { + var deliveryDates = new List + { + new DeliveryDate + { + Name = "1-2 days", + DisplayOrder = 1 + }, + new DeliveryDate + { + Name = "3-5 days", + DisplayOrder = 5 + }, + new DeliveryDate + { + Name = "1 week", + DisplayOrder = 10 + }, + }; + _deliveryDateRepository.Insert(deliveryDates); + } + + protected virtual void InstallProductAvailabilityRanges() + { + var productAvailabilityRanges = new List + { + new ProductAvailabilityRange + { + Name = "2-4 days", + DisplayOrder = 1 + }, + new ProductAvailabilityRange + { + Name = "7-10 days", + DisplayOrder = 2 + }, + new ProductAvailabilityRange + { + Name = "2 week", + DisplayOrder = 3 + }, + }; + _productAvailabilityRangeRepository.Insert(productAvailabilityRanges); + } + + protected virtual void InstallCustomersAndUsers(string defaultUserEmail, string defaultUserPassword) + { + var crAdministrators = new CustomerRole + { + Name = "Administrators", + Active = true, + IsSystemRole = true, + SystemName = SystemCustomerRoleNames.Administrators, + }; + var crForumModerators = new CustomerRole + { + Name = "Forum Moderators", + Active = true, + IsSystemRole = true, + SystemName = SystemCustomerRoleNames.ForumModerators, + }; + var crRegistered = new CustomerRole + { + Name = "Registered", + Active = true, + IsSystemRole = true, + SystemName = SystemCustomerRoleNames.Registered, + }; + var crGuests = new CustomerRole + { + Name = "Guests", + Active = true, + IsSystemRole = true, + SystemName = SystemCustomerRoleNames.Guests, + }; + var crVendors = new CustomerRole + { + Name = "Vendors", + Active = true, + IsSystemRole = true, + SystemName = SystemCustomerRoleNames.Vendors, + }; + var customerRoles = new List + { + crAdministrators, + crForumModerators, + crRegistered, + crGuests, + crVendors + }; + _customerRoleRepository.Insert(customerRoles); + + //default store + var defaultStore = _storeRepository.Table.FirstOrDefault(); + + if (defaultStore == null) + throw new Exception("No default store could be loaded"); + + var storeId = defaultStore.Id; + + //admin user + var adminUser = new Customer + { + CustomerGuid = Guid.NewGuid(), + Email = defaultUserEmail, + Username = defaultUserEmail, + Active = true, + CreatedOnUtc = DateTime.UtcNow, + LastActivityDateUtc = DateTime.UtcNow, + RegisteredInStoreId = storeId + }; + + var defaultAdminUserAddress = new Address + { + FirstName = "John", + LastName = "Smith", + PhoneNumber = "12345678", + Email = defaultUserEmail, + FaxNumber = "", + Company = "Nop Solutions Ltd", + Address1 = "21 West 52nd Street", + Address2 = "", + City = "New York", + StateProvince = _stateProvinceRepository.Table.FirstOrDefault(sp => sp.Name == "New York"), + Country = _countryRepository.Table.FirstOrDefault(c => c.ThreeLetterIsoCode == "USA"), + ZipPostalCode = "10021", + CreatedOnUtc = DateTime.UtcNow, + }; + adminUser.Addresses.Add(defaultAdminUserAddress); + adminUser.BillingAddress = defaultAdminUserAddress; + adminUser.ShippingAddress = defaultAdminUserAddress; + + adminUser.CustomerRoles.Add(crAdministrators); + adminUser.CustomerRoles.Add(crForumModerators); + adminUser.CustomerRoles.Add(crRegistered); + + _customerRepository.Insert(adminUser); + //set default customer name + _genericAttributeService.SaveAttribute(adminUser, SystemCustomerAttributeNames.FirstName, "John"); + _genericAttributeService.SaveAttribute(adminUser, SystemCustomerAttributeNames.LastName, "Smith"); + + //set hashed admin password + var customerRegistrationService = EngineContext.Current.Resolve(); + customerRegistrationService.ChangePassword(new ChangePasswordRequest(defaultUserEmail, false, + PasswordFormat.Hashed, defaultUserPassword)); + + //second user + var secondUserEmail = "steve_gates@nopCommerce.com"; + var secondUser = new Customer + { + CustomerGuid = Guid.NewGuid(), + Email = secondUserEmail, + Username = secondUserEmail, + Active = true, + CreatedOnUtc = DateTime.UtcNow, + LastActivityDateUtc = DateTime.UtcNow, + RegisteredInStoreId = storeId + }; + var defaultSecondUserAddress = new Address + { + FirstName = "Steve", + LastName = "Gates", + PhoneNumber = "87654321", + Email = secondUserEmail, + FaxNumber = "", + Company = "Steve Company", + Address1 = "750 Bel Air Rd.", + Address2 = "", + City = "Los Angeles", + StateProvince = _stateProvinceRepository.Table.FirstOrDefault(sp => sp.Name == "California"), + Country = _countryRepository.Table.FirstOrDefault(c => c.ThreeLetterIsoCode == "USA"), + ZipPostalCode = "90077", + CreatedOnUtc = DateTime.UtcNow, + }; + secondUser.Addresses.Add(defaultSecondUserAddress); + secondUser.BillingAddress = defaultSecondUserAddress; + secondUser.ShippingAddress = defaultSecondUserAddress; + + secondUser.CustomerRoles.Add(crRegistered); + + _customerRepository.Insert(secondUser); + //set default customer name + _genericAttributeService.SaveAttribute(secondUser, SystemCustomerAttributeNames.FirstName, defaultSecondUserAddress.FirstName); + _genericAttributeService.SaveAttribute(secondUser, SystemCustomerAttributeNames.LastName, defaultSecondUserAddress.LastName); + + //set customer password + _customerPasswordRepository.Insert(new CustomerPassword + { + Customer = secondUser, + Password = "123456", + PasswordFormat = PasswordFormat.Clear, + PasswordSalt = string.Empty, + CreatedOnUtc = DateTime.UtcNow + }); + + //third user + var thirdUserEmail = "arthur_holmes@nopCommerce.com"; + var thirdUser = new Customer + { + CustomerGuid = Guid.NewGuid(), + Email = thirdUserEmail, + Username = thirdUserEmail, + Active = true, + CreatedOnUtc = DateTime.UtcNow, + LastActivityDateUtc = DateTime.UtcNow, + RegisteredInStoreId = storeId + }; + var defaultThirdUserAddress = new Address + { + FirstName = "Arthur", + LastName = "Holmes", + PhoneNumber = "111222333", + Email = thirdUserEmail, + FaxNumber = "", + Company = "Holmes Company", + Address1 = "221B Baker Street", + Address2 = "", + City = "London", + Country = _countryRepository.Table.FirstOrDefault(c => c.ThreeLetterIsoCode == "GBR"), + ZipPostalCode = "NW1 6XE", + CreatedOnUtc = DateTime.UtcNow, + }; + thirdUser.Addresses.Add(defaultThirdUserAddress); + thirdUser.BillingAddress = defaultThirdUserAddress; + thirdUser.ShippingAddress = defaultThirdUserAddress; + + thirdUser.CustomerRoles.Add(crRegistered); + + _customerRepository.Insert(thirdUser); + //set default customer name + _genericAttributeService.SaveAttribute(thirdUser, SystemCustomerAttributeNames.FirstName, defaultThirdUserAddress.FirstName); + _genericAttributeService.SaveAttribute(thirdUser, SystemCustomerAttributeNames.LastName, defaultThirdUserAddress.LastName); + + //set customer password + _customerPasswordRepository.Insert(new CustomerPassword + { + Customer = thirdUser, + Password = "123456", + PasswordFormat = PasswordFormat.Clear, + PasswordSalt = string.Empty, + CreatedOnUtc = DateTime.UtcNow + }); + + //fourth user + var fourthUserEmail = "james_pan@nopCommerce.com"; + var fourthUser = new Customer + { + CustomerGuid = Guid.NewGuid(), + Email = fourthUserEmail, + Username = fourthUserEmail, + Active = true, + CreatedOnUtc = DateTime.UtcNow, + LastActivityDateUtc = DateTime.UtcNow, + RegisteredInStoreId = storeId + }; + var defaultFourthUserAddress = new Address + { + FirstName = "James", + LastName = "Pan", + PhoneNumber = "369258147", + Email = fourthUserEmail, + FaxNumber = "", + Company = "Pan Company", + Address1 = "St Katharines West 16", + Address2 = "", + City = "St Andrews", + Country = _countryRepository.Table.FirstOrDefault(c => c.ThreeLetterIsoCode == "GBR"), + ZipPostalCode = "KY16 9AX", + CreatedOnUtc = DateTime.UtcNow, + }; + fourthUser.Addresses.Add(defaultFourthUserAddress); + fourthUser.BillingAddress = defaultFourthUserAddress; + fourthUser.ShippingAddress = defaultFourthUserAddress; + + fourthUser.CustomerRoles.Add(crRegistered); + + _customerRepository.Insert(fourthUser); + //set default customer name + _genericAttributeService.SaveAttribute(fourthUser, SystemCustomerAttributeNames.FirstName, defaultFourthUserAddress.FirstName); + _genericAttributeService.SaveAttribute(fourthUser, SystemCustomerAttributeNames.LastName, defaultFourthUserAddress.LastName); + + //set customer password + _customerPasswordRepository.Insert(new CustomerPassword + { + Customer = fourthUser, + Password = "123456", + PasswordFormat = PasswordFormat.Clear, + PasswordSalt = string.Empty, + CreatedOnUtc = DateTime.UtcNow + }); + + //fifth user + var fifthUserEmail = "brenda_lindgren@nopCommerce.com"; + var fifthUser = new Customer + { + CustomerGuid = Guid.NewGuid(), + Email = fifthUserEmail, + Username = fifthUserEmail, + Active = true, + CreatedOnUtc = DateTime.UtcNow, + LastActivityDateUtc = DateTime.UtcNow, + RegisteredInStoreId = storeId + }; + var defaultFifthUserAddress = new Address + { + FirstName = "Brenda", + LastName = "Lindgren", + PhoneNumber = "14785236", + Email = fifthUserEmail, + FaxNumber = "", + Company = "Brenda Company", + Address1 = "1249 Tongass Avenue, Suite B", + Address2 = "", + City = "Ketchikan", + StateProvince = _stateProvinceRepository.Table.FirstOrDefault(sp => sp.Name == "Alaska"), + Country = _countryRepository.Table.FirstOrDefault(c => c.ThreeLetterIsoCode == "USA"), + ZipPostalCode = "99901", + CreatedOnUtc = DateTime.UtcNow, + }; + fifthUser.Addresses.Add(defaultFifthUserAddress); + fifthUser.BillingAddress = defaultFifthUserAddress; + fifthUser.ShippingAddress = defaultFifthUserAddress; + + fifthUser.CustomerRoles.Add(crRegistered); + + _customerRepository.Insert(fifthUser); + //set default customer name + _genericAttributeService.SaveAttribute(fifthUser, SystemCustomerAttributeNames.FirstName, defaultFifthUserAddress.FirstName); + _genericAttributeService.SaveAttribute(fifthUser, SystemCustomerAttributeNames.LastName, defaultFifthUserAddress.LastName); + + //set customer password + _customerPasswordRepository.Insert(new CustomerPassword + { + Customer = fifthUser, + Password = "123456", + PasswordFormat = PasswordFormat.Clear, + PasswordSalt = string.Empty, + CreatedOnUtc = DateTime.UtcNow + }); + + //sixth user + var sixthUserEmail = "victoria_victoria@nopCommerce.com"; + var sixthUser = new Customer + { + CustomerGuid = Guid.NewGuid(), + Email = sixthUserEmail, + Username = sixthUserEmail, + Active = true, + CreatedOnUtc = DateTime.UtcNow, + LastActivityDateUtc = DateTime.UtcNow, + RegisteredInStoreId = storeId + }; + var defaultSixthUserAddress = new Address + { + FirstName = "Victoria", + LastName = "Terces", + PhoneNumber = "45612378", + Email = sixthUserEmail, + FaxNumber = "", + Company = "Terces Company", + Address1 = "201 1st Avenue South", + Address2 = "", + City = "Saskatoon", + StateProvince = _stateProvinceRepository.Table.FirstOrDefault(sp => sp.Name == "Saskatchewan"), + Country = _countryRepository.Table.FirstOrDefault(c => c.ThreeLetterIsoCode == "CAN"), + ZipPostalCode = "S7K 1J9", + CreatedOnUtc = DateTime.UtcNow, + }; + sixthUser.Addresses.Add(defaultSixthUserAddress); + sixthUser.BillingAddress = defaultSixthUserAddress; + sixthUser.ShippingAddress = defaultSixthUserAddress; + + sixthUser.CustomerRoles.Add(crRegistered); + + _customerRepository.Insert(sixthUser); + //set default customer name + _genericAttributeService.SaveAttribute(sixthUser, SystemCustomerAttributeNames.FirstName, defaultSixthUserAddress.FirstName); + _genericAttributeService.SaveAttribute(sixthUser, SystemCustomerAttributeNames.LastName, defaultSixthUserAddress.LastName); + + //set customer password + _customerPasswordRepository.Insert(new CustomerPassword + { + Customer = sixthUser, + Password = "123456", + PasswordFormat = PasswordFormat.Clear, + PasswordSalt = string.Empty, + CreatedOnUtc = DateTime.UtcNow + }); + + //search engine (crawler) built-in user + var searchEngineUser = new Customer + { + Email = "builtin@search_engine_record.com", + CustomerGuid = Guid.NewGuid(), + AdminComment = "Built-in system guest record used for requests from search engines.", + Active = true, + IsSystemAccount = true, + SystemName = SystemCustomerNames.SearchEngine, + CreatedOnUtc = DateTime.UtcNow, + LastActivityDateUtc = DateTime.UtcNow, + RegisteredInStoreId = storeId + }; + searchEngineUser.CustomerRoles.Add(crGuests); + _customerRepository.Insert(searchEngineUser); + + + //built-in user for background tasks + var backgroundTaskUser = new Customer + { + Email = "builtin@background-task-record.com", + CustomerGuid = Guid.NewGuid(), + AdminComment = "Built-in system record used for background tasks.", + Active = true, + IsSystemAccount = true, + SystemName = SystemCustomerNames.BackgroundTask, + CreatedOnUtc = DateTime.UtcNow, + LastActivityDateUtc = DateTime.UtcNow, + RegisteredInStoreId = storeId + }; + backgroundTaskUser.CustomerRoles.Add(crGuests); + _customerRepository.Insert(backgroundTaskUser); + } + + protected virtual void InstallOrders() + { + //default store + var defaultStore = _storeRepository.Table.FirstOrDefault(); + if (defaultStore == null) + throw new Exception("No default store could be loaded"); + + //first order + var firstCustomer = _customerRepository.Table.First(c => c.Email.Equals("steve_gates@nopCommerce.com")); + var firstOrder = new Order() + { + StoreId = defaultStore.Id, + OrderGuid = Guid.NewGuid(), + Customer = firstCustomer, + CustomerLanguageId = _languageRepository.Table.First().Id, + CustomerIp = "127.0.0.1", + OrderSubtotalInclTax = 1855M, + OrderSubtotalExclTax = 1855M, + OrderSubTotalDiscountInclTax = decimal.Zero, + OrderSubTotalDiscountExclTax = decimal.Zero, + OrderShippingInclTax = decimal.Zero, + OrderShippingExclTax = decimal.Zero, + OrderShippingNonTaxable = decimal.Zero, + PaymentMethodAdditionalFeeInclTax = decimal.Zero, + PaymentMethodAdditionalFeeExclTax = decimal.Zero, + PaymentMethodAdditionalFeeNonTaxable = decimal.Zero, + TaxRates = "0:0;", + OrderTax = decimal.Zero, + OrderTotal = 1855M, + OrderAmount = 1855M, + OrderAmountIncl = 1855M, + OrderDiscountIncl = decimal.Zero, + EarnedRewardPointsBaseAmountIncl = 1855M, + EarnedRewardPointsBaseAmountExcl = 1855M, + RefundedAmount = decimal.Zero, + OrderDiscount = decimal.Zero, + CheckoutAttributeDescription = string.Empty, + CheckoutAttributesXml = string.Empty, + CustomerCurrencyCode = "USD", + CurrencyRate = 1M, + AffiliateId = 0, + OrderStatus = OrderStatus.Processing, + AllowStoringCreditCardNumber = false, + CardType = string.Empty, + CardName = string.Empty, + CardNumber = string.Empty, + MaskedCreditCardNumber = string.Empty, + CardCvv2 = string.Empty, + CardExpirationMonth = string.Empty, + CardExpirationYear = string.Empty, + PaymentMethodSystemName = "Payments.CheckMoneyOrder", + AuthorizationTransactionId = string.Empty, + AuthorizationTransactionCode = string.Empty, + AuthorizationTransactionResult = string.Empty, + CaptureTransactionId = string.Empty, + CaptureTransactionResult = string.Empty, + SubscriptionTransactionId = string.Empty, + PaymentStatus = PaymentStatus.Paid, + PaidDateUtc = DateTime.UtcNow, + BillingAddress = (Address)firstCustomer.BillingAddress.Clone(), + ShippingAddress = (Address)firstCustomer.ShippingAddress.Clone(), + ShippingStatus = ShippingStatus.NotYetShipped, + ShippingMethod = "Ground", + PickUpInStore = false, + ShippingRateComputationMethodSystemName = "Shipping.FixedOrByWeight", + CustomValuesXml = string.Empty, + VatNumber = string.Empty, + CreatedOnUtc = DateTime.UtcNow, + CustomOrderNumber = string.Empty + }; + _orderRepository.Insert(firstOrder); + firstOrder.CustomOrderNumber = firstOrder.Id.ToString(); + _orderRepository.Update(firstOrder); + + //item Apple iCam + var firstOrderItem1 = new OrderItem() + { + OrderItemGuid = Guid.NewGuid(), + Order = firstOrder, + ProductId = _productRepository.Table.First(p => p.Name.Equals("Apple iCam")).Id, + UnitPriceInclTax = 1300M, + UnitPriceExclTax = 1300M, + PriceInclTax = 1300M, + PriceExclTax = 1300M, + OriginalProductCost = decimal.Zero, + AttributeDescription = string.Empty, + AttributesXml = string.Empty, + Quantity = 1, + DiscountAmountInclTax = decimal.Zero, + DiscountAmountExclTax = decimal.Zero, + DownloadCount = 0, + IsDownloadActivated = false, + LicenseDownloadId = 0, + ItemWeight = null, + RentalStartDateUtc = null, + RentalEndDateUtc = null, + TaxRate = 0 + }; + _orderItemRepository.Insert(firstOrderItem1); + + //item Leica T Mirrorless Digital Camera + var fierstOrderItem2 = new OrderItem() + { + OrderItemGuid = Guid.NewGuid(), + Order = firstOrder, + ProductId = _productRepository.Table.First(p => p.Name.Equals("Leica T Mirrorless Digital Camera")).Id, + UnitPriceInclTax = 530M, + UnitPriceExclTax = 530M, + PriceInclTax = 530M, + PriceExclTax = 530M, + OriginalProductCost = decimal.Zero, + AttributeDescription = string.Empty, + AttributesXml = string.Empty, + Quantity = 1, + DiscountAmountInclTax = decimal.Zero, + DiscountAmountExclTax = decimal.Zero, + DownloadCount = 0, + IsDownloadActivated = false, + LicenseDownloadId = 0, + ItemWeight = null, + RentalStartDateUtc = null, + RentalEndDateUtc = null, + TaxRate = 0 + }; + _orderItemRepository.Insert(fierstOrderItem2); + + //item $25 Virtual Gift Card + var firstOrderItem3 = new OrderItem() + { + OrderItemGuid = Guid.NewGuid(), + Order = firstOrder, + ProductId = _productRepository.Table.First(p => p.Name.Equals("$25 Virtual Gift Card")).Id, + UnitPriceInclTax = 25M, + UnitPriceExclTax = 25M, + PriceInclTax = 25M, + PriceExclTax = 25M, + OriginalProductCost = decimal.Zero, + AttributeDescription = "From: Steve Gates <steve_gates@nopCommerce.com>
    For: Brenda Lindgren <brenda_lindgren@nopCommerce.com>", + AttributesXml = "Brenda Lindgrenbrenda_lindgren@nopCommerce.comSteve Gatessteve_gates@gmail.com", + Quantity = 1, + DiscountAmountInclTax = decimal.Zero, + DiscountAmountExclTax = decimal.Zero, + DownloadCount = 0, + IsDownloadActivated = false, + LicenseDownloadId = 0, + ItemWeight = null, + RentalStartDateUtc = null, + RentalEndDateUtc = null, + TaxRate = 0 + }; + _orderItemRepository.Insert(firstOrderItem3); + + var firstOrderGiftcard = new GiftCard + { + GiftCardType = GiftCardType.Virtual, + PurchasedWithOrderItem = firstOrderItem3, + Amount = 25M, + IsGiftCardActivated = false, + GiftCardCouponCode = string.Empty, + RecipientName = "Brenda Lindgren", + RecipientEmail = "brenda_lindgren@nopCommerce.com", + SenderName = "Steve Gates", + SenderEmail = "steve_gates@nopCommerce.com", + Message = string.Empty, + IsRecipientNotified = false, + CreatedOnUtc = DateTime.UtcNow + }; + _giftCardRepository.Insert(firstOrderGiftcard); + + //order notes + _orderNoteRepository.Insert(new OrderNote() + { + CreatedOnUtc = DateTime.UtcNow, + Note = "Order placed", + Order = firstOrder + }); + _orderNoteRepository.Insert(new OrderNote() + { + CreatedOnUtc = DateTime.UtcNow, + Note = "Order paid", + Order = firstOrder + }); + + + //second order + var secondCustomer = _customerRepository.Table.First(c => c.Email.Equals("arthur_holmes@nopCommerce.com")); + var secondOrder = new Order() + { + StoreId = defaultStore.Id, + OrderGuid = Guid.NewGuid(), + Customer = secondCustomer, + CustomerLanguageId = _languageRepository.Table.First().Id, + CustomerIp = "127.0.0.1", + OrderSubtotalInclTax = 2460M, + OrderSubtotalExclTax = 2460M, + OrderSubTotalDiscountInclTax = decimal.Zero, + OrderSubTotalDiscountExclTax = decimal.Zero, + OrderShippingInclTax = decimal.Zero, + OrderShippingExclTax = decimal.Zero, + OrderShippingNonTaxable = decimal.Zero, + PaymentMethodAdditionalFeeInclTax = decimal.Zero, + PaymentMethodAdditionalFeeExclTax = decimal.Zero, + PaymentMethodAdditionalFeeNonTaxable = decimal.Zero, + TaxRates = "0:0;", + OrderTax = decimal.Zero, + OrderTotal = 2460M, + OrderAmount = 2460M, + OrderAmountIncl = 2460M, + OrderDiscountIncl = decimal.Zero, + EarnedRewardPointsBaseAmountIncl = 2460M, + EarnedRewardPointsBaseAmountExcl = 2460M, + RefundedAmount = decimal.Zero, + OrderDiscount = decimal.Zero, + CheckoutAttributeDescription = string.Empty, + CheckoutAttributesXml = string.Empty, + CustomerCurrencyCode = "USD", + CurrencyRate = 1M, + AffiliateId = 0, + OrderStatus = OrderStatus.Pending, + AllowStoringCreditCardNumber = false, + CardType = string.Empty, + CardName = string.Empty, + CardNumber = string.Empty, + MaskedCreditCardNumber = string.Empty, + CardCvv2 = string.Empty, + CardExpirationMonth = string.Empty, + CardExpirationYear = string.Empty, + PaymentMethodSystemName = "Payments.CheckMoneyOrder", + AuthorizationTransactionId = string.Empty, + AuthorizationTransactionCode = string.Empty, + AuthorizationTransactionResult = string.Empty, + CaptureTransactionId = string.Empty, + CaptureTransactionResult = string.Empty, + SubscriptionTransactionId = string.Empty, + PaymentStatus = PaymentStatus.Pending, + PaidDateUtc = null, + BillingAddress = (Address)secondCustomer.BillingAddress.Clone(), + ShippingAddress = (Address)secondCustomer.ShippingAddress.Clone(), + ShippingStatus = ShippingStatus.NotYetShipped, + ShippingMethod = "Next Day Air", + PickUpInStore = false, + ShippingRateComputationMethodSystemName = "Shipping.FixedOrByWeight", + CustomValuesXml = string.Empty, + VatNumber = string.Empty, + CreatedOnUtc = DateTime.UtcNow, + CustomOrderNumber = string.Empty + }; + _orderRepository.Insert(secondOrder); + secondOrder.CustomOrderNumber = secondOrder.Id.ToString(); + _orderRepository.Update(secondOrder); + + //order notes + _orderNoteRepository.Insert(new OrderNote() + { + CreatedOnUtc = DateTime.UtcNow, + Note = "Order placed", + Order = secondOrder + }); + + //item Vintage Style Engagement Ring + var secondOrderItem1 = new OrderItem() + { + OrderItemGuid = Guid.NewGuid(), + Order = secondOrder, + ProductId = _productRepository.Table.First(p => p.Name.Equals("Vintage Style Engagement Ring")).Id, + UnitPriceInclTax = 2100M, + UnitPriceExclTax = 2100M, + PriceInclTax = 2100M, + PriceExclTax = 2100M, + OriginalProductCost = decimal.Zero, + AttributeDescription = string.Empty, + AttributesXml = string.Empty, + Quantity = 1, + DiscountAmountInclTax = decimal.Zero, + DiscountAmountExclTax = decimal.Zero, + DownloadCount = 0, + IsDownloadActivated = false, + LicenseDownloadId = 0, + ItemWeight = null, + RentalStartDateUtc = null, + RentalEndDateUtc = null, + TaxRate = 0 + }; + _orderItemRepository.Insert(secondOrderItem1); + + //item Flower Girl Bracelet + var secondOrderItem2 = new OrderItem() + { + OrderItemGuid = Guid.NewGuid(), + Order = secondOrder, + ProductId = _productRepository.Table.First(p => p.Name.Equals("Flower Girl Bracelet")).Id, + UnitPriceInclTax = 360M, + UnitPriceExclTax = 360M, + PriceInclTax = 360M, + PriceExclTax = 360M, + OriginalProductCost = decimal.Zero, + AttributeDescription = string.Empty, + AttributesXml = string.Empty, + Quantity = 1, + DiscountAmountInclTax = decimal.Zero, + DiscountAmountExclTax = decimal.Zero, + DownloadCount = 0, + IsDownloadActivated = false, + LicenseDownloadId = 0, + ItemWeight = null, + RentalStartDateUtc = null, + RentalEndDateUtc = null, + TaxRate = 0 + }; + _orderItemRepository.Insert(secondOrderItem2); + + + //third order + var thirdCustomer = _customerRepository.Table.First(c => c.Email.Equals("james_pan@nopCommerce.com")); + var thirdOrder = new Order() + { + StoreId = defaultStore.Id, + OrderGuid = Guid.NewGuid(), + Customer = thirdCustomer, + CustomerLanguageId = _languageRepository.Table.First().Id, + CustomerIp = "127.0.0.1", + OrderSubtotalInclTax = 8.80M, + OrderSubtotalExclTax = 8.80M, + OrderSubTotalDiscountInclTax = decimal.Zero, + OrderSubTotalDiscountExclTax = decimal.Zero, + OrderShippingInclTax = decimal.Zero, + OrderShippingExclTax = decimal.Zero, + OrderShippingNonTaxable = decimal.Zero, + PaymentMethodAdditionalFeeInclTax = decimal.Zero, + PaymentMethodAdditionalFeeExclTax = decimal.Zero, + PaymentMethodAdditionalFeeNonTaxable = decimal.Zero, + TaxRates = "0:0;", + OrderTax = decimal.Zero, + OrderTotal = 8.80M, + OrderAmount = 8.80M, + OrderAmountIncl = 8.80M, + OrderDiscountIncl = decimal.Zero, + EarnedRewardPointsBaseAmountIncl = 8.80M, + EarnedRewardPointsBaseAmountExcl = 8.80M, + RefundedAmount = decimal.Zero, + OrderDiscount = decimal.Zero, + CheckoutAttributeDescription = string.Empty, + CheckoutAttributesXml = string.Empty, + CustomerCurrencyCode = "USD", + CurrencyRate = 1M, + AffiliateId = 0, + OrderStatus = OrderStatus.Pending, + AllowStoringCreditCardNumber = false, + CardType = string.Empty, + CardName = string.Empty, + CardNumber = string.Empty, + MaskedCreditCardNumber = string.Empty, + CardCvv2 = string.Empty, + CardExpirationMonth = string.Empty, + CardExpirationYear = string.Empty, + PaymentMethodSystemName = "Payments.CheckMoneyOrder", + AuthorizationTransactionId = string.Empty, + AuthorizationTransactionCode = string.Empty, + AuthorizationTransactionResult = string.Empty, + CaptureTransactionId = string.Empty, + CaptureTransactionResult = string.Empty, + SubscriptionTransactionId = string.Empty, + PaymentStatus = PaymentStatus.Pending, + PaidDateUtc = null, + BillingAddress = (Address)thirdCustomer.BillingAddress.Clone(), + ShippingAddress = null, + ShippingStatus = ShippingStatus.ShippingNotRequired, + ShippingMethod = string.Empty, + PickUpInStore = false, + ShippingRateComputationMethodSystemName = string.Empty, + CustomValuesXml = string.Empty, + VatNumber = string.Empty, + CreatedOnUtc = DateTime.UtcNow, + CustomOrderNumber = string.Empty + }; + _orderRepository.Insert(thirdOrder); + thirdOrder.CustomOrderNumber = thirdOrder.Id.ToString(); + _orderRepository.Update(thirdOrder); + + //order notes + _orderNoteRepository.Insert(new OrderNote() + { + CreatedOnUtc = DateTime.UtcNow, + Note = "Order placed", + Order = thirdOrder + }); + + //item If You Wait + var thirdOrderItem1 = new OrderItem() + { + OrderItemGuid = Guid.NewGuid(), + Order = thirdOrder, + ProductId = _productRepository.Table.First(p => p.Name.Equals("If You Wait (donation)")).Id, + UnitPriceInclTax = 3M, + UnitPriceExclTax = 3M, + PriceInclTax = 3M, + PriceExclTax = 3M, + OriginalProductCost = decimal.Zero, + AttributeDescription = string.Empty, + AttributesXml = string.Empty, + Quantity = 1, + DiscountAmountInclTax = decimal.Zero, + DiscountAmountExclTax = decimal.Zero, + DownloadCount = 0, + IsDownloadActivated = false, + LicenseDownloadId = 0, + ItemWeight = null, + RentalStartDateUtc = null, + RentalEndDateUtc = null, + TaxRate = 0 + }; + _orderItemRepository.Insert(thirdOrderItem1); + + //item Night Visions + var thirdOrderItem2 = new OrderItem() + { + OrderItemGuid = Guid.NewGuid(), + Order = thirdOrder, + ProductId = _productRepository.Table.First(p => p.Name.Equals("Night Visions")).Id, + UnitPriceInclTax = 2.8M, + UnitPriceExclTax = 2.8M, + PriceInclTax = 2.8M, + PriceExclTax = 2.8M, + OriginalProductCost = decimal.Zero, + AttributeDescription = string.Empty, + AttributesXml = string.Empty, + Quantity = 1, + DiscountAmountInclTax = decimal.Zero, + DiscountAmountExclTax = decimal.Zero, + DownloadCount = 0, + IsDownloadActivated = false, + LicenseDownloadId = 0, + ItemWeight = null, + RentalStartDateUtc = null, + RentalEndDateUtc = null, + TaxRate = 0 + }; + _orderItemRepository.Insert(thirdOrderItem2); + + //item Science & Faith + var thirdOrderItem3 = new OrderItem() + { + OrderItemGuid = Guid.NewGuid(), + Order = thirdOrder, + ProductId = _productRepository.Table.First(p => p.Name.Equals("Science & Faith")).Id, + UnitPriceInclTax = 3M, + UnitPriceExclTax = 3M, + PriceInclTax = 3M, + PriceExclTax = 3M, + OriginalProductCost = decimal.Zero, + AttributeDescription = string.Empty, + AttributesXml = string.Empty, + Quantity = 1, + DiscountAmountInclTax = decimal.Zero, + DiscountAmountExclTax = decimal.Zero, + DownloadCount = 0, + IsDownloadActivated = false, + LicenseDownloadId = 0, + ItemWeight = null, + RentalStartDateUtc = null, + RentalEndDateUtc = null, + TaxRate = 0 + }; + _orderItemRepository.Insert(thirdOrderItem3); + + + //fourth order + var fourthCustomer = _customerRepository.Table.First(c => c.Email.Equals("brenda_lindgren@nopCommerce.com")); + var fourthOrder = new Order() + { + StoreId = defaultStore.Id, + OrderGuid = Guid.NewGuid(), + Customer = fourthCustomer, + CustomerLanguageId = _languageRepository.Table.First().Id, + CustomerIp = "127.0.0.1", + OrderSubtotalInclTax = 102M, + OrderSubtotalExclTax = 102M, + OrderSubTotalDiscountInclTax = decimal.Zero, + OrderSubTotalDiscountExclTax = decimal.Zero, + OrderShippingInclTax = decimal.Zero, + OrderShippingExclTax = decimal.Zero, + OrderShippingNonTaxable = decimal.Zero, + PaymentMethodAdditionalFeeInclTax = decimal.Zero, + PaymentMethodAdditionalFeeExclTax = decimal.Zero, + PaymentMethodAdditionalFeeNonTaxable = decimal.Zero, + TaxRates = "0:0;", + OrderTax = decimal.Zero, + OrderTotal = 102M, + OrderAmount = 102M, + OrderAmountIncl = 102M, + OrderDiscountIncl = decimal.Zero, + EarnedRewardPointsBaseAmountIncl = 102M, + EarnedRewardPointsBaseAmountExcl = 102M, + RefundedAmount = decimal.Zero, + OrderDiscount = decimal.Zero, + CheckoutAttributeDescription = string.Empty, + CheckoutAttributesXml = string.Empty, + CustomerCurrencyCode = "USD", + CurrencyRate = 1M, + AffiliateId = 0, + OrderStatus = OrderStatus.Processing, + AllowStoringCreditCardNumber = false, + CardType = string.Empty, + CardName = string.Empty, + CardNumber = string.Empty, + MaskedCreditCardNumber = string.Empty, + CardCvv2 = string.Empty, + CardExpirationMonth = string.Empty, + CardExpirationYear = string.Empty, + PaymentMethodSystemName = "Payments.CheckMoneyOrder", + AuthorizationTransactionId = string.Empty, + AuthorizationTransactionCode = string.Empty, + AuthorizationTransactionResult = string.Empty, + CaptureTransactionId = string.Empty, + CaptureTransactionResult = string.Empty, + SubscriptionTransactionId = string.Empty, + PaymentStatus = PaymentStatus.Paid, + PaidDateUtc = DateTime.UtcNow, + BillingAddress = (Address)fourthCustomer.BillingAddress.Clone(), + ShippingAddress = (Address)fourthCustomer.ShippingAddress.Clone(), + ShippingStatus = ShippingStatus.Shipped, + ShippingMethod = "Pickup in store", + PickUpInStore = true, + PickupAddress = (Address)fourthCustomer.ShippingAddress.Clone(), + ShippingRateComputationMethodSystemName = "Pickup.PickupInStore", + CustomValuesXml = string.Empty, + VatNumber = string.Empty, + CreatedOnUtc = DateTime.UtcNow, + CustomOrderNumber = string.Empty + }; + _orderRepository.Insert(fourthOrder); + fourthOrder.CustomOrderNumber = fourthOrder.Id.ToString(); + _orderRepository.Update(fourthOrder); + + //order notes + _orderNoteRepository.Insert(new OrderNote() + { + CreatedOnUtc = DateTime.UtcNow, + Note = "Order placed", + Order = fourthOrder + }); + _orderNoteRepository.Insert(new OrderNote() + { + CreatedOnUtc = DateTime.UtcNow, + Note = "Order paid", + Order = fourthOrder + }); + _orderNoteRepository.Insert(new OrderNote() + { + CreatedOnUtc = DateTime.UtcNow, + Note = "Order shipped", + Order = fourthOrder + }); + + //item Pride and Prejudice + var fourthOrderItem1 = new OrderItem() + { + OrderItemGuid = Guid.NewGuid(), + Order = fourthOrder, + ProductId = _productRepository.Table.First(p => p.Name.Equals("Pride and Prejudice")).Id, + UnitPriceInclTax = 24M, + UnitPriceExclTax = 24M, + PriceInclTax = 24M, + PriceExclTax = 24M, + OriginalProductCost = decimal.Zero, + AttributeDescription = string.Empty, + AttributesXml = string.Empty, + Quantity = 1, + DiscountAmountInclTax = decimal.Zero, + DiscountAmountExclTax = decimal.Zero, + DownloadCount = 0, + IsDownloadActivated = false, + LicenseDownloadId = 0, + ItemWeight = null, + RentalStartDateUtc = null, + RentalEndDateUtc = null, + TaxRate = 0 + }; + _orderItemRepository.Insert(fourthOrderItem1); + + //item First Prize Pies + var fourthOrderItem2 = new OrderItem() + { + OrderItemGuid = Guid.NewGuid(), + Order = fourthOrder, + ProductId = _productRepository.Table.First(p => p.Name.Equals("First Prize Pies")).Id, + UnitPriceInclTax = 51M, + UnitPriceExclTax = 51M, + PriceInclTax = 51M, + PriceExclTax = 51M, + OriginalProductCost = decimal.Zero, + AttributeDescription = string.Empty, + AttributesXml = string.Empty, + Quantity = 1, + DiscountAmountInclTax = decimal.Zero, + DiscountAmountExclTax = decimal.Zero, + DownloadCount = 0, + IsDownloadActivated = false, + LicenseDownloadId = 0, + ItemWeight = null, + RentalStartDateUtc = null, + RentalEndDateUtc = null, + TaxRate = 0 + }; + _orderItemRepository.Insert(fourthOrderItem2); + + //item Fahrenheit 451 by Ray Bradbury + var fourthOrderItem3 = new OrderItem() + { + OrderItemGuid = Guid.NewGuid(), + Order = fourthOrder, + ProductId = _productRepository.Table.First(p => p.Name.Equals("Fahrenheit 451 by Ray Bradbury")).Id, + UnitPriceInclTax = 27M, + UnitPriceExclTax = 27M, + PriceInclTax = 27M, + PriceExclTax = 27M, + OriginalProductCost = decimal.Zero, + AttributeDescription = string.Empty, + AttributesXml = string.Empty, + Quantity = 1, + DiscountAmountInclTax = decimal.Zero, + DiscountAmountExclTax = decimal.Zero, + DownloadCount = 0, + IsDownloadActivated = false, + LicenseDownloadId = 0, + ItemWeight = null, + RentalStartDateUtc = null, + RentalEndDateUtc = null, + TaxRate = 0 + }; + _orderItemRepository.Insert(fourthOrderItem3); + + //shipments + //shipment 1 + var fourthOrderShipment1 = new Shipment + { + Order = fourthOrder, + TrackingNumber = string.Empty, + TotalWeight = 4M, + ShippedDateUtc = DateTime.UtcNow, + DeliveryDateUtc = DateTime.UtcNow, + AdminComment = string.Empty, + CreatedOnUtc = DateTime.UtcNow + }; + _shipmentRepository.Insert(fourthOrderShipment1); + + var fourthOrderShipment1Item1 = new ShipmentItem() + { + OrderItemId = fourthOrderItem1.Id, + Quantity = 1, + WarehouseId = 0, + Shipment = fourthOrderShipment1 + }; + _shipmentItemRepository.Insert(fourthOrderShipment1Item1); + + var fourthOrderShipment1Item2 = new ShipmentItem() + { + OrderItemId = fourthOrderItem2.Id, + Quantity = 1, + WarehouseId = 0, + Shipment = fourthOrderShipment1 + }; + _shipmentItemRepository.Insert(fourthOrderShipment1Item2); + + //shipment 2 + var fourthOrderShipment2 = new Shipment + { + Order = fourthOrder, + TrackingNumber = string.Empty, + TotalWeight = 2M, + ShippedDateUtc = DateTime.UtcNow, + DeliveryDateUtc = DateTime.UtcNow, + AdminComment = string.Empty, + CreatedOnUtc = DateTime.UtcNow + }; + _shipmentRepository.Insert(fourthOrderShipment2); + + var fourthOrderShipment2Item1 = new ShipmentItem() + { + OrderItemId = fourthOrderItem3.Id, + Quantity = 1, + WarehouseId = 0, + Shipment = fourthOrderShipment2 + }; + _shipmentItemRepository.Insert(fourthOrderShipment2Item1); + + + + + //fifth order + var fifthCustomer = _customerRepository.Table.First(c => c.Email.Equals("victoria_victoria@nopCommerce.com")); + var fifthOrder = new Order() + { + StoreId = defaultStore.Id, + OrderGuid = Guid.NewGuid(), + Customer = fifthCustomer, + CustomerLanguageId = _languageRepository.Table.First().Id, + CustomerIp = "127.0.0.1", + OrderSubtotalInclTax = 43.50M, + OrderSubtotalExclTax = 43.50M, + OrderSubTotalDiscountInclTax = decimal.Zero, + OrderSubTotalDiscountExclTax = decimal.Zero, + OrderShippingInclTax = decimal.Zero, + OrderShippingExclTax = decimal.Zero, + OrderShippingNonTaxable = decimal.Zero, + PaymentMethodAdditionalFeeInclTax = decimal.Zero, + PaymentMethodAdditionalFeeExclTax = decimal.Zero, + PaymentMethodAdditionalFeeNonTaxable = decimal.Zero, + TaxRates = "0:0;", + OrderTax = decimal.Zero, + OrderTotal = 43.50M, + OrderAmount = 43.50M, + OrderAmountIncl = 43.50M, + OrderDiscountIncl = decimal.Zero, + EarnedRewardPointsBaseAmountIncl = 43.50M, + EarnedRewardPointsBaseAmountExcl = 43.50M, + RefundedAmount = decimal.Zero, + OrderDiscount = decimal.Zero, + CheckoutAttributeDescription = string.Empty, + CheckoutAttributesXml = string.Empty, + CustomerCurrencyCode = "USD", + CurrencyRate = 1M, + AffiliateId = 0, + OrderStatus = OrderStatus.Complete, + AllowStoringCreditCardNumber = false, + CardType = string.Empty, + CardName = string.Empty, + CardNumber = string.Empty, + MaskedCreditCardNumber = string.Empty, + CardCvv2 = string.Empty, + CardExpirationMonth = string.Empty, + CardExpirationYear = string.Empty, + PaymentMethodSystemName = "Payments.CheckMoneyOrder", + AuthorizationTransactionId = string.Empty, + AuthorizationTransactionCode = string.Empty, + AuthorizationTransactionResult = string.Empty, + CaptureTransactionId = string.Empty, + CaptureTransactionResult = string.Empty, + SubscriptionTransactionId = string.Empty, + PaymentStatus = PaymentStatus.Paid, + PaidDateUtc = DateTime.UtcNow, + BillingAddress = (Address)fifthCustomer.BillingAddress.Clone(), + ShippingAddress = (Address)fifthCustomer.ShippingAddress.Clone(), + ShippingStatus = ShippingStatus.Delivered, + ShippingMethod = "Ground", + PickUpInStore = false, + ShippingRateComputationMethodSystemName = "Shipping.FixedOrByWeight", + CustomValuesXml = string.Empty, + VatNumber = string.Empty, + CreatedOnUtc = DateTime.UtcNow, + CustomOrderNumber = string.Empty + }; + _orderRepository.Insert(fifthOrder); + fifthOrder.CustomOrderNumber = fifthOrder.Id.ToString(); + _orderRepository.Update(fifthOrder); + + //order notes + _orderNoteRepository.Insert(new OrderNote() + { + CreatedOnUtc = DateTime.UtcNow, + Note = "Order placed", + Order = fifthOrder + }); + _orderNoteRepository.Insert(new OrderNote() + { + CreatedOnUtc = DateTime.UtcNow, + Note = "Order paid", + Order = fifthOrder + }); + _orderNoteRepository.Insert(new OrderNote() + { + CreatedOnUtc = DateTime.UtcNow, + Note = "Order shipped", + Order = fifthOrder + }); + _orderNoteRepository.Insert(new OrderNote() + { + CreatedOnUtc = DateTime.UtcNow, + Note = "Order delivered", + Order = fifthOrder + }); + + //item Levi's 511 Jeans + var fifthOrderItem1 = new OrderItem() + { + OrderItemGuid = Guid.NewGuid(), + Order = fifthOrder, + ProductId = _productRepository.Table.First(p => p.Name.Equals("Levi's 511 Jeans")).Id, + UnitPriceInclTax = 43.50M, + UnitPriceExclTax = 43.50M, + PriceInclTax = 43.50M, + PriceExclTax = 43.50M, + OriginalProductCost = decimal.Zero, + AttributeDescription = string.Empty, + AttributesXml = string.Empty, + Quantity = 1, + DiscountAmountInclTax = decimal.Zero, + DiscountAmountExclTax = decimal.Zero, + DownloadCount = 0, + IsDownloadActivated = false, + LicenseDownloadId = 0, + ItemWeight = null, + RentalStartDateUtc = null, + RentalEndDateUtc = null, + TaxRate = 0 + }; + _orderItemRepository.Insert(fifthOrderItem1); + + //shipment 1 + var fifthOrderShipment1 = new Shipment + { + Order = fifthOrder, + TrackingNumber = string.Empty, + TotalWeight = 2M, + ShippedDateUtc = DateTime.UtcNow, + DeliveryDateUtc = DateTime.UtcNow, + AdminComment = string.Empty, + CreatedOnUtc = DateTime.UtcNow + }; + _shipmentRepository.Insert(fifthOrderShipment1); + + var fifthOrderShipment1Item1 = new ShipmentItem() + { + OrderItemId = fifthOrderItem1.Id, + Quantity = 1, + WarehouseId = 0, + Shipment = fifthOrderShipment1 + }; + _shipmentItemRepository.Insert(fifthOrderShipment1Item1); + } + + protected virtual void InstallActivityLog(string defaultUserEmail) + { + //default customer/user + var defaultCustomer = _customerRepository.Table.FirstOrDefault(x => x.Email == defaultUserEmail); + if (defaultCustomer == null) + throw new Exception("Cannot load default customer"); + + _activityLogRepository.Insert(new ActivityLog() + { + ActivityLogType = _activityLogTypeRepository.Table.First(alt => alt.SystemKeyword.Equals("EditCategory")), + Comment = "Edited a category ('Computers')", + CreatedOnUtc = DateTime.UtcNow, + Customer = defaultCustomer, + IpAddress = "127.0.0.1" + }); + _activityLogRepository.Insert(new ActivityLog() + { + ActivityLogType = _activityLogTypeRepository.Table.First(alt => alt.SystemKeyword.Equals("EditDiscount")), + Comment = "Edited a discount ('Sample discount with coupon code')", + CreatedOnUtc = DateTime.UtcNow, + Customer = defaultCustomer, + IpAddress = "127.0.0.1" + }); + _activityLogRepository.Insert(new ActivityLog() + { + ActivityLogType = _activityLogTypeRepository.Table.First(alt => alt.SystemKeyword.Equals("EditSpecAttribute")), + Comment = "Edited a specification attribute ('CPU Type')", + CreatedOnUtc = DateTime.UtcNow, + Customer = defaultCustomer, + IpAddress = "127.0.0.1" + }); + _activityLogRepository.Insert(new ActivityLog() + { + ActivityLogType = _activityLogTypeRepository.Table.First(alt => alt.SystemKeyword.Equals("AddNewProductAttribute")), + Comment = "Added a new product attribute ('Some attribute')", + CreatedOnUtc = DateTime.UtcNow, + Customer = defaultCustomer, + IpAddress = "127.0.0.1" + }); + _activityLogRepository.Insert(new ActivityLog() + { + ActivityLogType = _activityLogTypeRepository.Table.First(alt => alt.SystemKeyword.Equals("DeleteGiftCard")), + Comment = "Deleted a gift card ('bdbbc0ef-be57')", + CreatedOnUtc = DateTime.UtcNow, + Customer = defaultCustomer, + IpAddress = "127.0.0.1" + }); + } + + protected virtual void InstallSearchTerms() + { + //default store + var defaultStore = _storeRepository.Table.FirstOrDefault(); + if (defaultStore == null) + throw new Exception("No default store could be loaded"); + + _searchTermRepository.Insert(new SearchTerm() + { + Count = 34, + Keyword = "computer", + StoreId = defaultStore.Id + }); + _searchTermRepository.Insert(new SearchTerm() + { + Count = 30, + Keyword = "camera", + StoreId = defaultStore.Id + }); + _searchTermRepository.Insert(new SearchTerm() + { + Count = 27, + Keyword = "jewelry", + StoreId = defaultStore.Id + }); + _searchTermRepository.Insert(new SearchTerm() + { + Count = 26, + Keyword = "shoes", + StoreId = defaultStore.Id + }); + _searchTermRepository.Insert(new SearchTerm() + { + Count = 19, + Keyword = "jeans", + StoreId = defaultStore.Id + }); + _searchTermRepository.Insert(new SearchTerm() + { + Count = 10, + Keyword = "gift", + StoreId = defaultStore.Id + }); + } + + protected virtual void InstallEmailAccounts() + { + var emailAccounts = new List + { + new EmailAccount + { + Email = "test@mail.com", + DisplayName = "Store name", + Host = "smtp.mail.com", + Port = 25, + Username = "123", + Password = "123", + EnableSsl = false, + UseDefaultCredentials = false + }, + }; + _emailAccountRepository.Insert(emailAccounts); + } + + protected virtual void InstallMessageTemplates() + { + var eaGeneral = _emailAccountRepository.Table.FirstOrDefault(); + if (eaGeneral == null) + throw new Exception("Default email account cannot be loaded"); + + var messageTemplates = new List + { + new MessageTemplate + { + Name = MessageTemplateSystemNames.BlogCommentNotification, + Subject = "%Store.Name%. New blog comment.", + Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}A new blog comment has been created for blog post \"%BlogComment.BlogPostTitle%\".{0}

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.BackInStockNotification, + Subject = "%Store.Name%. Back in stock notification", + Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}Hello %Customer.FullName%,{0}
    {0}Product %BackInStockSubscription.ProductName% is in stock.{0}

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.CustomerEmailValidationMessage, + Subject = "%Store.Name%. Email validation", + Body = string.Format("%Store.Name%{0}
    {0}
    {0}To activate your account click here.{0}
    {0}
    {0}%Store.Name%{0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.CustomerEmailRevalidationMessage, + Subject = "%Store.Name%. Email validation", + Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}Hello %Customer.FullName%!{0}
    {0}To validate your new email address click here.{0}
    {0}
    {0}%Store.Name%{0}

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.PrivateMessageNotification, + Subject = "%Store.Name%. You have received a new private message", + Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}You have received a new private message.{0}

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.CustomerPasswordRecoveryMessage, + Subject = "%Store.Name%. Password recovery", + Body = string.Format("%Store.Name%{0}
    {0}
    {0}To change your password click here.{0}
    {0}
    {0}%Store.Name%{0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.CustomerWelcomeMessage, + Subject = "Welcome to %Store.Name%", + Body = string.Format("We welcome you to %Store.Name%.{0}
    {0}
    {0}You can now take part in the various services we have to offer you. Some of these services include:{0}
    {0}
    {0}Permanent Cart - Any products added to your online cart remain there until you remove them, or check them out.{0}
    {0}Address Book - We can now deliver your products to another address other than yours! This is perfect to send birthday gifts direct to the birthday-person themselves.{0}
    {0}Order History - View your history of purchases that you have made with us.{0}
    {0}Products Reviews - Share your opinions on products with our other customers.{0}
    {0}
    {0}For help with any of our online services, please email the store-owner: %Store.Email%.{0}
    {0}
    {0}Note: This email address was provided on our registration page. If you own the email and did not register on our site, please send an email to %Store.Email%.{0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.NewForumPostMessage, + Subject = "%Store.Name%. New Post Notification.", + Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}A new post has been created in the topic \"%Forums.TopicName%\" at \"%Forums.ForumName%\" forum.{0}
    {0}
    {0}Click here for more info.{0}
    {0}
    {0}Post author: %Forums.PostAuthor%{0}
    {0}Post body: %Forums.PostBody%{0}

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.NewForumTopicMessage, + Subject = "%Store.Name%. New Topic Notification.", + Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}A new topic \"%Forums.TopicName%\" has been created at \"%Forums.ForumName%\" forum.{0}
    {0}
    {0}Click here for more info.{0}

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.GiftCardNotification, + Subject = "%GiftCard.SenderName% has sent you a gift card for %Store.Name%", + Body = string.Format("

    {0}You have received a gift card for %Store.Name%{0}

    {0}

    {0}Dear %GiftCard.RecipientName%,{0}
    {0}
    {0}%GiftCard.SenderName% (%GiftCard.SenderEmail%) has sent you a %GiftCard.Amount% gift cart for %Store.Name%{0}

    {0}

    {0}You gift card code is %GiftCard.CouponCode%{0}

    {0}

    {0}%GiftCard.Message%{0}

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.CustomerRegisteredNotification, + Subject = "%Store.Name%. New customer registration", + Body = string.Format("

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

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.NewReturnRequestStoreOwnerNotification, + Subject = "%Store.Name%. New return request.", + Body = string.Format("

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

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.NewReturnRequestCustomerNotification, + Subject = "%Store.Name%. New return request.", + Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}Hello %Customer.FullName%!{0}
    {0}You have just submitted a new return request. Details are below:{0}
    {0}Request ID: %ReturnRequest.CustomNumber%{0}
    {0}Product: %ReturnRequest.Product.Quantity% x Product: %ReturnRequest.Product.Name%{0}
    {0}Reason for return: %ReturnRequest.Reason%{0}
    {0}Requested action: %ReturnRequest.RequestedAction%{0}
    {0}Customer comments:{0}
    {0}%ReturnRequest.CustomerComment%{0}

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.NewsCommentNotification, + Subject = "%Store.Name%. New news comment.", + Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}A new news comment has been created for news \"%NewsComment.NewsTitle%\".{0}

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.NewsletterSubscriptionActivationMessage, + Subject = "%Store.Name%. Subscription activation message.", + Body = string.Format("

    {0}Click here to confirm your subscription to our list.{0}

    {0}

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

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.NewsletterSubscriptionDeactivationMessage, + Subject = "%Store.Name%. Subscription deactivation message.", + Body = string.Format("

    {0}Click here to unsubscribe from our newsletter.{0}

    {0}

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

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.NewVatSubmittedStoreOwnerNotification, + Subject = "%Store.Name%. New VAT number is submitted.", + Body = string.Format("

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

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.OrderCancelledCustomerNotification, + Subject = "%Store.Name%. Your order cancelled", + Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}Hello %Order.CustomerFullName%,{0}
    {0}Your order has been cancelled. Below is the summary of the order.{0}
    {0}
    {0}Order Number: %Order.OrderNumber%{0}
    {0}Order Details: %Order.OrderURLForCustomer%{0}
    {0}Date Ordered: %Order.CreatedOn%{0}
    {0}
    {0}
    {0}
    {0}Billing Address{0}
    {0}%Order.BillingFirstName% %Order.BillingLastName%{0}
    {0}%Order.BillingAddress1%{0}
    {0}%Order.BillingCity% %Order.BillingZipPostalCode%{0}
    {0}%Order.BillingStateProvince% %Order.BillingCountry%{0}
    {0}
    {0}
    {0}
    {0}%if (%Order.Shippable%) Shipping Address{0}
    {0}%Order.ShippingFirstName% %Order.ShippingLastName%{0}
    {0}%Order.ShippingAddress1%{0}
    {0}%Order.ShippingCity% %Order.ShippingZipPostalCode%{0}
    {0}%Order.ShippingStateProvince% %Order.ShippingCountry%{0}
    {0}
    {0}Shipping Method: %Order.ShippingMethod%{0}
    {0}
    {0} endif% %Order.Product(s)%{0}

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.OrderCompletedCustomerNotification, + Subject = "%Store.Name%. Your order completed", + Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}Hello %Order.CustomerFullName%,{0}
    {0}Your order has been completed. Below is the summary of the order.{0}
    {0}
    {0}Order Number: %Order.OrderNumber%{0}
    {0}Order Details: %Order.OrderURLForCustomer%{0}
    {0}Date Ordered: %Order.CreatedOn%{0}
    {0}
    {0}
    {0}
    {0}Billing Address{0}
    {0}%Order.BillingFirstName% %Order.BillingLastName%{0}
    {0}%Order.BillingAddress1%{0}
    {0}%Order.BillingCity% %Order.BillingZipPostalCode%{0}
    {0}%Order.BillingStateProvince% %Order.BillingCountry%{0}
    {0}
    {0}
    {0}
    {0}%if (%Order.Shippable%) Shipping Address{0}
    {0}%Order.ShippingFirstName% %Order.ShippingLastName%{0}
    {0}%Order.ShippingAddress1%{0}
    {0}%Order.ShippingCity% %Order.ShippingZipPostalCode%{0}
    {0}%Order.ShippingStateProvince% %Order.ShippingCountry%{0}
    {0}
    {0}Shipping Method: %Order.ShippingMethod%{0}
    {0}
    {0} endif% %Order.Product(s)%{0}

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.ShipmentDeliveredCustomerNotification, + Subject = "Your order from %Store.Name% has been delivered.", + Body = string.Format("

    {0} %Store.Name%{0}
    {0}
    {0}Hello %Order.CustomerFullName%,{0}
    {0}Good news! You order has been delivered.{0}
    {0}Order Number: %Order.OrderNumber%{0}
    {0}Order Details: %Order.OrderURLForCustomer%{0}
    {0}Date Ordered: %Order.CreatedOn%{0}
    {0}
    {0}
    {0}
    {0}Billing Address{0}
    {0}%Order.BillingFirstName% %Order.BillingLastName%{0}
    {0}%Order.BillingAddress1%{0}
    {0}%Order.BillingCity% %Order.BillingZipPostalCode%{0}
    {0}%Order.BillingStateProvince% %Order.BillingCountry%{0}
    {0}
    {0}
    {0}
    {0}%if (%Order.Shippable%) Shipping Address{0}
    {0}%Order.ShippingFirstName% %Order.ShippingLastName%{0}
    {0}%Order.ShippingAddress1%{0}
    {0}%Order.ShippingCity% %Order.ShippingZipPostalCode%{0}
    {0}%Order.ShippingStateProvince% %Order.ShippingCountry%{0}
    {0}
    {0}Shipping Method: %Order.ShippingMethod%{0}
    {0}
    {0} endif% Delivered Products:{0}
    {0}
    {0}%Shipment.Product(s)%{0}

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.OrderPlacedCustomerNotification, + Subject = "Order receipt from %Store.Name%.", + Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}Hello %Order.CustomerFullName%,{0}
    {0}Thanks for buying from %Store.Name%. Below is the summary of the order.{0}
    {0}
    {0}Order Number: %Order.OrderNumber%{0}
    {0}Order Details: %Order.OrderURLForCustomer%{0}
    {0}Date Ordered: %Order.CreatedOn%{0}
    {0}
    {0}
    {0}
    {0}Billing Address{0}
    {0}%Order.BillingFirstName% %Order.BillingLastName%{0}
    {0}%Order.BillingAddress1%{0}
    {0}%Order.BillingCity% %Order.BillingZipPostalCode%{0}
    {0}%Order.BillingStateProvince% %Order.BillingCountry%{0}
    {0}
    {0}
    {0}
    {0}%if (%Order.Shippable%) Shipping Address{0}
    {0}%Order.ShippingFirstName% %Order.ShippingLastName%{0}
    {0}%Order.ShippingAddress1%{0}
    {0}%Order.ShippingCity% %Order.ShippingZipPostalCode%{0}
    {0}%Order.ShippingStateProvince% %Order.ShippingCountry%{0}
    {0}
    {0}Shipping Method: %Order.ShippingMethod%{0}
    {0}
    {0} endif% %Order.Product(s)%{0}

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.OrderPlacedStoreOwnerNotification, + Subject = "%Store.Name%. Purchase Receipt for Order #%Order.OrderNumber%", + Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}%Order.CustomerFullName% (%Order.CustomerEmail%) has just placed an order from your store. Below is the summary of the order.{0}
    {0}
    {0}Order Number: %Order.OrderNumber%{0}
    {0}Date Ordered: %Order.CreatedOn%{0}
    {0}
    {0}
    {0}
    {0}Billing Address{0}
    {0}%Order.BillingFirstName% %Order.BillingLastName%{0}
    {0}%Order.BillingAddress1%{0}
    {0}%Order.BillingCity% %Order.BillingZipPostalCode%{0}
    {0}%Order.BillingStateProvince% %Order.BillingCountry%{0}
    {0}
    {0}
    {0}
    {0}%if (%Order.Shippable%) Shipping Address{0}
    {0}%Order.ShippingFirstName% %Order.ShippingLastName%{0}
    {0}%Order.ShippingAddress1%{0}
    {0}%Order.ShippingCity% %Order.ShippingZipPostalCode%{0}
    {0}%Order.ShippingStateProvince% %Order.ShippingCountry%{0}
    {0}
    {0}Shipping Method: %Order.ShippingMethod%{0}
    {0}
    {0} endif% %Order.Product(s)%{0}

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.ShipmentSentCustomerNotification, + Subject = "Your order from %Store.Name% has been shipped.", + Body = string.Format("

    {0} %Store.Name%{0}
    {0}
    {0}Hello %Order.CustomerFullName%!,{0}
    {0}Good news! You order has been shipped.{0}
    {0}Order Number: %Order.OrderNumber%{0}
    {0}Order Details: %Order.OrderURLForCustomer%{0}
    {0}Date Ordered: %Order.CreatedOn%{0}
    {0}
    {0}
    {0}
    {0}Billing Address{0}
    {0}%Order.BillingFirstName% %Order.BillingLastName%{0}
    {0}%Order.BillingAddress1%{0}
    {0}%Order.BillingCity% %Order.BillingZipPostalCode%{0}
    {0}%Order.BillingStateProvince% %Order.BillingCountry%{0}
    {0}
    {0}
    {0}
    {0}%if (%Order.Shippable%) Shipping Address{0}
    {0}%Order.ShippingFirstName% %Order.ShippingLastName%{0}
    {0}%Order.ShippingAddress1%{0}
    {0}%Order.ShippingCity% %Order.ShippingZipPostalCode%{0}
    {0}%Order.ShippingStateProvince% %Order.ShippingCountry%{0}
    {0}
    {0}Shipping Method: %Order.ShippingMethod%{0}
    {0}
    {0} endif% Shipped Products:{0}
    {0}
    {0}%Shipment.Product(s)%{0}

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.ProductReviewNotification, + Subject = "%Store.Name%. New product review.", + Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}A new product review has been written for product \"%ProductReview.ProductName%\".{0}

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.QuantityBelowStoreOwnerNotification, + Subject = "%Store.Name%. Quantity below notification. %Product.Name%", + Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}%Product.Name% (ID: %Product.ID%) low quantity.{0}
    {0}
    {0}Quantity: %Product.StockQuantity%{0}
    {0}

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.QuantityBelowAttributeCombinationStoreOwnerNotification, + Subject = "%Store.Name%. Quantity below notification. %Product.Name%", + Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}%Product.Name% (ID: %Product.ID%) low quantity.{0}
    {0}%AttributeCombination.Formatted%{0}
    {0}Quantity: %AttributeCombination.StockQuantity%{0}
    {0}

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.ReturnRequestStatusChangedCustomerNotification, + Subject = "%Store.Name%. Return request status was changed.", + Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}Hello %Customer.FullName%,{0}
    {0}Your return request #%ReturnRequest.CustomNumber% status has been changed.{0}

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.EmailAFriendMessage, + Subject = "%Store.Name%. Referred Item", + Body = string.Format("

    {0} %Store.Name%{0}
    {0}
    {0}%EmailAFriend.Email% was shopping on %Store.Name% and wanted to share the following item with you.{0}
    {0}
    {0}%Product.Name%{0}
    {0}%Product.ShortDescription%{0}
    {0}
    {0}For more info click here{0}
    {0}
    {0}
    {0}%EmailAFriend.PersonalMessage%{0}
    {0}
    {0}%Store.Name%{0}

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.WishlistToFriendMessage, + Subject = "%Store.Name%. Wishlist", + Body = string.Format("

    {0} %Store.Name%{0}
    {0}
    {0}%Wishlist.Email% was shopping on %Store.Name% and wanted to share a wishlist with you.{0}
    {0}
    {0}
    {0}For more info click here{0}
    {0}
    {0}
    {0}%Wishlist.PersonalMessage%{0}
    {0}
    {0}%Store.Name%{0}

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.NewOrderNoteAddedCustomerNotification, + Subject = "%Store.Name%. New order note has been added", + Body = string.Format("

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

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.RecurringPaymentCancelledStoreOwnerNotification, + Subject = "%Store.Name%. Recurring payment cancelled", + Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}%if (%RecurringPayment.CancelAfterFailedPayment%) The last payment for the recurring payment ID=%RecurringPayment.ID% failed, so it was cancelled. endif% %if (!%RecurringPayment.CancelAfterFailedPayment%) %Customer.FullName% (%Customer.Email%) has just cancelled a recurring payment ID=%RecurringPayment.ID%. endif%{0}

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.RecurringPaymentCancelledCustomerNotification, + Subject = "%Store.Name%. Recurring payment cancelled", + Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}Hello %Customer.FullName%,{0}
    {0}%if (%RecurringPayment.CancelAfterFailedPayment%) It appears your credit card didn't go through for this recurring payment (%Order.OrderURLForCustomer%){0}
    {0}So your subscription has been canceled. endif% %if (!%RecurringPayment.CancelAfterFailedPayment%) The recurring payment ID=%RecurringPayment.ID% was cancelled. endif%{0}

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.RecurringPaymentFailedCustomerNotification, + Subject = "%Store.Name%. Last recurring payment failed", + Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}Hello %Customer.FullName%,{0}
    {0}It appears your credit card didn't go through for this recurring payment (%Order.OrderURLForCustomer%){0}
    %if (%RecurringPayment.RecurringPaymentType% == \"Manual\") {0}You can recharge balance and manually retry payment or cancel it on the order history page. endif% %if (%RecurringPayment.RecurringPaymentType% == \"Automatic\") {0}You can recharge balance and wait, we will try to make the payment again, or you can cancel it on the order history page. endif%{0}

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.OrderPlacedVendorNotification, + Subject = "%Store.Name%. Order placed", + Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}%Customer.FullName% (%Customer.Email%) has just placed an order.{0}
    {0}
    {0}Order Number: %Order.OrderNumber%{0}
    {0}Date Ordered: %Order.CreatedOn%{0}
    {0}
    {0}%Order.Product(s)%{0}

    {0}", Environment.NewLine), + //this template is disabled by default + IsActive = false, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.OrderRefundedCustomerNotification, + Subject = "%Store.Name%. Order #%Order.OrderNumber% refunded", + Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}Hello %Order.CustomerFullName%,{0}
    {0}Thanks for buying from %Store.Name%. Order #%Order.OrderNumber% has been has been refunded. Please allow 7-14 days for the refund to be reflected in your account.{0}
    {0}
    {0}Amount refunded: %Order.AmountRefunded%{0}
    {0}
    {0}Below is the summary of the order.{0}
    {0}
    {0}Order Number: %Order.OrderNumber%{0}
    {0}Order Details: %Order.OrderURLForCustomer%{0}
    {0}Date Ordered: %Order.CreatedOn%{0}
    {0}
    {0}
    {0}
    {0}Billing Address{0}
    {0}%Order.BillingFirstName% %Order.BillingLastName%{0}
    {0}%Order.BillingAddress1%{0}
    {0}%Order.BillingCity% %Order.BillingZipPostalCode%{0}
    {0}%Order.BillingStateProvince% %Order.BillingCountry%{0}
    {0}
    {0}
    {0}
    {0}%if (%Order.Shippable%) Shipping Address{0}
    {0}%Order.ShippingFirstName% %Order.ShippingLastName%{0}
    {0}%Order.ShippingAddress1%{0}
    {0}%Order.ShippingCity% %Order.ShippingZipPostalCode%{0}
    {0}%Order.ShippingStateProvince% %Order.ShippingCountry%{0}
    {0}
    Shipping Method: %Order.ShippingMethod%{0}
    {0}
    {0} endif% %Order.Product(s)%{0}

    {0}", Environment.NewLine), + //this template is disabled by default + IsActive = false, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.OrderRefundedStoreOwnerNotification, + Subject = "%Store.Name%. Order #%Order.OrderNumber% refunded", + Body = string.Format("%Store.Name%. Order #%Order.OrderNumber% refunded', N'{0}

    {0}%Store.Name%{0}
    {0}
    {0}Order #%Order.OrderNumber% has been just refunded{0}
    {0}
    {0}Amount refunded: %Order.AmountRefunded%{0}
    {0}
    {0}Date Ordered: %Order.CreatedOn%{0}

    {0}", Environment.NewLine), + //this template is disabled by default + IsActive = false, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.OrderPaidStoreOwnerNotification, + Subject = "%Store.Name%. Order #%Order.OrderNumber% paid", + Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}Order #%Order.OrderNumber% has been just paid{0}
    {0}Date Ordered: %Order.CreatedOn%{0}

    {0}", Environment.NewLine), + //this template is disabled by default + IsActive = false, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.OrderPaidCustomerNotification, + Subject = "%Store.Name%. Order #%Order.OrderNumber% paid", + Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}Hello %Order.CustomerFullName%,{0}
    {0}Thanks for buying from %Store.Name%. Order #%Order.OrderNumber% has been just paid. Below is the summary of the order.{0}
    {0}
    {0}Order Number: %Order.OrderNumber%{0}
    {0}Order Details: %Order.OrderURLForCustomer%{0}
    {0}Date Ordered: %Order.CreatedOn%{0}
    {0}
    {0}
    {0}
    {0}Billing Address{0}
    {0}%Order.BillingFirstName% %Order.BillingLastName%{0}
    {0}%Order.BillingAddress1%{0}
    {0}%Order.BillingCity% %Order.BillingZipPostalCode%{0}
    {0}%Order.BillingStateProvince% %Order.BillingCountry%{0}
    {0}
    {0}
    {0}
    {0}%if (%Order.Shippable%) Shipping Address{0}
    {0}%Order.ShippingFirstName% %Order.ShippingLastName%{0}
    {0}%Order.ShippingAddress1%{0}
    {0}%Order.ShippingCity% %Order.ShippingZipPostalCode%{0}
    {0}%Order.ShippingStateProvince% %Order.ShippingCountry%{0}
    {0}
    {0}Shipping Method: %Order.ShippingMethod%{0}
    {0}
    {0} endif% %Order.Product(s)%{0}

    {0}", Environment.NewLine), + //this template is disabled by default + IsActive = false, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.OrderPaidVendorNotification, + Subject = "%Store.Name%. Order #%Order.OrderNumber% paid", + Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}Order #%Order.OrderNumber% has been just paid.{0}
    {0}
    {0}Order Number: %Order.OrderNumber%{0}
    {0}Date Ordered: %Order.CreatedOn%{0}
    {0}
    {0}%Order.Product(s)%{0}

    {0}", Environment.NewLine), + //this template is disabled by default + IsActive = false, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.NewVendorAccountApplyStoreOwnerNotification, + Subject = "%Store.Name%. New vendor account submitted.", + Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}%Customer.FullName% (%Customer.Email%) has just submitted for a vendor account. Details are below:{0}
    {0}Vendor name: %Vendor.Name%{0}
    {0}Vendor email: %Vendor.Email%{0}
    {0}
    {0}You can activate it in admin area.{0}

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.VendorInformationChangeNotification, + Subject = "%Store.Name%. Vendor information change.", + Body = string.Format("

    {0}%Store.Name%{0}
    {0}
    {0}Vendor %Vendor.Name% (%Vendor.Email%) has just changed information about itself.{0}

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.ContactUsMessage, + Subject = "%Store.Name%. Contact us", + Body = string.Format("

    {0}%ContactUs.Body%{0}

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + }, + new MessageTemplate + { + Name = MessageTemplateSystemNames.ContactVendorMessage, + Subject = "%Store.Name%. Contact us", + Body = string.Format("

    {0}%ContactUs.Body%{0}

    {0}", Environment.NewLine), + IsActive = true, + EmailAccountId = eaGeneral.Id, + } + }; + _messageTemplateRepository.Insert(messageTemplates); + } + + protected virtual void InstallTopics() + { + var defaultTopicTemplate = + _topicTemplateRepository.Table.FirstOrDefault(tt => tt.Name == "Default template"); + if (defaultTopicTemplate == null) + throw new Exception("Topic template cannot be loaded"); + + var topics = new List + { + new Topic + { + SystemName = "AboutUs", + IncludeInSitemap = false, + IsPasswordProtected = false, + IncludeInFooterColumn1 = true, + DisplayOrder = 20, + Published = true, + Title = "About us", + Body = "

    Put your "About Us" information here. You can edit this in the admin site.

    ", + TopicTemplateId = defaultTopicTemplate.Id + }, + new Topic + { + SystemName = "CheckoutAsGuestOrRegister", + IncludeInSitemap = false, + IsPasswordProtected = false, + DisplayOrder = 1, + Published = true, + Title = "", + Body = "

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

    • Fast and easy check out
    • Easy access to your order history and status
    ", + TopicTemplateId = defaultTopicTemplate.Id + }, + new Topic + { + SystemName = "ConditionsOfUse", + IncludeInSitemap = false, + IsPasswordProtected = false, + IncludeInFooterColumn1 = true, + DisplayOrder = 15, + Published = true, + Title = "Conditions of Use", + Body = "

    Put your conditions of use information here. You can edit this in the admin site.

    ", + TopicTemplateId = defaultTopicTemplate.Id + }, + new Topic + { + SystemName = "ContactUs", + IncludeInSitemap = false, + IsPasswordProtected = false, + DisplayOrder = 1, + Published = true, + Title = "", + Body = "

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

    ", + TopicTemplateId = defaultTopicTemplate.Id + }, + new Topic + { + SystemName = "ForumWelcomeMessage", + IncludeInSitemap = false, + IsPasswordProtected = false, + DisplayOrder = 1, + Published = true, + Title = "Forums", + Body = "

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

    ", + TopicTemplateId = defaultTopicTemplate.Id + }, + new Topic + { + SystemName = "HomePageText", + IncludeInSitemap = false, + IsPasswordProtected = false, + DisplayOrder = 1, + Published = true, + Title = "Welcome to our store", + Body = "

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

    If you have questions, see the Documentation, or post in the Forums at nopCommerce.com

    ", + TopicTemplateId = defaultTopicTemplate.Id + }, + new Topic + { + SystemName = "LoginRegistrationInfo", + IncludeInSitemap = false, + IsPasswordProtected = false, + DisplayOrder = 1, + Published = true, + Title = "About login / registration", + Body = "

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

    ", + TopicTemplateId = defaultTopicTemplate.Id + }, + new Topic + { + SystemName = "PrivacyInfo", + IncludeInSitemap = false, + IsPasswordProtected = false, + IncludeInFooterColumn1 = true, + DisplayOrder = 10, + Published = true, + Title = "Privacy notice", + Body = "

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

    ", + TopicTemplateId = defaultTopicTemplate.Id + }, + new Topic + { + SystemName = "PageNotFound", + IncludeInSitemap = false, + IsPasswordProtected = false, + DisplayOrder = 1, + Published = true, + Title = "", + Body = "

    The page you requested was not found, and we have a fine guess why.

    • If you typed the URL directly, please make sure the spelling is correct.
    • The page no longer exists. In this case, we profusely apologize for the inconvenience and for any damage this may cause.
    ", + TopicTemplateId = defaultTopicTemplate.Id + }, + new Topic + { + SystemName = "ShippingInfo", + IncludeInSitemap = false, + IsPasswordProtected = false, + IncludeInFooterColumn1 = true, + DisplayOrder = 5, + Published = true, + Title = "Shipping & returns", + Body = "

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

    ", + TopicTemplateId = defaultTopicTemplate.Id + }, + new Topic + { + SystemName = "ApplyVendor", + IncludeInSitemap = false, + IsPasswordProtected = false, + DisplayOrder = 1, + Published = true, + Title = "", + Body = "

    Put your apply vendor instructions here. You can edit this in the admin site.

    ", + TopicTemplateId = defaultTopicTemplate.Id + }, + }; + _topicRepository.Insert(topics); + + + //search engine names + foreach (var topic in topics) + { + _urlRecordRepository.Insert(new UrlRecord + { + EntityId = topic.Id, + EntityName = "Topic", + LanguageId = 0, + IsActive = true, + Slug = topic.ValidateSeName("", !String.IsNullOrEmpty(topic.Title) ? topic.Title : topic.SystemName, true) + }); + } + + } + + protected virtual void InstallSettings(bool installSampleData) + { + var settingService = EngineContext.Current.Resolve(); + settingService.SaveSetting(new PdfSettings + { + LogoPictureId = 0, + LetterPageSizeEnabled = false, + RenderOrderNotes = true, + FontFileName = "FreeSerif.ttf", + InvoiceFooterTextColumn1 = null, + InvoiceFooterTextColumn2 = null, + }); + + settingService.SaveSetting(new CommonSettings + { + UseSystemEmailForContactUsForm = true, + UseStoredProceduresIfSupported = true, + UseStoredProcedureForLoadingCategories = false, + SitemapEnabled = true, + SitemapIncludeCategories = true, + SitemapIncludeManufacturers = true, + SitemapIncludeProducts = false, + DisplayJavaScriptDisabledWarning = false, + UseFullTextSearch = false, + FullTextMode = FulltextSearchMode.ExactMatch, + Log404Errors = true, + BreadcrumbDelimiter = "/", + RenderXuaCompatible = false, + XuaCompatibleValue = "IE=edge", + BbcodeEditorOpenLinksInNewWindow = false + }); + + settingService.SaveSetting(new SeoSettings + { + PageTitleSeparator = ". ", + PageTitleSeoAdjustment = PageTitleSeoAdjustment.PagenameAfterStorename, + DefaultTitle = "Your store", + DefaultMetaKeywords = "", + DefaultMetaDescription = "", + GenerateProductMetaDescription = true, + ConvertNonWesternChars = false, + AllowUnicodeCharsInUrls = true, + CanonicalUrlsEnabled = false, + WwwRequirement = WwwRequirement.NoMatter, + //we disable bundling out of the box because it requires a lot of server resources + EnableJsBundling = false, + EnableCssBundling = false, + TwitterMetaTags = true, + OpenGraphMetaTags = true, + ReservedUrlRecordSlugs = new List + { + "admin", + "install", + "recentlyviewedproducts", + "newproducts", + "compareproducts", + "clearcomparelist", + "setproductreviewhelpfulness", + "login", + "register", + "logout", + "cart", + "wishlist", + "emailwishlist", + "checkout", + "onepagecheckout", + "contactus", + "passwordrecovery", + "subscribenewsletter", + "blog", + "boards", + "inboxupdate", + "sentupdate", + "news", + "sitemap", + "search", + "config", + "eucookielawaccept", + "page-not-found", + //system names are not allowed (anyway they will cause a runtime error), + "con", + "lpt1", + "lpt2", + "lpt3", + "lpt4", + "lpt5", + "lpt6", + "lpt7", + "lpt8", + "lpt9", + "com1", + "com2", + "com3", + "com4", + "com5", + "com6", + "com7", + "com8", + "com9", + "null", + "prn", + "aux" + }, + CustomHeadTags = "" + }); + + settingService.SaveSetting(new AdminAreaSettings + { + DefaultGridPageSize = 15, + PopupGridPageSize = 10, + GridPageSizes = "10, 15, 20, 50, 100", + RichEditorAdditionalSettings = null, + RichEditorAllowJavaScript = false, + UseRichEditorInMessageTemplates = false, + UseIsoDateTimeConverterInJson = true + }); + + + settingService.SaveSetting(new ProductEditorSettings + { + Weight = true, + Dimensions = true, + ProductAttributes = true, + SpecificationAttributes =true + }); + + settingService.SaveSetting(new CatalogSettings + { + AllowViewUnpublishedProductPage = true, + DisplayDiscontinuedMessageForUnpublishedProducts = true, + PublishBackProductWhenCancellingOrders = false, + ShowSkuOnProductDetailsPage = true, + ShowSkuOnCatalogPages = false, + ShowManufacturerPartNumber = false, + ShowGtin = false, + ShowFreeShippingNotification = true, + AllowProductSorting = true, + AllowProductViewModeChanging = true, + DefaultViewMode = "grid", + ShowProductsFromSubcategories = false, + ShowCategoryProductNumber = false, + ShowCategoryProductNumberIncludingSubcategories = false, + CategoryBreadcrumbEnabled = true, + ShowShareButton = true, + PageShareCode = "
    ", + ProductReviewsMustBeApproved = false, + DefaultProductRatingValue = 5, + AllowAnonymousUsersToReviewProduct = false, + ProductReviewPossibleOnlyAfterPurchasing = false, + NotifyStoreOwnerAboutNewProductReviews = false, + EmailAFriendEnabled = true, + AllowAnonymousUsersToEmailAFriend = false, + RecentlyViewedProductsNumber = 3, + RecentlyViewedProductsEnabled = true, + NewProductsNumber = 6, + NewProductsEnabled = true, + CompareProductsEnabled = true, + CompareProductsNumber = 4, + ProductSearchAutoCompleteEnabled = true, + ProductSearchAutoCompleteNumberOfProducts = 10, + ProductSearchTermMinimumLength = 3, + ShowProductImagesInSearchAutoComplete = false, + ShowBestsellersOnHomepage = false, + NumberOfBestsellersOnHomepage = 4, + SearchPageProductsPerPage = 6, + SearchPageAllowCustomersToSelectPageSize = true, + SearchPagePageSizeOptions = "6, 3, 9, 18", + ProductsAlsoPurchasedEnabled = true, + ProductsAlsoPurchasedNumber = 4, + AjaxProcessAttributeChange = true, + NumberOfProductTags = 15, + ProductsByTagPageSize = 6, + IncludeShortDescriptionInCompareProducts = false, + IncludeFullDescriptionInCompareProducts = false, + IncludeFeaturedProductsInNormalLists = false, + DisplayTierPricesWithDiscounts = true, + IgnoreDiscounts = false, + IgnoreFeaturedProducts = false, + IgnoreAcl = true, + IgnoreStoreLimitations = true, + CacheProductPrices = false, + ProductsByTagAllowCustomersToSelectPageSize = true, + ProductsByTagPageSizeOptions = "6, 3, 9, 18", + MaximumBackInStockSubscriptions = 200, + ManufacturersBlockItemsToDisplay = 2, + DisplayTaxShippingInfoFooter = false, + DisplayTaxShippingInfoProductDetailsPage = false, + DisplayTaxShippingInfoProductBoxes = false, + DisplayTaxShippingInfoShoppingCart = false, + DisplayTaxShippingInfoWishlist = false, + DisplayTaxShippingInfoOrderDetailsPage = false, + DefaultCategoryPageSizeOptions = "6, 3, 9", + DefaultCategoryPageSize = 6, + DefaultManufacturerPageSizeOptions = "6, 3, 9", + DefaultManufacturerPageSize = 6, + ShowProductReviewsTabOnAccountPage = true, + ProductReviewsPageSizeOnAccountPage = 10, + ExportImportProductAttributes = true, + ExportImportUseDropdownlistsForAssociatedEntities = true + }); + + settingService.SaveSetting(new LocalizationSettings + { + DefaultAdminLanguageId = _languageRepository.Table.Single(l => l.Name == "English").Id, + UseImagesForLanguageSelection = false, + SeoFriendlyUrlsForLanguagesEnabled = false, + AutomaticallyDetectLanguage = false, + LoadAllLocaleRecordsOnStartup = true, + LoadAllLocalizedPropertiesOnStartup = true, + LoadAllUrlRecordsOnStartup = false, + IgnoreRtlPropertyForAdminArea = false + }); + + settingService.SaveSetting(new CustomerSettings + { + UsernamesEnabled = false, + CheckUsernameAvailabilityEnabled = false, + AllowUsersToChangeUsernames = false, + DefaultPasswordFormat = PasswordFormat.Hashed, + HashedPasswordFormat = "SHA1", + PasswordMinLength = 6, + UnduplicatedPasswordsNumber = 4, + PasswordRecoveryLinkDaysValid = 7, + PasswordLifetime = 90, + FailedPasswordAllowedAttempts = 0, + FailedPasswordLockoutMinutes = 30, + UserRegistrationType = UserRegistrationType.Standard, + AllowCustomersToUploadAvatars = false, + AvatarMaximumSizeBytes = 20000, + DefaultAvatarEnabled = true, + ShowCustomersLocation = false, + ShowCustomersJoinDate = false, + AllowViewingProfiles = false, + NotifyNewCustomerRegistration = false, + HideDownloadableProductsTab = false, + HideBackInStockSubscriptionsTab = false, + DownloadableProductsValidateUser = false, + CustomerNameFormat = CustomerNameFormat.ShowFirstName, + GenderEnabled = true, + DateOfBirthEnabled = true, + DateOfBirthRequired = false, + DateOfBirthMinimumAge = null, + CompanyEnabled = true, + StreetAddressEnabled = false, + StreetAddress2Enabled = false, + ZipPostalCodeEnabled = false, + CityEnabled = false, + CountryEnabled = false, + CountryRequired = false, + StateProvinceEnabled = false, + StateProvinceRequired = false, + PhoneEnabled = false, + FaxEnabled = false, + AcceptPrivacyPolicyEnabled = false, + NewsletterEnabled = true, + NewsletterTickedByDefault = true, + HideNewsletterBlock = false, + NewsletterBlockAllowToUnsubscribe = false, + OnlineCustomerMinutes = 20, + StoreLastVisitedPage = false, + SuffixDeletedCustomers = false, + EnteringEmailTwice = false, + RequireRegistrationForDownloadableProducts = false, + DeleteGuestTaskOlderThanMinutes = 1440 + }); + + settingService.SaveSetting(new AddressSettings + { + CompanyEnabled = true, + StreetAddressEnabled = true, + StreetAddressRequired = true, + StreetAddress2Enabled = true, + ZipPostalCodeEnabled = true, + ZipPostalCodeRequired = true, + CityEnabled = true, + CityRequired = true, + CountryEnabled = true, + StateProvinceEnabled = true, + PhoneEnabled = true, + PhoneRequired = true, + FaxEnabled = true, + }); + + settingService.SaveSetting(new MediaSettings + { + AvatarPictureSize = 120, + ProductThumbPictureSize = 415, + ProductDetailsPictureSize = 550, + ProductThumbPictureSizeOnProductDetailsPage = 100, + AssociatedProductPictureSize = 220, + CategoryThumbPictureSize = 450, + ManufacturerThumbPictureSize = 420, + VendorThumbPictureSize = 450, + CartThumbPictureSize = 80, + MiniCartThumbPictureSize = 70, + AutoCompleteSearchThumbPictureSize = 20, + ImageSquarePictureSize = 32, + MaximumImageSize = 1980, + DefaultPictureZoomEnabled = false, + DefaultImageQuality = 80, + MultipleThumbDirectories = false, + ImportProductImagesUsingHash = true, + AzureCacheControlHeader = string.Empty + }); + + settingService.SaveSetting(new StoreInformationSettings + { + StoreClosed = false, + DefaultStoreTheme = "DefaultClean", + AllowCustomerToSelectTheme = false, + DisplayMiniProfilerInPublicStore = false, + DisplayMiniProfilerForAdminOnly = false, + DisplayEuCookieLawWarning = false, + FacebookLink = "http://www.facebook.com/nopCommerce", + TwitterLink = "https://twitter.com/nopCommerce", + YoutubeLink = "http://www.youtube.com/user/nopCommerce", + GooglePlusLink = "https://plus.google.com/+nopcommerce", + HidePoweredByNopCommerce = false + }); + + settingService.SaveSetting(new ExternalAuthenticationSettings + { + AutoRegisterEnabled = true, + RequireEmailValidation = false + }); + + settingService.SaveSetting(new RewardPointsSettings + { + Enabled = true, + ExchangeRate = 1, + PointsForRegistration = 0, + PointsForPurchases_Amount = 10, + PointsForPurchases_Points = 1, + ActivationDelay = 0, + ActivationDelayPeriodId = 0, + DisplayHowMuchWillBeEarned = true, + PointsAccumulatedForAllStores = true, + PageSize = 10, + EarnedRewardPointsAreTaxable = false, + AwardPointsIncludeShipping = true, + AwardPointsIncludePaymentMethodAdditionalFee = true, + AwardPointsExcludeGiftCard = true, + AwardPointsExcludePurchasedRewardPoints = true, + EarnRewardPointsOnlyWhenUsingPurchasedRewardPoints = false + }); + + settingService.SaveSetting(new CurrencySettings + { + DisplayCurrencyLabel = false, + PrimaryStoreCurrencyId = _currencyRepository.Table.Single(c => c.CurrencyCode == "USD").Id, + PrimaryExchangeRateCurrencyId = _currencyRepository.Table.Single(c => c.CurrencyCode == "USD").Id, + ActiveExchangeRateProviderSystemName = "CurrencyExchange.MoneyConverter", + AutoUpdateEnabled = false + }); + + settingService.SaveSetting(new MeasureSettings + { + BaseDimensionId = _measureDimensionRepository.Table.Single(m => m.SystemKeyword == "inches").Id, + BaseWeightId = _measureWeightRepository.Table.Single(m => m.SystemKeyword == "lb").Id, + }); + + settingService.SaveSetting(new MessageTemplatesSettings + { + CaseInvariantReplacement = false, + Color1 = "#b9babe", + Color2 = "#ebecee", + Color3 = "#dde2e6", + }); + + settingService.SaveSetting(new ShoppingCartSettings + { + DisplayCartAfterAddingProduct = false, + DisplayWishlistAfterAddingProduct = false, + MaximumShoppingCartItems = 1000, + MaximumWishlistItems = 1000, + AllowOutOfStockItemsToBeAddedToWishlist = false, + MoveItemsFromWishlistToCart = true, + CartsSharedBetweenStores = false, + ShowProductImagesOnShoppingCart = true, + ShowProductImagesOnWishList = true, + ShowDiscountBox = true, + ShowGiftCardBox = true, + CrossSellsNumber = 4, + EmailWishlistEnabled = true, + AllowAnonymousUsersToEmailWishlist = false, + MiniShoppingCartEnabled = true, + ShowProductImagesInMiniShoppingCart = true, + MiniShoppingCartProductNumber = 5, + RoundPricesDuringCalculation = true, + GroupTierPricesForDistinctShoppingCartItems = false, + AllowCartItemEditing = true, + RenderAssociatedAttributeValueQuantity = true, + RenderProductAttributePrices = true + }); + + settingService.SaveSetting(new OrderSettings + { + ReturnRequestNumberMask = "{ID}", + IsReOrderAllowed = true, + MinOrderSubtotalAmount = 0, + MinOrderSubtotalAmountIncludingTax = false, + MinOrderTotalAmount = 0, + AutoUpdateOrderTotalsOnEditingOrder = false, + AnonymousCheckoutAllowed = true, + TermsOfServiceOnShoppingCartPage = true, + TermsOfServiceOnOrderConfirmPage = false, + OnePageCheckoutEnabled = true, + OnePageCheckoutDisplayOrderTotalsOnPaymentInfoTab = false, + DisableBillingAddressCheckoutStep = false, + DisableOrderCompletedPage = false, + AttachPdfInvoiceToOrderPlacedEmail = false, + AttachPdfInvoiceToOrderCompletedEmail = false, + GeneratePdfInvoiceInCustomerLanguage = true, + AttachPdfInvoiceToOrderPaidEmail = false, + ReturnRequestsEnabled = true, + ReturnRequestsAllowFiles = false, + ReturnRequestsFileMaximumSize = 2048, + NumberOfDaysReturnRequestAvailable = 365, + MinimumOrderPlacementInterval = 30, + ActivateGiftCardsAfterCompletingOrder = false, + DeactivateGiftCardsAfterCancellingOrder = false, + DeactivateGiftCardsAfterDeletingOrder = false, + CompleteOrderWhenDelivered = true, + CustomOrderNumberMask = "{ID}", + ExportWithProducts = true, + AssignInvoiceIdentFromTask = false, + }); + + settingService.SaveSetting(new SecuritySettings + { + ForceSslForAllPages = false, + EncryptionKey = CommonHelper.GenerateRandomDigitCode(16), + AdminAreaAllowedIpAddresses = null, + EnableXsrfProtectionForAdminArea = true, + EnableXsrfProtectionForPublicStore = true, + HoneypotEnabled = false, + HoneypotInputName = "hpinput" + }); + + settingService.SaveSetting(new ShippingSettings + { + ActiveShippingRateComputationMethodSystemNames = new List { "Shipping.FixedOrByWeight" }, + ActivePickupPointProviderSystemNames = new List { "Pickup.PickupInStore" }, + ShipToSameAddress = true, + AllowPickUpInStore = true, + DisplayPickupPointsOnMap = false, + UseWarehouseLocation = false, + NotifyCustomerAboutShippingFromMultipleLocations = false, + FreeShippingOverXEnabled = false, + FreeShippingOverXValue = decimal.Zero, + FreeShippingOverXIncludingTax = false, + EstimateShippingEnabled = true, + DisplayShipmentEventsToCustomers = false, + DisplayShipmentEventsToStoreOwner = false, + HideShippingTotal = false, + ReturnValidOptionsIfThereAreAny = true, + BypassShippingMethodSelectionIfOnlyOne = false, + UseCubeRootMethod = true, + ConsiderAssociatedProductsDimensions = true + }); + + settingService.SaveSetting(new PaymentSettings + { + ActivePaymentMethodSystemNames = new List + { + "Payments.CheckMoneyOrder", + "Payments.Manual", + "Payments.PayInStore", + "Payments.PurchaseOrder", + }, + AllowRePostingPayments = true, + BypassPaymentMethodSelectionIfOnlyOne = true, + ShowPaymentMethodDescriptions = true, + SkipPaymentInfoStepForRedirectionPaymentMethods = false, + CancelRecurringPaymentsAfterFailedPayment = false + }); + + settingService.SaveSetting(new TaxSettings + { + TaxBasedOn = TaxBasedOn.BillingAddress, + TaxBasedOnPickupPointAddress = false, + TaxDisplayType = TaxDisplayType.ExcludingTax, + ActiveTaxProviderSystemName = "Tax.FixedOrByCountryStateZip", + DefaultTaxAddressId = 0, + DisplayTaxSuffix = false, + DisplayTaxRates = false, + PricesIncludeTax = false, + AllowCustomersToSelectTaxDisplayType = false, + ForceTaxExclusionFromOrderSubtotal = false, + DefaultTaxCategoryId = 0, + HideZeroTax = false, + HideTaxInOrderSummary = false, + ShippingIsTaxable = false, + ShippingPriceIncludesTax = false, + ShippingTaxClassId = 0, + PaymentMethodAdditionalFeeIsTaxable = false, + PaymentMethodAdditionalFeeIncludesTax = false, + PaymentMethodAdditionalFeeTaxClassId = 0, + EuVatEnabled = false, + EuVatShopCountryId = 0, + EuVatAllowVatExemption = true, + EuVatUseWebService = false, + EuVatAssumeValid = false, + EuVatEmailAdminWhenNewVatSubmitted = false, + LogErrors = false + }); + + settingService.SaveSetting(new DateTimeSettings + { + DefaultStoreTimeZoneId = "", + AllowCustomersToSetTimeZone = false + }); + + settingService.SaveSetting(new BlogSettings + { + Enabled = true, + PostsPageSize = 10, + AllowNotRegisteredUsersToLeaveComments = true, + NotifyAboutNewBlogComments = false, + NumberOfTags = 15, + ShowHeaderRssUrl = false, + BlogCommentsMustBeApproved = false, + ShowBlogCommentsPerStore = false + }); + settingService.SaveSetting(new NewsSettings + { + Enabled = true, + AllowNotRegisteredUsersToLeaveComments = true, + NotifyAboutNewNewsComments = false, + ShowNewsOnMainPage = true, + MainPageNewsCount = 3, + NewsArchivePageSize = 10, + ShowHeaderRssUrl = false, + NewsCommentsMustBeApproved = false, + ShowNewsCommentsPerStore = false + }); + + settingService.SaveSetting(new ForumSettings + { + ForumsEnabled = false, + RelativeDateTimeFormattingEnabled = true, + AllowCustomersToDeletePosts = false, + AllowCustomersToEditPosts = false, + AllowCustomersToManageSubscriptions = false, + AllowGuestsToCreatePosts = false, + AllowGuestsToCreateTopics = false, + AllowPostVoting = true, + MaxVotesPerDay = 30, + TopicSubjectMaxLength = 450, + PostMaxLength = 4000, + StrippedTopicMaxLength = 45, + TopicsPageSize = 10, + PostsPageSize = 10, + SearchResultsPageSize = 10, + ActiveDiscussionsPageSize = 50, + LatestCustomerPostsPageSize = 10, + ShowCustomersPostCount = true, + ForumEditor = EditorType.BBCodeEditor, + SignaturesEnabled = true, + AllowPrivateMessages = false, + ShowAlertForPM = false, + PrivateMessagesPageSize = 10, + ForumSubscriptionsPageSize = 10, + NotifyAboutPrivateMessages = false, + PMSubjectMaxLength = 450, + PMTextMaxLength = 4000, + HomePageActiveDiscussionsTopicCount = 5, + ActiveDiscussionsFeedEnabled = false, + ActiveDiscussionsFeedCount = 25, + ForumFeedsEnabled = false, + ForumFeedCount = 10, + ForumSearchTermMinimumLength = 3, + }); + + settingService.SaveSetting(new VendorSettings + { + DefaultVendorPageSizeOptions = "6, 3, 9", + VendorsBlockItemsToDisplay = 0, + ShowVendorOnProductDetailsPage = true, + AllowCustomersToContactVendors = true, + AllowCustomersToApplyForVendorAccount = true, + AllowVendorsToEditInfo = false, + NotifyStoreOwnerAboutVendorInformationChange = true, + MaximumProductNumber = 3000, + AllowVendorsToImportProducts = true + }); + + var eaGeneral = _emailAccountRepository.Table.FirstOrDefault(); + if (eaGeneral == null) + throw new Exception("Default email account cannot be loaded"); + settingService.SaveSetting(new EmailAccountSettings + { + DefaultEmailAccountId = eaGeneral.Id + }); + + settingService.SaveSetting(new WidgetSettings + { + ActiveWidgetSystemNames = new List { "Widgets.NivoSlider" }, + }); + + settingService.SaveSetting(new DisplayDefaultMenuItemSettings + { + DisplayHomePageMenuItem = !installSampleData, + DisplayNewProductsMenuItem = !installSampleData, + DisplayProductSearchMenuItem = !installSampleData, + DisplayCustomerInfoMenuItem = !installSampleData, + DisplayBlogMenuItem = !installSampleData, + DisplayForumsMenuItem = !installSampleData, + DisplayContactUsMenuItem = !installSampleData + }); + } + + protected virtual void InstallCheckoutAttributes() + { + var ca1 = new CheckoutAttribute + { + Name = "Gift wrapping", + IsRequired = true, + ShippableProductRequired = true, + AttributeControlType = AttributeControlType.DropdownList, + DisplayOrder = 1, + }; + ca1.CheckoutAttributeValues.Add(new CheckoutAttributeValue + { + Name = "No", + PriceAdjustment = 0, + DisplayOrder = 1, + IsPreSelected = true, + }); + ca1.CheckoutAttributeValues.Add(new CheckoutAttributeValue + { + Name = "Yes", + PriceAdjustment = 10, + DisplayOrder = 2, + }); + var checkoutAttributes = new List + { + ca1, + }; + _checkoutAttributeRepository.Insert(checkoutAttributes); + } + + protected virtual void InstallSpecificationAttributes() + { + var sa1 = new SpecificationAttribute + { + Name = "Screensize", + DisplayOrder = 1, + }; + sa1.SpecificationAttributeOptions.Add(new SpecificationAttributeOption + { + Name = "13.0''", + DisplayOrder = 2, + }); + sa1.SpecificationAttributeOptions.Add(new SpecificationAttributeOption + { + Name = "13.3''", + DisplayOrder = 3, + }); + sa1.SpecificationAttributeOptions.Add(new SpecificationAttributeOption + { + Name = "14.0''", + DisplayOrder = 4, + }); + sa1.SpecificationAttributeOptions.Add(new SpecificationAttributeOption + { + Name = "15.0''", + DisplayOrder = 4, + }); + sa1.SpecificationAttributeOptions.Add(new SpecificationAttributeOption + { + Name = "15.6''", + DisplayOrder = 5, + }); + var sa2 = new SpecificationAttribute + { + Name = "CPU Type", + DisplayOrder = 2, + }; + sa2.SpecificationAttributeOptions.Add(new SpecificationAttributeOption + { + Name = "Intel Core i5", + DisplayOrder = 1, + }); + sa2.SpecificationAttributeOptions.Add(new SpecificationAttributeOption + { + Name = "Intel Core i7", + DisplayOrder = 2, + }); + var sa3 = new SpecificationAttribute + { + Name = "Memory", + DisplayOrder = 3, + }; + sa3.SpecificationAttributeOptions.Add(new SpecificationAttributeOption + { + Name = "4 GB", + DisplayOrder = 1, + }); + sa3.SpecificationAttributeOptions.Add(new SpecificationAttributeOption + { + Name = "8 GB", + DisplayOrder = 2, + }); + sa3.SpecificationAttributeOptions.Add(new SpecificationAttributeOption + { + Name = "16 GB", + DisplayOrder = 3, + }); + var sa4 = new SpecificationAttribute + { + Name = "Hardrive", + DisplayOrder = 5, + }; + sa4.SpecificationAttributeOptions.Add(new SpecificationAttributeOption + { + Name = "128 GB", + DisplayOrder = 7, + }); + sa4.SpecificationAttributeOptions.Add(new SpecificationAttributeOption + { + Name = "500 GB", + DisplayOrder = 4, + }); + sa4.SpecificationAttributeOptions.Add(new SpecificationAttributeOption + { + Name = "1 TB", + DisplayOrder = 3, + }); + var sa5 = new SpecificationAttribute + { + Name = "Color", + DisplayOrder = 1, + }; + sa5.SpecificationAttributeOptions.Add(new SpecificationAttributeOption + { + Name = "Grey", + DisplayOrder = 2, + ColorSquaresRgb = "#8a97a8" + }); + sa5.SpecificationAttributeOptions.Add(new SpecificationAttributeOption + { + Name = "Red", + DisplayOrder = 3, + ColorSquaresRgb = "#8a374a" + }); + sa5.SpecificationAttributeOptions.Add(new SpecificationAttributeOption + { + Name = "Blue", + DisplayOrder = 4, + ColorSquaresRgb = "#47476f" + }); + var specificationAttributes = new List + { + sa1, + sa2, + sa3, + sa4, + sa5 + }; + _specificationAttributeRepository.Insert(specificationAttributes); + } + + protected virtual void InstallProductAttributes() + { + var productAttributes = new List + { + new ProductAttribute + { + Name = "Color", + }, + new ProductAttribute + { + Name = "Print", + }, + new ProductAttribute + { + Name = "Custom Text", + }, + new ProductAttribute + { + Name = "HDD", + }, + new ProductAttribute + { + Name = "OS", + }, + new ProductAttribute + { + Name = "Processor", + }, + new ProductAttribute + { + Name = "RAM", + }, + new ProductAttribute + { + Name = "Size", + }, + new ProductAttribute + { + Name = "Software", + }, + }; + _productAttributeRepository.Insert(productAttributes); + } + + protected virtual void InstallCategories() + { + //pictures + var pictureService = EngineContext.Current.Resolve(); + var sampleImagesPath = CommonHelper.MapPath("~/content/samples/"); + + + + var categoryTemplateInGridAndLines = _categoryTemplateRepository + .Table.FirstOrDefault(pt => pt.Name == "Products in Grid or Lines"); + if (categoryTemplateInGridAndLines == null) + throw new Exception("Category template cannot be loaded"); + + + //categories + var allCategories = new List(); + var categoryComputers = new Category + { + Name = "Computers", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + PageSize = 6, + AllowCustomersToSelectPageSize = true, + PageSizeOptions = "6, 3, 9", + PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "category_computers.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName("Computers")).Id, + IncludeInTopMenu = true, + Published = true, + DisplayOrder = 1, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow + }; + allCategories.Add(categoryComputers); + _categoryRepository.Insert(categoryComputers); + + + var categoryDesktops = new Category + { + Name = "Desktops", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + PageSize = 6, + AllowCustomersToSelectPageSize = true, + PageSizeOptions = "6, 3, 9", + ParentCategoryId = categoryComputers.Id, + PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "category_desktops.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName("Desktops")).Id, + PriceRanges = "-1000;1000-1200;1200-;", + IncludeInTopMenu = true, + Published = true, + DisplayOrder = 1, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow + }; + allCategories.Add(categoryDesktops); + _categoryRepository.Insert(categoryDesktops); + + + var categoryNotebooks = new Category + { + Name = "Notebooks", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + PageSize = 6, + AllowCustomersToSelectPageSize = true, + PageSizeOptions = "6, 3, 9", + ParentCategoryId = categoryComputers.Id, + PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "category_notebooks.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName("Notebooks")).Id, + IncludeInTopMenu = true, + Published = true, + DisplayOrder = 2, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow + }; + allCategories.Add(categoryNotebooks); + _categoryRepository.Insert(categoryNotebooks); + + + var categorySoftware = new Category + { + Name = "Software", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + PageSize = 6, + AllowCustomersToSelectPageSize = true, + PageSizeOptions = "6, 3, 9", + ParentCategoryId = categoryComputers.Id, + PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "category_software.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName("Software")).Id, + IncludeInTopMenu = true, + Published = true, + DisplayOrder = 3, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow + }; + allCategories.Add(categorySoftware); + _categoryRepository.Insert(categorySoftware); + + + var categoryElectronics = new Category + { + Name = "Electronics", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + PageSize = 6, + AllowCustomersToSelectPageSize = true, + PageSizeOptions = "6, 3, 9", + PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "category_electronics.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName("Electronics")).Id, + IncludeInTopMenu = true, + Published = true, + ShowOnHomePage = true, + DisplayOrder = 2, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow + }; + allCategories.Add(categoryElectronics); + _categoryRepository.Insert(categoryElectronics); + + + var categoryCameraPhoto = new Category + { + Name = "Camera & photo", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + PageSize = 6, + AllowCustomersToSelectPageSize = true, + PageSizeOptions = "6, 3, 9", + ParentCategoryId = categoryElectronics.Id, + PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "category_camera_photo.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName("Camera, photo")).Id, + PriceRanges = "-500;500-;", + IncludeInTopMenu = true, + Published = true, + DisplayOrder = 1, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow + }; + allCategories.Add(categoryCameraPhoto); + _categoryRepository.Insert(categoryCameraPhoto); + + + var categoryCellPhones = new Category + { + Name = "Cell phones", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + PageSize = 6, + AllowCustomersToSelectPageSize = true, + PageSizeOptions = "6, 3, 9", + ParentCategoryId = categoryElectronics.Id, + PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "category_cell_phones.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName("Cell phones")).Id, + IncludeInTopMenu = true, + Published = true, + DisplayOrder = 2, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow + }; + allCategories.Add(categoryCellPhones); + _categoryRepository.Insert(categoryCellPhones); + + + var categoryOthers = new Category + { + Name = "Others", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + PageSize = 6, + AllowCustomersToSelectPageSize = true, + PageSizeOptions = "6, 3, 9", + ParentCategoryId = categoryElectronics.Id, + PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "category_accessories.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName("Accessories")).Id, + IncludeInTopMenu = true, + PriceRanges = "-100;100-;", + Published = true, + DisplayOrder = 3, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow + }; + allCategories.Add(categoryOthers); + _categoryRepository.Insert(categoryOthers); + + + var categoryApparel = new Category + { + Name = "Apparel", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + PageSize = 6, + AllowCustomersToSelectPageSize = true, + PageSizeOptions = "6, 3, 9", + PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "category_apparel.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName("Apparel")).Id, + IncludeInTopMenu = true, + Published = true, + ShowOnHomePage = true, + DisplayOrder = 3, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow + }; + allCategories.Add(categoryApparel); + _categoryRepository.Insert(categoryApparel); + + + var categoryShoes = new Category + { + Name = "Shoes", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + PageSize = 6, + AllowCustomersToSelectPageSize = true, + PageSizeOptions = "6, 3, 9", + ParentCategoryId = categoryApparel.Id, + PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "category_shoes.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName("Shoes")).Id, + PriceRanges = "-500;500-;", + IncludeInTopMenu = true, + Published = true, + DisplayOrder = 1, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow + }; + allCategories.Add(categoryShoes); + _categoryRepository.Insert(categoryShoes); + + + var categoryClothing = new Category + { + Name = "Clothing", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + PageSize = 6, + AllowCustomersToSelectPageSize = true, + PageSizeOptions = "6, 3, 9", + ParentCategoryId = categoryApparel.Id, + PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "category_clothing.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName("Clothing")).Id, + IncludeInTopMenu = true, + Published = true, + DisplayOrder = 2, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow + }; + allCategories.Add(categoryClothing); + _categoryRepository.Insert(categoryClothing); + + + var categoryAccessories = new Category + { + Name = "Accessories", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + PageSize = 6, + AllowCustomersToSelectPageSize = true, + PageSizeOptions = "6, 3, 9", + ParentCategoryId = categoryApparel.Id, + PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "category_apparel_accessories.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName("Apparel Accessories")).Id, + IncludeInTopMenu = true, + PriceRanges = "-100;100-;", + Published = true, + DisplayOrder = 3, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow + }; + allCategories.Add(categoryAccessories); + _categoryRepository.Insert(categoryAccessories); + + + var categoryDigitalDownloads = new Category + { + Name = "Digital downloads", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + PageSize = 6, + AllowCustomersToSelectPageSize = true, + PageSizeOptions = "6, 3, 9", + PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "category_digital_downloads.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName("Digital downloads")).Id, + IncludeInTopMenu = true, + Published = true, + ShowOnHomePage = true, + DisplayOrder = 4, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow + }; + allCategories.Add(categoryDigitalDownloads); + _categoryRepository.Insert(categoryDigitalDownloads); + + + var categoryBooks = new Category + { + Name = "Books", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + MetaKeywords = "Books, Dictionary, Textbooks", + MetaDescription = "Books category description", + PageSize = 6, + AllowCustomersToSelectPageSize = true, + PageSizeOptions = "6, 3, 9", + PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "category_book.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName("Book")).Id, + PriceRanges = "-25;25-50;50-;", + IncludeInTopMenu = true, + Published = true, + DisplayOrder = 5, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow + }; + allCategories.Add(categoryBooks); + _categoryRepository.Insert(categoryBooks); + + + var categoryJewelry = new Category + { + Name = "Jewelry", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + PageSize = 6, + AllowCustomersToSelectPageSize = true, + PageSizeOptions = "6, 3, 9", + PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "category_jewelry.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName("Jewelry")).Id, + PriceRanges = "0-500;500-700;700-3000;", + IncludeInTopMenu = true, + Published = true, + DisplayOrder = 6, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow + }; + allCategories.Add(categoryJewelry); + _categoryRepository.Insert(categoryJewelry); + + var categoryGiftCards = new Category + { + Name = "Gift Cards", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + PageSize = 6, + AllowCustomersToSelectPageSize = true, + PageSizeOptions = "6, 3, 9", + PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "category_gift_cards.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName("Gift Cards")).Id, + IncludeInTopMenu = true, + Published = true, + DisplayOrder = 7, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow + }; + allCategories.Add(categoryGiftCards); + _categoryRepository.Insert(categoryGiftCards); + + + + //search engine names + foreach (var category in allCategories) + { + _urlRecordRepository.Insert(new UrlRecord + { + EntityId = category.Id, + EntityName = "Category", + LanguageId = 0, + IsActive = true, + Slug = category.ValidateSeName("", category.Name, true) + }); + } + } + + protected virtual void InstallManufacturers() + { + var pictureService = EngineContext.Current.Resolve(); + var sampleImagesPath = CommonHelper.MapPath("~/content/samples/"); + + var manufacturerTemplateInGridAndLines = + _manufacturerTemplateRepository.Table.FirstOrDefault(pt => pt.Name == "Products in Grid or Lines"); + if (manufacturerTemplateInGridAndLines == null) + throw new Exception("Manufacturer template cannot be loaded"); + + var allManufacturers = new List(); + var manufacturerAsus = new Manufacturer + { + Name = "Apple", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + PageSize = 6, + AllowCustomersToSelectPageSize = true, + PageSizeOptions = "6, 3, 9", + Published = true, + PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "manufacturer_apple.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName("Apple")).Id, + DisplayOrder = 1, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow + }; + _manufacturerRepository.Insert(manufacturerAsus); + allManufacturers.Add(manufacturerAsus); + + + var manufacturerHp = new Manufacturer + { + Name = "HP", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + PageSize = 6, + AllowCustomersToSelectPageSize = true, + PageSizeOptions = "6, 3, 9", + Published = true, + PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "manufacturer_hp.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName("Hp")).Id, + DisplayOrder = 5, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow + }; + _manufacturerRepository.Insert(manufacturerHp); + allManufacturers.Add(manufacturerHp); + + + var manufacturerNike = new Manufacturer + { + Name = "Nike", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + PageSize = 6, + AllowCustomersToSelectPageSize = true, + PageSizeOptions = "6, 3, 9", + Published = true, + PictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "manufacturer_nike.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName("Nike")).Id, + DisplayOrder = 5, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow + }; + _manufacturerRepository.Insert(manufacturerNike); + allManufacturers.Add(manufacturerNike); + + //search engine names + foreach (var manufacturer in allManufacturers) + { + _urlRecordRepository.Insert(new UrlRecord + { + EntityId = manufacturer.Id, + EntityName = "Manufacturer", + LanguageId = 0, + IsActive = true, + Slug = manufacturer.ValidateSeName("", manufacturer.Name, true) + }); + } + } + + protected virtual void InstallProducts(string defaultUserEmail) + { + var productTemplateSimple = _productTemplateRepository.Table.FirstOrDefault(pt => pt.Name == "Simple product"); + if (productTemplateSimple == null) + throw new Exception("Simple product template could not be loaded"); + var productTemplateGrouped = _productTemplateRepository.Table.FirstOrDefault(pt => pt.Name == "Grouped product (with variants)"); + if (productTemplateGrouped == null) + throw new Exception("Grouped product template could not be loaded"); + + //delivery date + var deliveryDate = _deliveryDateRepository.Table.FirstOrDefault(); + if (deliveryDate == null) + throw new Exception("No default deliveryDate could be loaded"); + + //product availability range + var productAvailabilityRange = _productAvailabilityRangeRepository.Table.FirstOrDefault(); + if (productAvailabilityRange == null) + throw new Exception("No default product availability range could be loaded"); + + //default customer/user + var defaultCustomer = _customerRepository.Table.FirstOrDefault(x => x.Email == defaultUserEmail); + if (defaultCustomer == null) + throw new Exception("Cannot load default customer"); + + //default store + var defaultStore = _storeRepository.Table.FirstOrDefault(); + if (defaultStore == null) + throw new Exception("No default store could be loaded"); + + + //pictures + var pictureService = EngineContext.Current.Resolve(); + var sampleImagesPath = CommonHelper.MapPath("~/content/samples/"); + + //downloads + var downloadService = EngineContext.Current.Resolve(); + var sampleDownloadsPath = CommonHelper.MapPath("~/content/samples/"); + + //products + var allProducts = new List(); + + #region Desktops + + + var productBuildComputer = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Build your own computer", + Sku = "COMP_CUST", + ShortDescription = "Build it", + FullDescription = "

    Fight back against cluttered workspaces with the stylish IBM zBC12 All-in-One desktop PC, featuring powerful computing resources and a stunning 20.1-inch widescreen display with stunning XBRITE-HiColor LCD technology. The black IBM zBC12 has a built-in microphone and MOTION EYE camera with face-tracking technology that allows for easy communication with friends and family. And it has a built-in DVD burner and Sony's Movie Store software so you can create a digital entertainment library for personal viewing at your convenience. Easy to setup and even easier to use, this JS-series All-in-One includes an elegantly designed keyboard and a USB mouse.

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "build-your-own-computer", + AllowCustomerReviews = true, + Price = 1200M, + IsShipEnabled = true, + IsFreeShipping = true, + Weight = 2, + Length = 2, + Width = 2, + Height = 2, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + ShowOnHomePage = true, + MarkAsNew = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductAttributeMappings = + { + new ProductAttributeMapping + { + ProductAttribute = _productAttributeRepository.Table.Single(x => x.Name == "Processor"), + AttributeControlType = AttributeControlType.DropdownList, + IsRequired = true, + ProductAttributeValues = + { + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "2.2 GHz Intel Pentium Dual-Core E2200", + DisplayOrder = 1, + }, + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "2.5 GHz Intel Pentium Dual-Core E2200", + IsPreSelected = true, + PriceAdjustment = 15, + DisplayOrder = 2, + } + } + }, + new ProductAttributeMapping + { + ProductAttribute = _productAttributeRepository.Table.Single(x => x.Name == "RAM"), + AttributeControlType = AttributeControlType.DropdownList, + IsRequired = true, + ProductAttributeValues = + { + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "2 GB", + DisplayOrder = 1, + }, + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "4GB", + PriceAdjustment = 20, + DisplayOrder = 2, + }, + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "8GB", + PriceAdjustment = 60, + DisplayOrder = 3, + } + } + }, + new ProductAttributeMapping + { + ProductAttribute = _productAttributeRepository.Table.Single(x => x.Name == "HDD"), + AttributeControlType = AttributeControlType.RadioList, + IsRequired = true, + ProductAttributeValues = + { + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "320 GB", + DisplayOrder = 1, + }, + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "400 GB", + PriceAdjustment = 100, + DisplayOrder = 2, + } + } + }, + new ProductAttributeMapping + { + ProductAttribute = _productAttributeRepository.Table.Single(x => x.Name == "OS"), + AttributeControlType = AttributeControlType.RadioList, + IsRequired = true, + ProductAttributeValues = + { + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "Vista Home", + PriceAdjustment = 50, + IsPreSelected = true, + DisplayOrder = 1, + }, + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "Vista Premium", + PriceAdjustment = 60, + DisplayOrder = 2, + } + } + }, + new ProductAttributeMapping + { + ProductAttribute = _productAttributeRepository.Table.Single(x => x.Name == "Software"), + AttributeControlType = AttributeControlType.Checkboxes, + ProductAttributeValues = + { + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "Microsoft Office", + PriceAdjustment = 50, + IsPreSelected = true, + DisplayOrder = 1, + }, + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "Acrobat Reader", + PriceAdjustment = 10, + DisplayOrder = 2, + }, + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "Total Commander", + PriceAdjustment = 5, + DisplayOrder = 2, + } + } + } + }, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Desktops"), + DisplayOrder = 1, + } + } + }; + allProducts.Add(productBuildComputer); + productBuildComputer.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_Desktops_1.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productBuildComputer.Name)), + DisplayOrder = 1, + }); + productBuildComputer.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_Desktops_2.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productBuildComputer.Name)), + DisplayOrder = 2, + }); + _productRepository.Insert(productBuildComputer); + + + + + + var productDigitalStorm = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Digital Storm VANQUISH 3 Custom Performance PC", + Sku = "DS_VA3_PC", + ShortDescription = "Digital Storm Vanquish 3 Desktop PC", + FullDescription = "

    Blow the doors off todays most demanding games with maximum detail, speed, and power for an immersive gaming experience without breaking the bank.

    Stay ahead of the competition, VANQUISH 3 is fully equipped to easily handle future upgrades, keeping your system on the cutting edge for years to come.

    Each system is put through an extensive stress test, ensuring you experience zero bottlenecks and get the maximum performance from your hardware.

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "compaq-presario-sr1519x-pentium-4-desktop-pc-with-cdrw", + AllowCustomerReviews = true, + Price = 1259M, + IsShipEnabled = true, + Weight = 7, + Length = 7, + Width = 7, + Height = 7, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Desktops"), + DisplayOrder = 1, + } + } + }; + allProducts.Add(productDigitalStorm); + productDigitalStorm.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_DigitalStorm.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productDigitalStorm.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(productDigitalStorm); + + + + + + var productLenovoIdeaCentre = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Lenovo IdeaCentre 600 All-in-One PC", + Sku = "LE_IC_600", + ShortDescription = "", + FullDescription = "

    The A600 features a 21.5in screen, DVD or optional Blu-Ray drive, support for the full beans 1920 x 1080 HD, Dolby Home Cinema certification and an optional hybrid analogue/digital TV tuner.

    Connectivity is handled by 802.11a/b/g - 802.11n is optional - and an ethernet port. You also get four USB ports, a Firewire slot, a six-in-one card reader and a 1.3- or two-megapixel webcam.

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "hp-iq506-touchsmart-desktop-pc", + AllowCustomerReviews = true, + Price = 500M, + IsShipEnabled = true, + Weight = 7, + Length = 7, + Width = 7, + Height = 7, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Desktops"), + DisplayOrder = 1, + } + } + }; + allProducts.Add(productLenovoIdeaCentre); + productLenovoIdeaCentre.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_LenovoIdeaCentre.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productLenovoIdeaCentre.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(productLenovoIdeaCentre); + + + + + #endregion + + #region Notebooks + + var productAppleMacBookPro = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Apple MacBook Pro 13-inch", + Sku = "AP_MBP_13", + ShortDescription = "A groundbreaking Retina display. A new force-sensing trackpad. All-flash architecture. Powerful dual-core and quad-core Intel processors. Together, these features take the notebook to a new level of performance. And they will do the same for you in everything you create.", + FullDescription = "

    With fifth-generation Intel Core processors, the latest graphics, and faster flash storage, the incredibly advanced MacBook Pro with Retina display moves even further ahead in performance and battery life.* *Compared with the previous generation.

    Retina display with 2560-by-1600 resolution

    Fifth-generation dual-core Intel Core i5 processor

    Intel Iris Graphics

    Up to 9 hours of battery life1

    Faster flash storage2

    802.11ac Wi-Fi

    Two Thunderbolt 2 ports for connecting high-performance devices and transferring data at lightning speed

    Two USB 3 ports (compatible with USB 2 devices) and HDMI

    FaceTime HD camera

    Pages, Numbers, Keynote, iPhoto, iMovie, GarageBand included

    OS X, the world's most advanced desktop operating system

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "asus-eee-pc-1000ha-10-inch-netbook", + AllowCustomerReviews = true, + Price = 1800M, + IsShipEnabled = true, + IsFreeShipping = true, + Weight = 3, + Length = 3, + Width = 2, + Height = 2, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 2, + OrderMaximumQuantity = 10000, + Published = true, + ShowOnHomePage = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Notebooks"), + DisplayOrder = 1, + } + }, + ProductManufacturers = + { + new ProductManufacturer + { + Manufacturer = _manufacturerRepository.Table.Single(c => c.Name == "Apple"), + DisplayOrder = 2, + } + }, + ProductSpecificationAttributes = + { + new ProductSpecificationAttribute + { + AllowFiltering = false, + ShowOnProductPage = true, + DisplayOrder = 1, + SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Screensize").SpecificationAttributeOptions.Single(sao => sao.Name == "13.0''") + }, + new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 2, + SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "CPU Type").SpecificationAttributeOptions.Single(sao => sao.Name == "Intel Core i5") + }, + new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Memory").SpecificationAttributeOptions.Single(sao => sao.Name == "4 GB") + } + //new ProductSpecificationAttribute + //{ + // AllowFiltering = false, + // ShowOnProductPage = true, + // DisplayOrder = 4, + // SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Hardrive").SpecificationAttributeOptions.Single(sao => sao.Name == "160 GB") + //} + } + }; + allProducts.Add(productAppleMacBookPro); + productAppleMacBookPro.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_macbook_1.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productAppleMacBookPro.Name)), + DisplayOrder = 1, + }); + productAppleMacBookPro.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_macbook_2.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productAppleMacBookPro.Name)), + DisplayOrder = 2, + }); + _productRepository.Insert(productAppleMacBookPro); + + + + + + var productAsusN551JK = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Asus N551JK-XO076H Laptop", + Sku = "AS_551_LP", + ShortDescription = "Laptop Asus N551JK Intel Core i7-4710HQ 2.5 GHz, RAM 16GB, HDD 1TB, Video NVidia GTX 850M 4GB, BluRay, 15.6, Full HD, Win 8.1", + FullDescription = "

    The ASUS N550JX combines cutting-edge audio and visual technology to deliver an unsurpassed multimedia experience. A full HD wide-view IPS panel is tailor-made for watching movies and the intuitive touchscreen makes for easy, seamless navigation. ASUS has paired the N550JXs impressive display with SonicMaster Premium, co-developed with Bang & Olufsen ICEpower audio experts, for true surround sound. A quad-speaker array and external subwoofer combine for distinct vocals and a low bass that you can feel.

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "asus-eee-pc-900ha-89-inch-netbook-black", + AllowCustomerReviews = true, + Price = 1500M, + IsShipEnabled = true, + Weight = 7, + Length = 7, + Width = 7, + Height = 7, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Notebooks"), + DisplayOrder = 1, + } + }, + ProductSpecificationAttributes = + { + new ProductSpecificationAttribute + { + AllowFiltering = false, + ShowOnProductPage = true, + DisplayOrder = 1, + SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Screensize").SpecificationAttributeOptions.Single(sao => sao.Name == "15.6''") + }, + new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 2, + SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "CPU Type").SpecificationAttributeOptions.Single(sao => sao.Name == "Intel Core i7") + }, + new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Memory").SpecificationAttributeOptions.Single(sao => sao.Name == "16 GB") + }, + new ProductSpecificationAttribute + { + AllowFiltering = false, + ShowOnProductPage = true, + DisplayOrder = 4, + SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Hardrive").SpecificationAttributeOptions.Single(sao => sao.Name == "1 TB") + } + } + }; + allProducts.Add(productAsusN551JK); + productAsusN551JK.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_asuspc_N551JK.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productAsusN551JK.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(productAsusN551JK); + + + + + + var productSamsungSeries = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Samsung Series 9 NP900X4C Premium Ultrabook", + Sku = "SM_900_PU", + ShortDescription = "Samsung Series 9 NP900X4C-A06US 15-Inch Ultrabook (1.70 GHz Intel Core i5-3317U Processor, 8GB DDR3, 128GB SSD, Windows 8) Ash Black", + FullDescription = "

    Designed with mobility in mind, Samsung's durable, ultra premium, lightweight Series 9 laptop (model NP900X4C-A01US) offers mobile professionals and power users a sophisticated laptop equally suited for work and entertainment. Featuring a minimalist look that is both simple and sophisticated, its polished aluminum uni-body design offers an iconic look and feel that pushes the envelope with an edge just 0.58 inches thin. This Series 9 laptop also includes a brilliant 15-inch SuperBright Plus display with HD+ technology, 128 GB Solid State Drive (SSD), 8 GB of system memory, and up to 10 hours of battery life.

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "hp-pavilion-artist-edition-dv2890nr-141-inch-laptop", + AllowCustomerReviews = true, + Price = 1590M, + IsShipEnabled = true, + Weight = 7, + Length = 7, + Width = 7, + Height = 7, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + //ShowOnHomePage = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Notebooks"), + DisplayOrder = 1, + } + }, + ProductSpecificationAttributes = + { + new ProductSpecificationAttribute + { + AllowFiltering = false, + ShowOnProductPage = true, + DisplayOrder = 1, + SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Screensize").SpecificationAttributeOptions.Single(sao => sao.Name == "15.0''") + }, + new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 2, + SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "CPU Type").SpecificationAttributeOptions.Single(sao => sao.Name == "Intel Core i5") + }, + new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Memory").SpecificationAttributeOptions.Single(sao => sao.Name == "8 GB") + }, + new ProductSpecificationAttribute + { + AllowFiltering = false, + ShowOnProductPage = true, + DisplayOrder = 4, + SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Hardrive").SpecificationAttributeOptions.Single(sao => sao.Name == "128 GB") + } + } + }; + allProducts.Add(productSamsungSeries); + productSamsungSeries.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_SamsungNP900X4C.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productSamsungSeries.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(productSamsungSeries); + + + + + + var productHpSpectre = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "HP Spectre XT Pro UltraBook", + Sku = "HP_SPX_UB", + ShortDescription = "HP Spectre XT Pro UltraBook / Intel Core i5-2467M / 13.3 / 4GB / 128GB / Windows 7 Professional / Laptop", + FullDescription = "

    Introducing HP ENVY Spectre XT, the Ultrabook designed for those who want style without sacrificing substance. It's sleek. It's thin. And with Intel. Corer i5 processor and premium materials, it's designed to go anywhere from the bistro to the boardroom, it's unlike anything you've ever seen from HP.

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "hp-pavilion-elite-m9150f-desktop-pc", + AllowCustomerReviews = true, + Price = 1350M, + IsShipEnabled = true, + Weight = 7, + Length = 7, + Width = 7, + Height = 7, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Notebooks"), + DisplayOrder = 1, + } + }, + ProductManufacturers = + { + new ProductManufacturer + { + Manufacturer = _manufacturerRepository.Table.Single(c => c.Name == "HP"), + DisplayOrder = 3, + } + }, + ProductSpecificationAttributes = + { + new ProductSpecificationAttribute + { + AllowFiltering = false, + ShowOnProductPage = true, + DisplayOrder = 1, + SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Screensize").SpecificationAttributeOptions.Single(sao => sao.Name == "13.3''") + }, + new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 2, + SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "CPU Type").SpecificationAttributeOptions.Single(sao => sao.Name == "Intel Core i5") + }, + new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Memory").SpecificationAttributeOptions.Single(sao => sao.Name == "4 GB") + }, + new ProductSpecificationAttribute + { + AllowFiltering = false, + ShowOnProductPage = true, + DisplayOrder = 4, + SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Hardrive").SpecificationAttributeOptions.Single(sao => sao.Name == "128 GB") + } + } + }; + allProducts.Add(productHpSpectre); + productHpSpectre.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_HPSpectreXT_1.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productHpSpectre.Name)), + DisplayOrder = 1, + }); + productHpSpectre.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_HPSpectreXT_2.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productHpSpectre.Name)), + DisplayOrder = 2, + }); + _productRepository.Insert(productHpSpectre); + + + + var productHpEnvy = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "HP Envy 6-1180ca 15.6-Inch Sleekbook", + Sku = "HP_ESB_15", + ShortDescription = "HP ENVY 6-1202ea Ultrabook Beats Audio, 3rd generation Intel CoreTM i7-3517U processor, 8GB RAM, 500GB HDD, Microsoft Windows 8, AMD Radeon HD 8750M (2 GB DDR3 dedicated)", + FullDescription = "The UltrabookTM that's up for anything. Thin and light, the HP ENVY is the large screen UltrabookTM with Beats AudioTM. With a soft-touch base that makes it easy to grab and go, it's a laptop that's up for anything.

    Features

    - Windows 8 or other operating systems available

    Top performance. Stylish design. Take notice.

    - At just 19.8 mm (0.78 in) thin, the HP ENVY UltrabookTM is slim and light enough to take anywhere. It's the laptop that gets you noticed with the power to get it done.
    - With an eye-catching metal design, it's a laptop that you want to carry with you. The soft-touch, slip-resistant base gives you the confidence to carry it with ease.

    More entertaining. More gaming. More fun.

    - Own the UltrabookTM with Beats AudioTM, dual speakers, a subwoofer, and an awesome display. Your music, movies and photo slideshows will always look and sound their best.
    - Tons of video memory let you experience incredible gaming and multimedia without slowing down. Create and edit videos in a flash. And enjoy more of what you love to the fullest.
    - The HP ENVY UltrabookTM is loaded with the ports you'd expect on a world-class laptop, but on a Sleekbook instead. Like HDMI, USB, RJ-45, and a headphone jack. You get all the right connections without compromising size.

    Only from HP.

    - Life heats up. That's why there's HP CoolSense technology, which automatically adjusts your notebook's temperature based on usage and conditions. It stays cool. You stay comfortable.
    - With HP ProtectSmart, your notebook's data stays safe from accidental bumps and bruises. It senses motion and plans ahead, stopping your hard drive and protecting your entire digital life.
    - Keep playing even in dimly lit rooms or on red eye flights. The optional backlit keyboard[1] is full-size so you don't compromise comfort. Backlit keyboard. Another bright idea.

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "hp-pavilion-g60-230us-160-inch-laptop", + AllowCustomerReviews = true, + Price = 1460M, + IsShipEnabled = true, + Weight = 7, + Length = 7, + Width = 7, + Height = 7, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Notebooks"), + DisplayOrder = 1, + } + }, + ProductManufacturers = + { + new ProductManufacturer + { + Manufacturer = _manufacturerRepository.Table.Single(c => c.Name == "HP"), + DisplayOrder = 4, + } + }, + ProductSpecificationAttributes = + { + new ProductSpecificationAttribute + { + AllowFiltering = false, + ShowOnProductPage = true, + DisplayOrder = 1, + SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Screensize").SpecificationAttributeOptions.Single(sao => sao.Name == "15.6''") + }, + new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 2, + SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "CPU Type").SpecificationAttributeOptions.Single(sao => sao.Name == "Intel Core i7") + }, + new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Memory").SpecificationAttributeOptions.Single(sao => sao.Name == "8 GB") + }, + new ProductSpecificationAttribute + { + AllowFiltering = false, + ShowOnProductPage = true, + DisplayOrder = 4, + SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Hardrive").SpecificationAttributeOptions.Single(sao => sao.Name == "500 GB") + } + } + }; + allProducts.Add(productHpEnvy); + productHpEnvy.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_HpEnvy6.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productHpEnvy.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(productHpEnvy); + + + + + + var productLenovoThinkpad = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Lenovo Thinkpad X1 Carbon Laptop", + Sku = "LE_TX1_CL", + ShortDescription = "Lenovo Thinkpad X1 Carbon Touch Intel Core i7 14 Ultrabook", + FullDescription = "

    The X1 Carbon brings a new level of quality to the ThinkPad legacy of high standards and innovation. It starts with the durable, carbon fiber-reinforced roll cage, making for the best Ultrabook construction available, and adds a host of other new features on top of the old favorites. Because for 20 years, we haven't stopped innovating. And you shouldn't stop benefiting from that.

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "toshiba-satellite-a305-s6908-154-inch-laptop", + AllowCustomerReviews = true, + Price = 1360M, + IsShipEnabled = true, + Weight = 7, + Length = 7, + Width = 7, + Height = 7, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Notebooks"), + DisplayOrder = 1, + } + }, + ProductSpecificationAttributes = + { + new ProductSpecificationAttribute + { + AllowFiltering = false, + ShowOnProductPage = true, + DisplayOrder = 1, + SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Screensize").SpecificationAttributeOptions.Single(sao => sao.Name == "14.0''") + }, + new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 2, + SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "CPU Type").SpecificationAttributeOptions.Single(sao => sao.Name == "Intel Core i7") + } + //new ProductSpecificationAttribute + //{ + // AllowFiltering = true, + // ShowOnProductPage = true, + // DisplayOrder = 3, + // SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Memory").SpecificationAttributeOptions.Single(sao => sao.Name == "1 GB") + //}, + //new ProductSpecificationAttribute + //{ + // AllowFiltering = false, + // ShowOnProductPage = true, + // DisplayOrder = 4, + // SpecificationAttributeOption = _specificationAttributeRepository.Table.Single(sa => sa.Name == "Hardrive").SpecificationAttributeOptions.Single(sao => sao.Name == "250 GB") + //} + } + }; + allProducts.Add(productLenovoThinkpad); + productLenovoThinkpad.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_LenovoThinkpad.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productLenovoThinkpad.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(productLenovoThinkpad); + + #endregion + + #region Software + + + var productAdobePhotoshop = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Adobe Photoshop CS4", + Sku = "AD_CS4_PH", + ShortDescription = "Easily find and view all your photos", + FullDescription = "

    Adobe Photoshop CS4 software combines power and simplicity so you can make ordinary photos extraordinary; tell engaging stories in beautiful, personalized creations for print and web; and easily find and view all your photos. New Photoshop.com membership* works with Photoshop CS4 so you can protect your photos with automatic online backup and 2 GB of storage; view your photos anywhere you are; and share your photos in fun, interactive ways with invitation-only Online Albums.

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "adobe-photoshop-elements-7", + AllowCustomerReviews = true, + Price = 75M, + IsShipEnabled = true, + Weight = 2, + Length = 2, + Width = 2, + Height = 3, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Software"), + DisplayOrder = 1, + } + } + }; + allProducts.Add(productAdobePhotoshop); + productAdobePhotoshop.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_AdobePhotoshop.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productAdobePhotoshop.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(productAdobePhotoshop); + + + + + + + var productWindows8Pro = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Windows 8 Pro", + Sku = "MS_WIN_8P", + ShortDescription = "Windows 8 is a Microsoft operating system that was released in 2012 as part of the company's Windows NT OS family. ", + FullDescription = "

    Windows 8 Pro is comparable to Windows 7 Professional and Ultimate and is targeted towards enthusiasts and business users; it includes all the features of Windows 8. Additional features include the ability to receive Remote Desktop connections, the ability to participate in a Windows Server domain, Encrypting File System, Hyper-V, and Virtual Hard Disk Booting, Group Policy as well as BitLocker and BitLocker To Go. Windows Media Center functionality is available only for Windows 8 Pro as a separate software package.

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "corel-paint-shop-pro-photo-x2", + AllowCustomerReviews = true, + Price = 65M, + IsShipEnabled = true, + Weight = 2, + Length = 2, + Width = 2, + Height = 3, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Software"), + DisplayOrder = 1, + } + } + }; + allProducts.Add(productWindows8Pro); + productWindows8Pro.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_Windows8.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productWindows8Pro.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(productWindows8Pro); + + + + + + var productSoundForge = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Sound Forge Pro 11 (recurring)", + Sku = "SF_PRO_11", + ShortDescription = "Advanced audio waveform editor.", + FullDescription = "

    Sound Forge Pro is the application of choice for a generation of creative and prolific artists, producers, and editors. Record audio quickly on a rock-solid platform, address sophisticated audio processing tasks with surgical precision, and render top-notch master files with ease. New features include one-touch recording, metering for the new critical standards, more repair and restoration tools, and exclusive round-trip interoperability with SpectraLayers Pro. Taken together, these enhancements make this edition of Sound Forge Pro the deepest and most advanced audio editing platform available.

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "major-league-baseball-2k9", + IsRecurring = true, + RecurringCycleLength = 30, + RecurringCyclePeriod = RecurringProductCyclePeriod.Months, + RecurringTotalCycles = 12, + AllowCustomerReviews = true, + Price = 54.99M, + IsShipEnabled = true, + Weight = 7, + Length = 7, + Width = 7, + Height = 7, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Software"), + DisplayOrder = 1, + } + } + }; + allProducts.Add(productSoundForge); + productSoundForge.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_SoundForge.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productSoundForge.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(productSoundForge); + + + + + #endregion + + #region Camera, Photo + + + //this one is a grouped product with two associated ones + var productNikonD5500DSLR = new Product + { + ProductType = ProductType.GroupedProduct, + VisibleIndividually = true, + Name = "Nikon D5500 DSLR", + Sku = "N5500DS_0", + ShortDescription = "Slim, lightweight Nikon D5500 packs a vari-angle touchscreen", + FullDescription = "Nikon has announced its latest DSLR, the D5500. A lightweight, compact DX-format camera with a 24.2MP sensor, its the first of its type to offer a vari-angle touchscreen. The D5500 replaces the D5300 in Nikons range, and while it offers much the same features the company says its a much slimmer and lighter prospect. Theres a deep grip for easier handling and built-in Wi-Fi that lets you transfer and share shots via your phone or tablet.", + ProductTemplateId = productTemplateGrouped.Id, + //SeName = "canon-digital-slr-camera", + AllowCustomerReviews = true, + Published = true, + Price = 670M, + IsShipEnabled = true, + Weight = 2, + Length = 2, + Width = 2, + Height = 2, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Camera & photo"), + DisplayOrder = 1, + } + } + }; + allProducts.Add(productNikonD5500DSLR); + productNikonD5500DSLR.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_NikonCamera_1.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productNikonD5500DSLR.Name)), + DisplayOrder = 1, + }); + productNikonD5500DSLR.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_NikonCamera_2.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productNikonD5500DSLR.Name)), + DisplayOrder = 2, + }); + _productRepository.Insert(productNikonD5500DSLR); + var productNikonD5500DSLR_associated_1 = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = false, //hide this products + ParentGroupedProductId = productNikonD5500DSLR.Id, + Name = "Nikon D5500 DSLR - Black", + Sku = "N5500DS_B", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "canon-digital-slr-camera-black", + AllowCustomerReviews = true, + Published = true, + Price = 670M, + IsShipEnabled = true, + Weight = 2, + Length = 2, + Width = 2, + Height = 2, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow + }; + allProducts.Add(productNikonD5500DSLR_associated_1); + productNikonD5500DSLR_associated_1.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_NikonCamera_black.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName("Canon Digital SLR Camera - Black")), + DisplayOrder = 1, + }); + _productRepository.Insert(productNikonD5500DSLR_associated_1); + var productNikonD5500DSLR_associated_2 = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = false, //hide this products + ParentGroupedProductId = productNikonD5500DSLR.Id, + Name = "Nikon D5500 DSLR - Red", + Sku = "N5500DS_R", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "canon-digital-slr-camera-silver", + AllowCustomerReviews = true, + Published = true, + Price = 630M, + IsShipEnabled = true, + Weight = 2, + Length = 2, + Width = 2, + Height = 2, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow + }; + allProducts.Add(productNikonD5500DSLR_associated_2); + productNikonD5500DSLR_associated_2.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_NikonCamera_red.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName("Canon Digital SLR Camera - Silver")), + DisplayOrder = 1, + }); + _productRepository.Insert(productNikonD5500DSLR_associated_2); + + + + + + var productLeica = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Leica T Mirrorless Digital Camera", + Sku = "LT_MIR_DC", + ShortDescription = "Leica T (Typ 701) Silver", + FullDescription = "

    The new Leica T offers a minimalist design that's crafted from a single block of aluminum. Made in Germany and assembled by hand, this 16.3 effective mega pixel camera is easy to use. With a massive 3.7 TFT LCD intuitive touch screen control, the user is able to configure and save their own menu system. The Leica T has outstanding image quality and also has 16GB of built in memory. This is Leica's first system camera to use Wi-Fi. Add the T-App to your portable iOS device and be able to transfer and share your images (free download from the Apple App Store)

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "canon-vixia-hf100-camcorder", + AllowCustomerReviews = true, + Price = 530M, + IsShipEnabled = true, + Weight = 7, + Length = 7, + Width = 7, + Height = 7, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Camera & photo"), + DisplayOrder = 3, + } + } + }; + allProducts.Add(productLeica); + productLeica.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_LeicaT.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productLeica.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(productLeica); + + + + + + + var productAppleICam = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Apple iCam", + Sku = "APPLE_CAM", + ShortDescription = "Photography becomes smart", + FullDescription = "

    A few months ago we featured the amazing WVIL camera, by many considered the future of digital photography. This is another very good looking concept, iCam is the vision of Italian designer Antonio DeRosa, the idea is to have a device that attaches to the iPhone 5, which then allows the user to have a camera with interchangeable lenses. The device would also feature a front-touch screen and a projector. Would be great if apple picked up on this and made it reality.

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "panasonic-hdc-sdt750k-high-definition-3d-camcorder", + AllowCustomerReviews = true, + Price = 1300M, + IsShipEnabled = true, + Weight = 7, + Length = 7, + Width = 7, + Height = 7, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Camera & photo"), + DisplayOrder = 2, + } + }, + ProductManufacturers = + { + new ProductManufacturer + { + Manufacturer = _manufacturerRepository.Table.Single(c => c.Name == "Apple"), + DisplayOrder = 1, + } + } + }; + allProducts.Add(productAppleICam); + productAppleICam.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_iCam.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productAppleICam.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(productAppleICam); + + + + + #endregion + + #region Cell Phone + + var productHtcOne = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "HTC One M8 Android L 5.0 Lollipop", + Sku = "M8_HTC_5L", + ShortDescription = "HTC - One (M8) 4G LTE Cell Phone with 32GB Memory - Gunmetal (Sprint)", + FullDescription = "

    HTC One (M8) Cell Phone for Sprint: With its brushed-metal design and wrap-around unibody frame, the HTC One (M8) is designed to fit beautifully in your hand. It's fun to use with amped up sound and a large Full HD touch screen, and intuitive gesture controls make it seem like your phone almost knows what you need before you do.

    Sprint Easy Pay option available in store.

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "blackberry-bold-9000-phone-black-att", + AllowCustomerReviews = true, + Price = 245M, + IsShipEnabled = true, + Weight = 2, + Length = 2, + Width = 2, + Height = 2, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + ShowOnHomePage = true, + MarkAsNew = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Cell phones"), + DisplayOrder = 1, + } + } + }; + allProducts.Add(productHtcOne); + productHtcOne.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_HTC_One_M8.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productHtcOne.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(productHtcOne); + + + + + + + var productHtcOneMini = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "HTC One Mini Blue", + Sku = "OM_HTC_BL", + ShortDescription = "HTC One and HTC One Mini now available in bright blue hue", + FullDescription = "

    HTC One mini smartphone with 4.30-inch 720x1280 display powered by 1.4GHz processor alongside 1GB RAM and 4-Ultrapixel rear camera.

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "samsung-rugby-a837-phone-black-att", + AllowCustomerReviews = true, + Price = 100M, + IsShipEnabled = true, + Weight = 7, + Length = 7, + Width = 7, + Height = 7, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + MarkAsNew = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Cell phones"), + DisplayOrder = 1, + } + } + }; + allProducts.Add(productHtcOneMini); + productHtcOneMini.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_HTC_One_Mini_1.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productHtcOneMini.Name)), + DisplayOrder = 1, + }); + productHtcOneMini.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_HTC_One_Mini_2.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productHtcOneMini.Name)), + DisplayOrder = 2, + }); + _productRepository.Insert(productHtcOneMini); + + + + + + + var productNokiaLumia = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Nokia Lumia 1020", + Sku = "N_1020_LU", + ShortDescription = "Nokia Lumia 1020 4G Cell Phone (Unlocked)", + FullDescription = "

    Capture special moments for friends and family with this Nokia Lumia 1020 32GB WHITE cell phone that features an easy-to-use 41.0MP rear-facing camera and a 1.2MP front-facing camera. The AMOLED touch screen offers 768 x 1280 resolution for crisp visuals.

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "sony-dcr-sr85-1mp-60gb-hard-drive-handycam-camcorder", + AllowCustomerReviews = true, + Price = 349M, + IsShipEnabled = true, + Weight = 7, + Length = 7, + Width = 7, + Height = 7, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Cell phones"), + DisplayOrder = 1, + } + } + }; + allProducts.Add(productNokiaLumia); + productNokiaLumia.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_Lumia1020.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productNokiaLumia.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(productNokiaLumia); + + + #endregion + + #region Others + + + + var productBeatsPill = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Beats Pill 2.0 Wireless Speaker", + Sku = "BP_20_WSP", + ShortDescription = "Pill 2.0 Portable Bluetooth Speaker (1-Piece): Watch your favorite movies and listen to music with striking sound quality. This lightweight, portable speaker is easy to take with you as you travel to any destination, keeping you entertained wherever you are. ", + FullDescription = "
    • Pair and play with your Bluetooth device with 30 foot range
    • Built-in speakerphone
    • 7 hour rechargeable battery
    • Power your other devices with USB charge out
    • Tap two Beats Pills together for twice the sound with Beats Bond
    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "acer-aspire-one-89-mini-notebook-case-black", + AllowCustomerReviews = true, + Price = 79.99M, + IsShipEnabled = true, + IsFreeShipping = true, + Weight = 2, + Length = 2, + Width = 2, + Height = 3, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + MarkAsNew = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + TierPrices = + { + new TierPrice + { + Quantity = 2, + Price = 19 + }, + new TierPrice + { + Quantity = 5, + Price = 17 + }, + new TierPrice + { + Quantity = 10, + Price = 15, + StartDateTimeUtc = DateTime.UtcNow.AddDays(-7), + EndDateTimeUtc = DateTime.UtcNow.AddDays(7) + } + }, + HasTierPrices = true, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Others"), + DisplayOrder = 1, + } + } + }; + allProducts.Add(productBeatsPill); + productBeatsPill.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_PillBeats_1.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productBeatsPill.Name)), + DisplayOrder = 1, + }); + productBeatsPill.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_PillBeats_2.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productBeatsPill.Name)), + DisplayOrder = 2, + }); + _productRepository.Insert(productBeatsPill); + + + + + + var productUniversalTabletCover = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Universal 7-8 Inch Tablet Cover", + Sku = "TC_78I_UN", + ShortDescription = "Universal protection for 7-inch & 8-inch tablets", + FullDescription = "

    Made of durable polyurethane, our Universal Cover is slim, lightweight, and strong, with protective corners that stretch to hold most 7 and 8-inch tablets securely. This tough case helps protects your tablet from bumps, scuffs, and dings.

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "apc-back-ups-rs-800va-ups-800-va-ups-battery-lead-acid-br800blk", + AllowCustomerReviews = true, + Price = 39M, + IsShipEnabled = true, + Weight = 2, + Length = 2, + Width = 2, + Height = 3, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Others"), + DisplayOrder = 1, + } + } + }; + allProducts.Add(productUniversalTabletCover); + productUniversalTabletCover.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_TabletCover.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productUniversalTabletCover.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(productUniversalTabletCover); + + + + + var productPortableSoundSpeakers = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Portable Sound Speakers", + Sku = "PT_SPK_SN", + ShortDescription = "Universall portable sound speakers", + FullDescription = "

    Your phone cut the cord, now it's time for you to set your music free and buy a Bluetooth speaker. Thankfully, there's one suited for everyone out there.

    Some Bluetooth speakers excel at packing in as much functionality as the unit can handle while keeping the price down. Other speakers shuck excess functionality in favor of premium build materials instead. Whatever path you choose to go down, you'll be greeted with many options to suit your personal tastes.

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "microsoft-bluetooth-notebook-mouse-5000-macwindows", + AllowCustomerReviews = true, + Price = 37M, + IsShipEnabled = true, + Weight = 7, + Length = 7, + Width = 7, + Height = 7, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Electronics & Software").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Others"), + DisplayOrder = 1, + } + } + }; + allProducts.Add(productPortableSoundSpeakers); + productPortableSoundSpeakers.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_Speakers.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productPortableSoundSpeakers.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(productPortableSoundSpeakers); + + + #endregion + + #region Shoes + + + var productNikeFloral = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Nike Floral Roshe Customized Running Shoes", + Sku = "NK_FRC_RS", + ShortDescription = "When you ran across these shoes, you will immediately fell in love and needed a pair of these customized beauties.", + FullDescription = "

    Each Rosh Run is personalized and exclusive, handmade in our workshop Custom. Run Your Rosh creations born from the hand of an artist specialized in sneakers, more than 10 years of experience.

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "adidas-womens-supernova-csh-7-running-shoe", + AllowCustomerReviews = true, + Price = 40M, + IsShipEnabled = true, + Weight = 2, + Length = 2, + Width = 2, + Height = 2, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Apparel").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductAttributeMappings = + { + new ProductAttributeMapping + { + ProductAttribute = _productAttributeRepository.Table.Single(x => x.Name == "Size"), + AttributeControlType = AttributeControlType.DropdownList, + IsRequired = true, + ProductAttributeValues = + { + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "8", + DisplayOrder = 1, + }, + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "9", + DisplayOrder = 2, + }, + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "10", + DisplayOrder = 3, + }, + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "11", + DisplayOrder = 4, + } + } + }, + new ProductAttributeMapping + { + ProductAttribute = _productAttributeRepository.Table.Single(x => x.Name == "Color"), + AttributeControlType = AttributeControlType.DropdownList, + IsRequired = true, + ProductAttributeValues = + { + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "White/Blue", + DisplayOrder = 1, + }, + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "White/Black", + DisplayOrder = 2, + }, + } + }, + new ProductAttributeMapping + { + ProductAttribute = _productAttributeRepository.Table.Single(x => x.Name == "Print"), + AttributeControlType = AttributeControlType.ImageSquares, + IsRequired = true, + ProductAttributeValues = + { + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "Natural", + DisplayOrder = 1, + ImageSquaresPictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "p_attribute_print_2.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName("Natural Print")).Id, + }, + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "Fresh", + DisplayOrder = 2, + ImageSquaresPictureId = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "p_attribute_print_1.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName("Fresh Print")).Id, + }, + } + } + }, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Shoes"), + DisplayOrder = 1, + } + }, + ProductManufacturers = + { + new ProductManufacturer + { + Manufacturer = _manufacturerRepository.Table.Single(c => c.Name == "Nike"), + DisplayOrder = 2, + } + }, + ProductSpecificationAttributes = + { + new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = false, + DisplayOrder = 1, + SpecificationAttributeOption = + _specificationAttributeRepository.Table.Single(sa => sa.Name == "Color") + .SpecificationAttributeOptions.Single(sao => sao.Name == "Grey") + } + } + }; + allProducts.Add(productNikeFloral); + productNikeFloral.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_NikeFloralShoe_1.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName(productNikeFloral.Name)), + DisplayOrder = 1, + }); + productNikeFloral.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_NikeFloralShoe_2.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName(productNikeFloral.Name)), + DisplayOrder = 2, + }); + _productRepository.Insert(productNikeFloral); + + productNikeFloral.ProductAttributeMappings.First(x => x.ProductAttribute.Name == "Print").ProductAttributeValues.First(x => x.Name == "Natural").PictureId = productNikeFloral.ProductPictures.ElementAt(0).PictureId; + productNikeFloral.ProductAttributeMappings.First(x => x.ProductAttribute.Name == "Print").ProductAttributeValues.First(x => x.Name == "Fresh").PictureId = productNikeFloral.ProductPictures.ElementAt(1).PictureId; + _productRepository.Update(productNikeFloral); + + + + var productAdidas = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "adidas Consortium Campus 80s Running Shoes", + Sku = "AD_C80_RS", + ShortDescription = "adidas Consortium Campus 80s Primeknit Light Maroon/Running Shoes", + FullDescription = "

    One of three colorways of the adidas Consortium Campus 80s Primeknit set to drop alongside each other. This pair comes in light maroon and running white. Featuring a maroon-based primeknit upper with white accents. A limited release, look out for these at select adidas Consortium accounts worldwide.

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "etnies-mens-digit-sneaker", + AllowCustomerReviews = true, + Price = 27.56M, + IsShipEnabled = true, + Weight = 2, + Length = 2, + Width = 2, + Height = 2, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Apparel").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + //ShowOnHomePage = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductAttributeMappings = + { + new ProductAttributeMapping + { + ProductAttribute = _productAttributeRepository.Table.Single(x => x.Name == "Size"), + AttributeControlType = AttributeControlType.DropdownList, + IsRequired = true, + ProductAttributeValues = + { + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "8", + DisplayOrder = 1, + }, + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "9", + DisplayOrder = 2, + }, + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "10", + DisplayOrder = 3, + }, + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "11", + DisplayOrder = 4, + } + } + }, + new ProductAttributeMapping + { + ProductAttribute = _productAttributeRepository.Table.Single(x => x.Name == "Color"), + AttributeControlType = AttributeControlType.ColorSquares, + IsRequired = true, + ProductAttributeValues = + { + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "Red", + IsPreSelected = true, + ColorSquaresRgb = "#663030", + DisplayOrder = 1, + }, + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "Blue", + ColorSquaresRgb = "#363656", + DisplayOrder = 2, + }, + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "Silver", + ColorSquaresRgb = "#c5c5d5", + DisplayOrder = 3, + } + } + } + }, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Shoes"), + DisplayOrder = 1, + } + }, + ProductSpecificationAttributes = + { + new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = false, + DisplayOrder = 1, + SpecificationAttributeOption = + _specificationAttributeRepository.Table.Single(sa => sa.Name == "Color") + .SpecificationAttributeOptions.Single(sao => sao.Name == "Grey") + }, + new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = false, + DisplayOrder = 2, + SpecificationAttributeOption = + _specificationAttributeRepository.Table.Single(sa => sa.Name == "Color") + .SpecificationAttributeOptions.Single(sao => sao.Name == "Red") + }, + new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = false, + DisplayOrder = 3, + SpecificationAttributeOption = + _specificationAttributeRepository.Table.Single(sa => sa.Name == "Color") + .SpecificationAttributeOptions.Single(sao => sao.Name == "Blue") + }, + } + }; + allProducts.Add(productAdidas); + productAdidas.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_adidas.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName(productAdidas.Name)), + DisplayOrder = 1, + }); + productAdidas.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_adidas_2.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName(productAdidas.Name)), + DisplayOrder = 2, + }); + productAdidas.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_adidas_3.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName(productAdidas.Name)), + DisplayOrder = 3, + }); + + + _productRepository.Insert(productAdidas); + + productAdidas.ProductAttributeMappings.First(x => x.ProductAttribute.Name == "Color").ProductAttributeValues.First(x => x.Name == "Red").PictureId = productAdidas.ProductPictures.ElementAt(0).PictureId; + productAdidas.ProductAttributeMappings.First(x => x.ProductAttribute.Name == "Color").ProductAttributeValues.First(x => x.Name == "Blue").PictureId = productAdidas.ProductPictures.ElementAt(1).PictureId; + productAdidas.ProductAttributeMappings.First(x => x.ProductAttribute.Name == "Color").ProductAttributeValues.First(x => x.Name == "Silver").PictureId = productAdidas.ProductPictures.ElementAt(2).PictureId; + _productRepository.Update(productAdidas); + + + + + var productNikeZoom = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Nike SB Zoom Stefan Janoski \"Medium Mint\"", + Sku = "NK_ZSJ_MM", + ShortDescription = "Nike SB Zoom Stefan Janoski Dark Grey Medium Mint Teal ...", + FullDescription = "The newly Nike SB Zoom Stefan Janoski gets hit with a \"Medium Mint\" accents that sits atop a Dark Grey suede. Expected to drop in October.", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "v-blue-juniors-cuffed-denim-short-with-rhinestones", + AllowCustomerReviews = true, + Price = 30M, + IsShipEnabled = true, + Weight = 2, + Length = 2, + Width = 2, + Height = 2, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Apparel").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Shoes"), + DisplayOrder = 1, + } + }, + ProductManufacturers = + { + new ProductManufacturer + { + Manufacturer = _manufacturerRepository.Table.Single(c => c.Name == "Nike"), + DisplayOrder = 2, + } + }, + ProductSpecificationAttributes = + { + new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = false, + DisplayOrder = 1, + SpecificationAttributeOption = + _specificationAttributeRepository.Table.Single(sa => sa.Name == "Color") + .SpecificationAttributeOptions.Single(sao => sao.Name == "Grey") + } + } + }; + + allProducts.Add(productNikeZoom); + productNikeZoom.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_NikeZoom.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName(productNikeZoom.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(productNikeZoom); + + + #endregion + + #region Clothing + + var productNikeTailwind = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Nike Tailwind Loose Short-Sleeve Running Shirt", + Sku = "NK_TLS_RS", + ShortDescription = "", + FullDescription = "

    Boost your adrenaline with the Nike Women's Tailwind Running Shirt. The lightweight, slouchy fit is great for layering, and moisture-wicking fabrics keep you feeling at your best. This tee has a notched hem for an enhanced range of motion, while flat seams with reinforcement tape lessen discomfort and irritation over longer distances. Put your keys and card in the side zip pocket and take off in your Nike running t-shirt.

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "50s-rockabilly-polka-dot-top-jr-plus-size", + AllowCustomerReviews = true, + Published = true, + Price = 15M, + IsShipEnabled = true, + Weight = 1, + Length = 2, + Width = 3, + Height = 3, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Apparel").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductAttributeMappings = + { + new ProductAttributeMapping + { + ProductAttribute = _productAttributeRepository.Table.Single(x => x.Name == "Size"), + AttributeControlType = AttributeControlType.DropdownList, + IsRequired = true, + ProductAttributeValues = + { + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "Small", + DisplayOrder = 1, + }, + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "1X", + DisplayOrder = 2, + }, + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "2X", + DisplayOrder = 3, + }, + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "3X", + DisplayOrder = 4, + }, + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "4X", + DisplayOrder = 5, + }, + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "5X", + DisplayOrder = 6, + } + } + } + }, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Clothing"), + DisplayOrder = 1, + } + }, + ProductManufacturers = + { + new ProductManufacturer + { + Manufacturer = _manufacturerRepository.Table.Single(c => c.Name == "Nike"), + DisplayOrder = 2, + } + } + }; + allProducts.Add(productNikeTailwind); + productNikeTailwind.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_NikeShirt.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName(productNikeTailwind.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(productNikeTailwind); + + + + + var productOversizedWomenTShirt = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Oversized Women T-Shirt", + Sku = "WM_OVR_TS", + ShortDescription = "", + FullDescription = "

    This oversized women t-Shirt needs minimum ironing. It is a great product at a great value!

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "arrow-mens-wrinkle-free-pinpoint-solid-long-sleeve", + AllowCustomerReviews = true, + Price = 24M, + IsShipEnabled = true, + Weight = 4, + Length = 3, + Width = 3, + Height = 3, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Apparel").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + TierPrices = + { + new TierPrice + { + Quantity = 3, + Price = 21 + }, + new TierPrice + { + Quantity = 7, + Price = 19 + }, + new TierPrice + { + Quantity = 10, + Price = 16 + } + }, + HasTierPrices = true, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Clothing"), + DisplayOrder = 1, + } + } + }; + allProducts.Add(productOversizedWomenTShirt); + productOversizedWomenTShirt.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_WomenTShirt.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName(productOversizedWomenTShirt.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(productOversizedWomenTShirt); + + + + + var productCustomTShirt = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Custom T-Shirt", + Sku = "CS_TSHIRT", + ShortDescription = "T-Shirt - Add Your Content", + FullDescription = "

    Comfort comes in all shapes and forms, yet this tee out does it all. Rising above the rest, our classic cotton crew provides the simple practicality you need to make it through the day. Tag-free, relaxed fit wears well under dress shirts or stands alone in laid-back style. Reinforced collar and lightweight feel give way to long-lasting shape and breathability. One less thing to worry about, rely on this tee to provide comfort and ease with every wear.

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "custom-t-shirt", + AllowCustomerReviews = true, + Price = 15M, + IsShipEnabled = true, + Weight = 4, + Length = 3, + Width = 3, + Height = 3, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Apparel").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductAttributeMappings = + { + new ProductAttributeMapping + { + ProductAttribute = _productAttributeRepository.Table.Single(x => x.Name == "Custom Text"), + TextPrompt = "Enter your text:", + AttributeControlType = AttributeControlType.TextBox, + IsRequired = true, + } + }, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Clothing"), + DisplayOrder = 1, + } + } + }; + allProducts.Add(productCustomTShirt); + productCustomTShirt.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_CustomTShirt.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productCustomTShirt.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(productCustomTShirt); + + + + + + + var productLeviJeans = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Levi's 511 Jeans", + Sku = "LV_511_JN", + ShortDescription = "Levi's Faded Black 511 Jeans ", + FullDescription = "

    Between a skinny and straight fit, our 511™ slim fit jeans are cut close without being too restricting. Slim throughout the thigh and leg opening for a long and lean look.

    • Slouch1y at top; sits below the waist
    • Slim through the leg, close at the thigh and straight to the ankle
    • Stretch for added comfort
    • Classic five-pocket styling
    • 99% Cotton, 1% Spandex, 11.2 oz. - Imported
    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "levis-skinny-511-jeans", + AllowCustomerReviews = true, + Price = 43.5M, + OldPrice = 55M, + IsShipEnabled = true, + Weight = 2, + Length = 2, + Width = 2, + Height = 2, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Apparel").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + TierPrices = + { + new TierPrice + { + Quantity = 3, + Price = 40 + }, + new TierPrice + { + Quantity = 6, + Price = 38 + }, + new TierPrice + { + Quantity = 10, + Price = 35 + } + }, + HasTierPrices = true, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Clothing"), + DisplayOrder = 1, + } + } + }; + allProducts.Add(productLeviJeans); + + productLeviJeans.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_LeviJeans_1.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName(productLeviJeans.Name)), + DisplayOrder = 1, + }); + productLeviJeans.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_LeviJeans_2.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName(productLeviJeans.Name)), + DisplayOrder = 2, + }); + _productRepository.Insert(productLeviJeans); + + + #endregion + + #region Accessories + + + var productObeyHat = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Obey Propaganda Hat", + Sku = "OB_HAT_PR", + ShortDescription = "", + FullDescription = "

    Printed poplin 5 panel camp hat with debossed leather patch and web closure

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "indiana-jones-shapeable-wool-hat", + AllowCustomerReviews = true, + Price = 30M, + IsShipEnabled = true, + Weight = 2, + Length = 2, + Width = 2, + Height = 2, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Apparel").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductAttributeMappings = + { + new ProductAttributeMapping + { + ProductAttribute = _productAttributeRepository.Table.Single(x => x.Name == "Size"), + AttributeControlType = AttributeControlType.DropdownList, + IsRequired = true, + ProductAttributeValues = + { + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "Small", + DisplayOrder = 1, + }, + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "Medium", + DisplayOrder = 2, + }, + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "Large", + DisplayOrder = 3, + }, + new ProductAttributeValue + { + AttributeValueType = AttributeValueType.Simple, + Name = "X-Large", + DisplayOrder = 4, + } + } + } + }, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Accessories"), + DisplayOrder = 1, + } + } + }; + allProducts.Add(productObeyHat); + productObeyHat.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_hat.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName(productObeyHat.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(productObeyHat); + + + + + + + + var productBelt = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Reversible Horseferry Check Belt", + Sku = "RH_CHK_BL", + ShortDescription = "Reversible belt in Horseferry check with smooth leather trim", + FullDescription = "

    Reversible belt in Horseferry check with smooth leather trim

    Leather lining, polished metal buckle

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "nike-golf-casual-belt", + AllowCustomerReviews = true, + Price = 45M, + IsShipEnabled = true, + Weight = 7, + Length = 7, + Width = 7, + Height = 7, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Apparel").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + ProductAvailabilityRangeId = productAvailabilityRange.Id, + StockQuantity = 0, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Accessories"), + DisplayOrder = 1, + } + } + }; + allProducts.Add(productBelt); + productBelt.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_Belt.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productBelt.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(productBelt); + + + + + + + var productSunglasses = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Ray Ban Aviator Sunglasses", + Sku = "RB_AVR_SG", + ShortDescription = "Aviator sunglasses are one of the first widely popularized styles of modern day sunwear.", + FullDescription = "

    Since 1937, Ray-Ban can genuinely claim the title as the world's leading sunglasses and optical eyewear brand. Combining the best of fashion and sports performance, the Ray-Ban line of Sunglasses delivers a truly classic style that will have you looking great today and for years to come.

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "ray-ban-aviator-sunglasses-rb-3025", + AllowCustomerReviews = true, + Price = 25M, + IsShipEnabled = true, + Weight = 7, + Length = 7, + Width = 7, + Height = 7, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Apparel").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Accessories"), + DisplayOrder = 1, + } + } + }; + allProducts.Add(productSunglasses); + productSunglasses.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_Sunglasses.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName(productSunglasses.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(productSunglasses); + + #endregion + + #region Digital Downloads + + + var downloadNightVision1 = new Download + { + DownloadGuid = Guid.NewGuid(), + ContentType = MimeTypes.ApplicationXZipCo, + DownloadBinary = File.ReadAllBytes(sampleDownloadsPath + "product_NightVision_1.zip"), + Extension = ".zip", + Filename = "Night_Vision_1", + IsNew = true, + }; + downloadService.InsertDownload(downloadNightVision1); + var downloadNightVision2 = new Download + { + DownloadGuid = Guid.NewGuid(), + ContentType = MimeTypes.TextPlain, + DownloadBinary = File.ReadAllBytes(sampleDownloadsPath + "product_NightVision_2.txt"), + Extension = ".txt", + Filename = "Night_Vision_1", + IsNew = true, + }; + downloadService.InsertDownload(downloadNightVision2); + var productNightVision = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Night Visions", + Sku = "NIGHT_VSN", + ShortDescription = "Night Visions is the debut studio album by American rock band Imagine Dragons.", + FullDescription = "

    Original Release Date: September 4, 2012

    Release Date: September 4, 2012

    Genre - Alternative rock, indie rock, electronic rock

    Label - Interscope/KIDinaKORNER

    Copyright: (C) 2011 Interscope Records

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "poker-face", + AllowCustomerReviews = true, + Price = 2.8M, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Downloadable Products").Id, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + IsDownload = true, + DownloadId = downloadNightVision1.Id, + DownloadActivationType = DownloadActivationType.WhenOrderIsPaid, + UnlimitedDownloads = true, + HasUserAgreement = false, + HasSampleDownload = true, + SampleDownloadId = downloadNightVision2.Id, + Published = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Digital downloads"), + DisplayOrder = 1, + } + } + }; + allProducts.Add(productNightVision); + productNightVision.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_NightVisions.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productNightVision.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(productNightVision); + + + + + + var downloadIfYouWait1 = new Download + { + DownloadGuid = Guid.NewGuid(), + ContentType = MimeTypes.ApplicationXZipCo, + DownloadBinary = File.ReadAllBytes(sampleDownloadsPath + "product_IfYouWait_1.zip"), + Extension = ".zip", + Filename = "If_You_Wait_1", + IsNew = true, + }; + downloadService.InsertDownload(downloadIfYouWait1); + var downloadIfYouWait2 = new Download + { + DownloadGuid = Guid.NewGuid(), + ContentType = MimeTypes.TextPlain, + DownloadBinary = File.ReadAllBytes(sampleDownloadsPath + "product_IfYouWait_2.txt"), + Extension = ".txt", + Filename = "If_You_Wait_1", + IsNew = true, + }; + downloadService.InsertDownload(downloadIfYouWait2); + var productIfYouWait = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "If You Wait (donation)", + Sku = "IF_YOU_WT", + ShortDescription = "If You Wait is the debut studio album by English indie pop band London Grammar", + FullDescription = "

    Original Release Date: September 6, 2013

    Genre - Electronica, dream pop downtempo, pop

    Label - Metal & Dust/Ministry of Sound

    Producer - Tim Bran, Roy Kerr London, Grammar

    Length - 43:22

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "single-ladies-put-a-ring-on-it", + CustomerEntersPrice = true, + MinimumCustomerEnteredPrice = 0.5M, + MaximumCustomerEnteredPrice = 100M, + AllowCustomerReviews = true, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Downloadable Products").Id, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + IsDownload = true, + DownloadId = downloadIfYouWait1.Id, + DownloadActivationType = DownloadActivationType.WhenOrderIsPaid, + UnlimitedDownloads = true, + HasUserAgreement = false, + HasSampleDownload = true, + SampleDownloadId = downloadIfYouWait2.Id, + Published = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Digital downloads"), + DisplayOrder = 1, + } + } + }; + allProducts.Add(productIfYouWait); + + productIfYouWait.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_IfYouWait.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productIfYouWait.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(productIfYouWait); + + + + + + var downloadScienceAndFaith = new Download + { + DownloadGuid = Guid.NewGuid(), + ContentType = MimeTypes.ApplicationXZipCo, + DownloadBinary = File.ReadAllBytes(sampleDownloadsPath + "product_ScienceAndFaith_1.zip"), + Extension = ".zip", + Filename = "Science_And_Faith", + IsNew = true, + }; + downloadService.InsertDownload(downloadScienceAndFaith); + var productScienceAndFaith = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Science & Faith", + Sku = "SCI_FAITH", + ShortDescription = "Science & Faith is the second studio album by Irish pop rock band The Script.", + FullDescription = "

    # Original Release Date: September 10, 2010
    # Label: RCA, Epic/Phonogenic(America)
    # Copyright: 2010 RCA Records.

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "the-battle-of-los-angeles", + AllowCustomerReviews = true, + CustomerEntersPrice = true, + MinimumCustomerEnteredPrice = 0.5M, + MaximumCustomerEnteredPrice = 1000M, + Price = decimal.Zero, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Downloadable Products").Id, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + IsDownload = true, + DownloadId = downloadScienceAndFaith.Id, + DownloadActivationType = DownloadActivationType.WhenOrderIsPaid, + UnlimitedDownloads = true, + HasUserAgreement = false, + Published = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Digital downloads"), + DisplayOrder = 1, + } + } + }; + allProducts.Add(productScienceAndFaith); + productScienceAndFaith.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_ScienceAndFaith.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productScienceAndFaith.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(productScienceAndFaith); + + + + #endregion + + #region Books + + var productFahrenheit = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Fahrenheit 451 by Ray Bradbury", + Sku = "FR_451_RB", + ShortDescription = "Fahrenheit 451 is a dystopian novel by Ray Bradbury published in 1953. It is regarded as one of his best works.", + FullDescription = "

    The novel presents a future American society where books are outlawed and firemen burn any that are found. The title refers to the temperature that Bradbury understood to be the autoignition point of paper.

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "best-grilling-recipes", + AllowCustomerReviews = true, + Price = 27M, + OldPrice = 30M, + IsShipEnabled = true, + IsFreeShipping = true, + Weight = 2, + Length = 2, + Width = 2, + Height = 2, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Books").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Books"), + DisplayOrder = 1, + } + } + }; + allProducts.Add(productFahrenheit); + productFahrenheit.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_Fahrenheit451.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productFahrenheit.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(productFahrenheit); + + + + var productFirstPrizePies = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "First Prize Pies", + Sku = "FIRST_PRP", + ShortDescription = "Allison Kave made pies as a hobby, until one day her boyfriend convinced her to enter a Brooklyn pie-making contest. She won. In fact, her pies were such a hit that she turned pro.", + FullDescription = "

    First Prize Pies, a boutique, made-to-order pie business that originated on New York's Lower East Side, has become synonymous with tempting and unusual confections. For the home baker who is passionate about seasonal ingredients and loves a creative approach to recipes, First Prize Pies serves up 52 weeks of seasonal and eclectic pastries in an interesting pie-a-week format. Clear instructions, technical tips and creative encouragement guide novice bakers as well as pie mavens. With its nostalgia-evoking photos of homemade pies fresh out of the oven, First Prize Pies will be as giftable as it is practical.

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "eatingwell-in-season", + AllowCustomerReviews = true, + Price = 51M, + OldPrice = 67M, + IsShipEnabled = true, + Weight = 2, + Length = 2, + Width = 2, + Height = 2, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Books").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Books"), + DisplayOrder = 1, + } + } + }; + allProducts.Add(productFirstPrizePies); + productFirstPrizePies.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_FirstPrizePies.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productFirstPrizePies.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(productFirstPrizePies); + + + + + + + + var productPrideAndPrejudice = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Pride and Prejudice", + Sku = "PRIDE_PRJ", + ShortDescription = "Pride and Prejudice is a novel of manners by Jane Austen, first published in 1813.", + FullDescription = "

    Set in England in the early 19th century, Pride and Prejudice tells the story of Mr and Mrs Bennet's five unmarried daughters after the rich and eligible Mr Bingley and his status-conscious friend, Mr Darcy, have moved into their neighbourhood. While Bingley takes an immediate liking to the eldest Bennet daughter, Jane, Darcy has difficulty adapting to local society and repeatedly clashes with the second-eldest Bennet daughter, Elizabeth.

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "the-best-skillet-recipes", + AllowCustomerReviews = true, + Price = 24M, + OldPrice = 35M, + IsShipEnabled = true, + Weight = 2, + Length = 2, + Width = 2, + Height = 2, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Books").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Books"), + DisplayOrder = 1, + } + } + }; + allProducts.Add(productPrideAndPrejudice); + productPrideAndPrejudice.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_PrideAndPrejudice.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(productPrideAndPrejudice.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(productPrideAndPrejudice); + + + + #endregion + + #region Jewelry + + + + var productElegantGemstoneNecklace = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Elegant Gemstone Necklace (rental)", + Sku = "EG_GEM_NL", + ShortDescription = "Classic and elegant gemstone necklace now available in our store", + FullDescription = "

    For those who like jewelry, creating their ownelegant jewelry from gemstone beads provides an economical way to incorporate genuine gemstones into your jewelry wardrobe. Manufacturers create beads from all kinds of precious gemstones and semi-precious gemstones, which are available in bead shops, craft stores, and online marketplaces.

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "diamond-pave-earrings", + AllowCustomerReviews = true, + IsRental = true, + RentalPriceLength = 1, + RentalPricePeriod = RentalPricePeriod.Days, + Price = 30M, + IsShipEnabled = true, + Weight = 2, + Length = 2, + Width = 2, + Height = 2, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Jewelry").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + MarkAsNew = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Jewelry"), + DisplayOrder = 1, + } + } + }; + allProducts.Add(productElegantGemstoneNecklace); + productElegantGemstoneNecklace.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_GemstoneNecklaces.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName(productElegantGemstoneNecklace.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(productElegantGemstoneNecklace); + + + + + + var productFlowerGirlBracelet = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Flower Girl Bracelet", + Sku = "FL_GIRL_B", + ShortDescription = "Personalised Flower Braceled", + FullDescription = "

    This is a great gift for your flower girl to wear on your wedding day. A delicate bracelet that is made with silver plated soldered cable chain, gives this bracelet a dainty look for young wrist. A Swarovski heart, shown in Rose, hangs off a silver plated flower. Hanging alongside the heart is a silver plated heart charm with Flower Girl engraved on both sides. This is a great style for the younger flower girl.

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "diamond-tennis-bracelet", + AllowCustomerReviews = true, + Price = 360M, + IsShipEnabled = true, + IsFreeShipping = true, + Weight = 2, + Length = 2, + Width = 2, + Height = 2, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Jewelry").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Jewelry"), + DisplayOrder = 1, + } + } + }; + allProducts.Add(productFlowerGirlBracelet); + productFlowerGirlBracelet.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_FlowerBracelet.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName(productFlowerGirlBracelet.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(productFlowerGirlBracelet); + + + + + + + + + var productEngagementRing = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Vintage Style Engagement Ring", + Sku = "VS_ENG_RN", + ShortDescription = "1.24 Carat (ctw) in 14K White Gold (Certified)", + FullDescription = "

    Dazzle her with this gleaming 14 karat white gold vintage proposal. A ravishing collection of 11 decadent diamonds come together to invigorate a superbly ornate gold shank. Total diamond weight on this antique style engagement ring equals 1 1/4 carat (ctw). Item includes diamond certificate.

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "vintage-style-three-stone-diamond-engagement-ring", + AllowCustomerReviews = true, + Price = 2100M, + IsShipEnabled = true, + Weight = 2, + Length = 2, + Width = 2, + Height = 2, + TaxCategoryId = _taxCategoryRepository.Table.Single(tc => tc.Name == "Jewelry").Id, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayStockAvailability = true, + LowStockActivity = LowStockActivity.DisableBuyButton, + BackorderMode = BackorderMode.NoBackorders, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + Published = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Jewelry"), + DisplayOrder = 1, + } + } + }; + allProducts.Add(productEngagementRing); + productEngagementRing.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_EngagementRing_1.jpg"), MimeTypes.ImagePJpeg, pictureService.GetPictureSeName(productEngagementRing.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(productEngagementRing); + + + + #endregion + + #region Gift Cards + + + var product25GiftCard = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "$25 Virtual Gift Card", + Sku = "VG_CR_025", + ShortDescription = "$25 Gift Card. Gift Cards must be redeemed through our site Web site toward the purchase of eligible products.", + FullDescription = "

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

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "25-virtual-gift-card", + AllowCustomerReviews = true, + Price = 25M, + IsGiftCard = true, + GiftCardType = GiftCardType.Virtual, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + Published = true, + ShowOnHomePage = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Gift Cards"), + DisplayOrder = 2, + } + } + }; + allProducts.Add(product25GiftCard); + product25GiftCard.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_25giftcart.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(product25GiftCard.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(product25GiftCard); + + + + + + var product50GiftCard = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "$50 Physical Gift Card", + Sku = "PG_CR_050", + ShortDescription = "$50 Gift Card. Gift Cards must be redeemed through our site Web site toward the purchase of eligible products.", + FullDescription = "

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

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "50-physical-gift-card", + AllowCustomerReviews = true, + Price = 50M, + IsGiftCard = true, + GiftCardType = GiftCardType.Physical, + IsShipEnabled = true, + IsFreeShipping = true, + DeliveryDateId = deliveryDate.Id, + Weight = 1, + Length = 1, + Width = 1, + Height = 1, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + Published = true, + MarkAsNew = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Gift Cards"), + DisplayOrder = 3, + } + } + }; + allProducts.Add(product50GiftCard); + product50GiftCard.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_50giftcart.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(product50GiftCard.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(product50GiftCard); + + + + + + var product100GiftCard = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "$100 Physical Gift Card", + Sku = "PG_CR_100", + ShortDescription = "$100 Gift Card. Gift Cards must be redeemed through our site Web site toward the purchase of eligible products.", + FullDescription = "

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

    ", + ProductTemplateId = productTemplateSimple.Id, + //SeName = "100-physical-gift-card", + AllowCustomerReviews = true, + Price = 100M, + IsGiftCard = true, + GiftCardType = GiftCardType.Physical, + IsShipEnabled = true, + DeliveryDateId = deliveryDate.Id, + Weight = 1, + Length = 1, + Width = 1, + Height = 1, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + Published = true, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + ProductCategories = + { + new ProductCategory + { + Category = _categoryRepository.Table.Single(c => c.Name == "Gift Cards"), + DisplayOrder = 4, + } + } + }; + allProducts.Add(product100GiftCard); + product100GiftCard.ProductPictures.Add(new ProductPicture + { + Picture = pictureService.InsertPicture(File.ReadAllBytes(sampleImagesPath + "product_100giftcart.jpeg"), MimeTypes.ImageJpeg, pictureService.GetPictureSeName(product100GiftCard.Name)), + DisplayOrder = 1, + }); + _productRepository.Insert(product100GiftCard); + + #endregion + + + + //search engine names + foreach (var product in allProducts) + { + _urlRecordRepository.Insert(new UrlRecord + { + EntityId = product.Id, + EntityName = "Product", + LanguageId = 0, + IsActive = true, + Slug = product.ValidateSeName("", product.Name, true) + }); + } + + + #region Related Products + + //related products + var relatedProducts = new List + { + new RelatedProduct + { + ProductId1 = productFlowerGirlBracelet.Id, + ProductId2 = productEngagementRing.Id, + }, + new RelatedProduct + { + ProductId1 = productFlowerGirlBracelet.Id, + ProductId2 = productElegantGemstoneNecklace.Id, + }, + new RelatedProduct + { + ProductId1 = productEngagementRing.Id, + ProductId2 = productFlowerGirlBracelet.Id, + }, + new RelatedProduct + { + ProductId1 = productEngagementRing.Id, + ProductId2 = productElegantGemstoneNecklace.Id, + }, + new RelatedProduct + { + ProductId1 = productElegantGemstoneNecklace.Id, + ProductId2 = productFlowerGirlBracelet.Id, + }, + new RelatedProduct + { + ProductId1 = productElegantGemstoneNecklace.Id, + ProductId2 = productEngagementRing.Id, + }, + new RelatedProduct + { + ProductId1 = productIfYouWait.Id, + ProductId2 = productNightVision.Id, + }, + new RelatedProduct + { + ProductId1 = productIfYouWait.Id, + ProductId2 = productScienceAndFaith.Id, + }, + new RelatedProduct + { + ProductId1 = productNightVision.Id, + ProductId2 = productIfYouWait.Id, + }, + new RelatedProduct + { + ProductId1 = productNightVision.Id, + ProductId2 = productScienceAndFaith.Id, + }, + new RelatedProduct + { + ProductId1 = productPrideAndPrejudice.Id, + ProductId2 = productFirstPrizePies.Id, + }, + new RelatedProduct + { + ProductId1 = productPrideAndPrejudice.Id, + ProductId2 = productFahrenheit.Id, + }, + new RelatedProduct + { + ProductId1 = productFirstPrizePies.Id, + ProductId2 = productPrideAndPrejudice.Id, + }, + new RelatedProduct + { + ProductId1 = productFirstPrizePies.Id, + ProductId2 = productFahrenheit.Id, + }, + new RelatedProduct + { + ProductId1 = productFahrenheit.Id, + ProductId2 = productFirstPrizePies.Id, + }, + new RelatedProduct + { + ProductId1 = productFahrenheit.Id, + ProductId2 = productPrideAndPrejudice.Id, + }, + new RelatedProduct + { + ProductId1 = productAsusN551JK.Id, + ProductId2 = productLenovoThinkpad.Id, + }, + new RelatedProduct + { + ProductId1 = productAsusN551JK.Id, + ProductId2 = productAppleMacBookPro.Id, + }, + new RelatedProduct + { + ProductId1 = productAsusN551JK.Id, + ProductId2 = productSamsungSeries.Id, + }, + new RelatedProduct + { + ProductId1 = productAsusN551JK.Id, + ProductId2 = productHpSpectre.Id, + }, + new RelatedProduct + { + ProductId1 = productLenovoThinkpad.Id, + ProductId2 = productAsusN551JK.Id, + }, + new RelatedProduct + { + ProductId1 = productLenovoThinkpad.Id, + ProductId2 = productAppleMacBookPro.Id, + }, + new RelatedProduct + { + ProductId1 = productLenovoThinkpad.Id, + ProductId2 = productSamsungSeries.Id, + }, + new RelatedProduct + { + ProductId1 = productLenovoThinkpad.Id, + ProductId2 = productHpEnvy.Id, + }, + new RelatedProduct + { + ProductId1 = productAppleMacBookPro.Id, + ProductId2 = productLenovoThinkpad.Id, + }, + new RelatedProduct + { + ProductId1 = productAppleMacBookPro.Id, + ProductId2 = productSamsungSeries.Id, + }, + new RelatedProduct + { + ProductId1 = productAppleMacBookPro.Id, + ProductId2 = productAsusN551JK.Id, + }, + new RelatedProduct + { + ProductId1 = productAppleMacBookPro.Id, + ProductId2 = productHpSpectre.Id, + }, + new RelatedProduct + { + ProductId1 = productHpSpectre.Id, + ProductId2 = productLenovoThinkpad.Id, + }, + new RelatedProduct + { + ProductId1 = productHpSpectre.Id, + ProductId2 = productSamsungSeries.Id, + }, + new RelatedProduct + { + ProductId1 = productHpSpectre.Id, + ProductId2 = productAsusN551JK.Id, + }, + new RelatedProduct + { + ProductId1 = productHpSpectre.Id, + ProductId2 = productHpEnvy.Id, + }, + new RelatedProduct + { + ProductId1 = productHpEnvy.Id, + ProductId2 = productAsusN551JK.Id, + }, + new RelatedProduct + { + ProductId1 = productHpEnvy.Id, + ProductId2 = productAppleMacBookPro.Id, + }, + new RelatedProduct + { + ProductId1 = productHpEnvy.Id, + ProductId2 = productHpSpectre.Id, + }, + new RelatedProduct + { + ProductId1 = productHpEnvy.Id, + ProductId2 = productSamsungSeries.Id, + }, + new RelatedProduct + { + ProductId1 = productSamsungSeries.Id, + ProductId2 = productAsusN551JK.Id, + }, + new RelatedProduct + { + ProductId1 = productSamsungSeries.Id, + ProductId2 = productAppleMacBookPro.Id, + }, + new RelatedProduct + { + ProductId1 = productSamsungSeries.Id, + ProductId2 = productHpEnvy.Id, + }, + new RelatedProduct + { + ProductId1 = productSamsungSeries.Id, + ProductId2 = productHpSpectre.Id, + }, + new RelatedProduct + { + ProductId1 = productLeica.Id, + ProductId2 = productHtcOneMini.Id, + }, + new RelatedProduct + { + ProductId1 = productLeica.Id, + ProductId2 = productNikonD5500DSLR.Id, + }, + new RelatedProduct + { + ProductId1 = productLeica.Id, + ProductId2 = productAppleICam.Id, + }, + new RelatedProduct + { + ProductId1 = productLeica.Id, + ProductId2 = productNokiaLumia.Id, + }, + new RelatedProduct + { + ProductId1 = productHtcOne.Id, + ProductId2 = productHtcOneMini.Id, + }, + new RelatedProduct + { + ProductId1 = productHtcOne.Id, + ProductId2 = productNokiaLumia.Id, + }, + new RelatedProduct + { + ProductId1 = productHtcOne.Id, + ProductId2 = productBeatsPill.Id, + }, + new RelatedProduct + { + ProductId1 = productHtcOne.Id, + ProductId2 = productPortableSoundSpeakers.Id, + }, + new RelatedProduct + { + ProductId1 = productHtcOneMini.Id, + ProductId2 = productHtcOne.Id, + }, + new RelatedProduct + { + ProductId1 = productHtcOneMini.Id, + ProductId2 = productNokiaLumia.Id, + }, + new RelatedProduct + { + ProductId1 = productHtcOneMini.Id, + ProductId2 = productBeatsPill.Id, + }, + new RelatedProduct + { + ProductId1 = productHtcOneMini.Id, + ProductId2 = productPortableSoundSpeakers.Id, + }, + new RelatedProduct + { + ProductId1 = productNokiaLumia.Id, + ProductId2 = productHtcOne.Id, + }, + new RelatedProduct + { + ProductId1 = productNokiaLumia.Id, + ProductId2 = productHtcOneMini.Id, + }, + new RelatedProduct + { + ProductId1 = productNokiaLumia.Id, + ProductId2 = productBeatsPill.Id, + }, + new RelatedProduct + { + ProductId1 = productNokiaLumia.Id, + ProductId2 = productPortableSoundSpeakers.Id, + }, + new RelatedProduct + { + ProductId1 = productAdidas.Id, + ProductId2 = productLeviJeans.Id, + }, + new RelatedProduct + { + ProductId1 = productAdidas.Id, + ProductId2 = productNikeFloral.Id, + }, + new RelatedProduct + { + ProductId1 = productAdidas.Id, + ProductId2 = productNikeZoom.Id, + }, + new RelatedProduct + { + ProductId1 = productAdidas.Id, + ProductId2 = productNikeTailwind.Id, + }, + new RelatedProduct + { + ProductId1 = productLeviJeans.Id, + ProductId2 = productAdidas.Id, + }, + new RelatedProduct + { + ProductId1 = productLeviJeans.Id, + ProductId2 = productNikeFloral.Id, + }, + new RelatedProduct + { + ProductId1 = productLeviJeans.Id, + ProductId2 = productNikeZoom.Id, + }, + new RelatedProduct + { + ProductId1 = productLeviJeans.Id, + ProductId2 = productNikeTailwind.Id, + }, + + new RelatedProduct + { + ProductId1 = productCustomTShirt.Id, + ProductId2 = productLeviJeans.Id, + }, + new RelatedProduct + { + ProductId1 = productCustomTShirt.Id, + ProductId2 = productNikeTailwind.Id, + }, + new RelatedProduct + { + ProductId1 = productCustomTShirt.Id, + ProductId2 = productOversizedWomenTShirt.Id, + }, + new RelatedProduct + { + ProductId1 = productCustomTShirt.Id, + ProductId2 = productObeyHat.Id, + }, + new RelatedProduct + { + ProductId1 = productDigitalStorm.Id, + ProductId2 = productBuildComputer.Id, + }, + new RelatedProduct + { + ProductId1 = productDigitalStorm.Id, + ProductId2 = productLenovoIdeaCentre.Id, + }, + new RelatedProduct + { + ProductId1 = productDigitalStorm.Id, + ProductId2 = productLenovoThinkpad.Id, + }, + new RelatedProduct + { + ProductId1 = productDigitalStorm.Id, + ProductId2 = productAppleMacBookPro.Id, + }, + + + new RelatedProduct + { + ProductId1 = productLenovoIdeaCentre.Id, + ProductId2 = productBuildComputer.Id, + }, + new RelatedProduct + { + ProductId1 = productLenovoIdeaCentre.Id, + ProductId2 = productDigitalStorm.Id, + }, + new RelatedProduct + { + ProductId1 = productLenovoIdeaCentre.Id, + ProductId2 = productLenovoThinkpad.Id, + }, + new RelatedProduct + { + ProductId1 = productLenovoIdeaCentre.Id, + ProductId2 = productAppleMacBookPro.Id, + }, + }; + _relatedProductRepository.Insert(relatedProducts); + + #endregion + + #region Product Tags + + //product tags + AddProductTag(product25GiftCard, "nice"); + AddProductTag(product25GiftCard, "gift"); + AddProductTag(productNikeTailwind, "cool"); + AddProductTag(productNikeTailwind, "apparel"); + AddProductTag(productNikeTailwind, "shirt"); + AddProductTag(productBeatsPill, "computer"); + AddProductTag(productBeatsPill, "cool"); + AddProductTag(productNikeFloral, "cool"); + AddProductTag(productNikeFloral, "shoes"); + AddProductTag(productNikeFloral, "apparel"); + AddProductTag(productAdobePhotoshop, "computer"); + AddProductTag(productAdobePhotoshop, "awesome"); + AddProductTag(productUniversalTabletCover, "computer"); + AddProductTag(productUniversalTabletCover, "cool"); + AddProductTag(productOversizedWomenTShirt, "cool"); + AddProductTag(productOversizedWomenTShirt, "apparel"); + AddProductTag(productOversizedWomenTShirt, "shirt"); + AddProductTag(productAppleMacBookPro, "compact"); + AddProductTag(productAppleMacBookPro, "awesome"); + AddProductTag(productAppleMacBookPro, "computer"); + AddProductTag(productAsusN551JK, "compact"); + AddProductTag(productAsusN551JK, "awesome"); + AddProductTag(productAsusN551JK, "computer"); + AddProductTag(productFahrenheit, "awesome"); + AddProductTag(productFahrenheit, "book"); + AddProductTag(productFahrenheit, "nice"); + AddProductTag(productHtcOne, "cell"); + AddProductTag(productHtcOne, "compact"); + AddProductTag(productHtcOne, "awesome"); + AddProductTag(productBuildComputer, "awesome"); + AddProductTag(productBuildComputer, "computer"); + AddProductTag(productNikonD5500DSLR, "cool"); + AddProductTag(productNikonD5500DSLR, "camera"); + AddProductTag(productLeica, "camera"); + AddProductTag(productLeica, "cool"); + AddProductTag(productDigitalStorm, "cool"); + AddProductTag(productDigitalStorm, "computer"); + AddProductTag(productWindows8Pro, "awesome"); + AddProductTag(productWindows8Pro, "computer"); + AddProductTag(productCustomTShirt, "cool"); + AddProductTag(productCustomTShirt, "shirt"); + AddProductTag(productCustomTShirt, "apparel"); + AddProductTag(productElegantGemstoneNecklace, "jewelry"); + AddProductTag(productElegantGemstoneNecklace, "awesome"); + AddProductTag(productFlowerGirlBracelet, "awesome"); + AddProductTag(productFlowerGirlBracelet, "jewelry"); + AddProductTag(productFirstPrizePies, "book"); + AddProductTag(productAdidas, "cool"); + AddProductTag(productAdidas, "shoes"); + AddProductTag(productAdidas, "apparel"); + AddProductTag(productLenovoIdeaCentre, "awesome"); + AddProductTag(productLenovoIdeaCentre, "computer"); + AddProductTag(productSamsungSeries, "nice"); + AddProductTag(productSamsungSeries, "computer"); + AddProductTag(productSamsungSeries, "compact"); + AddProductTag(productHpSpectre, "nice"); + AddProductTag(productHpSpectre, "computer"); + AddProductTag(productHpEnvy, "computer"); + AddProductTag(productHpEnvy, "cool"); + AddProductTag(productHpEnvy, "compact"); + AddProductTag(productObeyHat, "apparel"); + AddProductTag(productObeyHat, "cool"); + AddProductTag(productLeviJeans, "cool"); + AddProductTag(productLeviJeans, "jeans"); + AddProductTag(productLeviJeans, "apparel"); + AddProductTag(productSoundForge, "game"); + AddProductTag(productSoundForge, "computer"); + AddProductTag(productSoundForge, "cool"); + AddProductTag(productNightVision, "awesome"); + AddProductTag(productNightVision, "digital"); + AddProductTag(productSunglasses, "apparel"); + AddProductTag(productSunglasses, "cool"); + AddProductTag(productHtcOneMini, "awesome"); + AddProductTag(productHtcOneMini, "compact"); + AddProductTag(productHtcOneMini, "cell"); + AddProductTag(productIfYouWait, "digital"); + AddProductTag(productIfYouWait, "awesome"); + AddProductTag(productNokiaLumia, "awesome"); + AddProductTag(productNokiaLumia, "cool"); + AddProductTag(productNokiaLumia, "camera"); + AddProductTag(productScienceAndFaith, "digital"); + AddProductTag(productScienceAndFaith, "awesome"); + AddProductTag(productPrideAndPrejudice, "book"); + AddProductTag(productLenovoThinkpad, "awesome"); + AddProductTag(productLenovoThinkpad, "computer"); + AddProductTag(productLenovoThinkpad, "compact"); + AddProductTag(productNikeZoom, "jeans"); + AddProductTag(productNikeZoom, "cool"); + AddProductTag(productNikeZoom, "apparel"); + AddProductTag(productEngagementRing, "jewelry"); + AddProductTag(productEngagementRing, "awesome"); + + + #endregion + + #region Reviews + + //reviews + var random = new Random(); + foreach (var product in allProducts) + { + if (product.ProductType != ProductType.SimpleProduct) + continue; + + //only 3 of 4 products will have reviews + if (random.Next(4) == 3) + continue; + + //rating from 4 to 5 + var rating = random.Next(4, 6); + product.ProductReviews.Add(new ProductReview + { + CustomerId = defaultCustomer.Id, + ProductId = product.Id, + StoreId = defaultStore.Id, + IsApproved = true, + Title = "Some sample review", + ReviewText = string.Format("This sample review is for the {0}. I've been waiting for this product to be available. It is priced just right.", product.Name), + //random (4 or 5) + Rating = rating, + HelpfulYesTotal = 0, + HelpfulNoTotal = 0, + CreatedOnUtc = DateTime.UtcNow + }); + product.ApprovedRatingSum = rating; + product.ApprovedTotalReviews = product.ProductReviews.Count; + + } + _productRepository.Update(allProducts); + + #endregion + + #region Stock quantity history + + foreach (var product in allProducts) + { + if (product.StockQuantity > 0) + _stockQuantityHistoryRepository.Insert(new StockQuantityHistory + { + ProductId = product.Id, + WarehouseId = product.WarehouseId > 0 ? (int?)product.WarehouseId : null, + QuantityAdjustment = product.StockQuantity, + StockQuantity = product.StockQuantity, + Message = "The stock quantity has been edited", + CreatedOnUtc = DateTime.UtcNow + }); + } + + #endregion + } + + protected virtual void InstallForums() + { + var forumGroup = new ForumGroup + { + Name = "General", + DisplayOrder = 5, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + }; + + _forumGroupRepository.Insert(forumGroup); + + var newProductsForum = new Forum + { + ForumGroup = forumGroup, + Name = "New Products", + Description = "Discuss new products and industry trends", + NumTopics = 0, + NumPosts = 0, + LastPostCustomerId = 0, + LastPostTime = null, + DisplayOrder = 1, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + }; + _forumRepository.Insert(newProductsForum); + + var mobileDevicesForum = new Forum + { + ForumGroup = forumGroup, + Name = "Mobile Devices Forum", + Description = "Discuss the mobile phone market", + NumTopics = 0, + NumPosts = 0, + LastPostCustomerId = 0, + LastPostTime = null, + DisplayOrder = 10, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + }; + _forumRepository.Insert(mobileDevicesForum); + + var packagingShippingForum = new Forum + { + ForumGroup = forumGroup, + Name = "Packaging & Shipping", + Description = "Discuss packaging & shipping", + NumTopics = 0, + NumPosts = 0, + LastPostTime = null, + DisplayOrder = 20, + CreatedOnUtc = DateTime.UtcNow, + UpdatedOnUtc = DateTime.UtcNow, + }; + _forumRepository.Insert(packagingShippingForum); + } + + protected virtual void InstallDiscounts() + { + var discounts = new List + { + new Discount + { + Name = "Sample discount with coupon code", + DiscountType = DiscountType.AssignedToSkus, + DiscountLimitation = DiscountLimitationType.Unlimited, + UsePercentage = false, + DiscountAmount = 10, + RequiresCouponCode = true, + CouponCode = "123", + }, + new Discount + { + Name = "'20% order total' discount", + DiscountType = DiscountType.AssignedToOrderTotal, + DiscountLimitation = DiscountLimitationType.Unlimited, + UsePercentage = true, + DiscountPercentage = 20, + StartDateUtc = new DateTime(2010,1,1), + EndDateUtc = new DateTime(2020,1,1), + RequiresCouponCode = true, + CouponCode = "456", + }, + }; + _discountRepository.Insert(discounts); + } + + protected virtual void InstallBlogPosts(string defaultUserEmail) + { + var defaultLanguage = _languageRepository.Table.FirstOrDefault(); + + var blogPosts = new List + { + new BlogPost + { + AllowComments = true, + Language = defaultLanguage, + Title = "How a blog can help your growing e-Commerce business", + BodyOverview = "

    When you start an online business, your main aim is to sell the products, right? As a business owner, you want to showcase your store to more audience. So, you decide to go on social media, why? Because everyone is doing it, then why shouldn’t you? It is tempting as everyone is aware of the hype that it is the best way to market your brand.

    Do you know having a blog for your online store can be very helpful? Many businesses do not understand the importance of having a blog because they don’t have time to post quality content.

    Today, we will talk about how a blog can play an important role for the growth of your e-Commerce business. Later, we will also discuss some tips that will be helpful to you for writing business related blog posts.

    ", + Body = "

    When you start an online business, your main aim is to sell the products, right? As a business owner, you want to showcase your store to more audience. So, you decide to go on social media, why? Because everyone is doing it, then why shouldn’t you? It is tempting as everyone is aware of the hype that it is the best way to market your brand.

    Do you know having a blog for your online store can be very helpful? Many businesses do not understand the importance of having a blog because they don’t have time to post quality content.

    Today, we will talk about how a blog can play an important role for the growth of your e-Commerce business. Later, we will also discuss some tips that will be helpful to you for writing business related blog posts.

    1) Blog is useful in educating your customers

    Blogging is one of the best way by which you can educate your customers about your products/services that you offer. This helps you as a business owner to bring more value to your brand. When you provide useful information to the customers about your products, they are more likely to buy products from you. You can use your blog for providing tutorials in regard to the use of your products.

    For example: If you have an online store that offers computer parts. You can write tutorials about how to build a computer or how to make your computer’s performance better. While talking about these things, you can mention products in the tutorials and provide link to your products within the blog post from your website. Your potential customers might get different ideas of using your product and will likely to buy products from your online store.

    2) Blog helps your business in Search Engine Optimization (SEO)

    Blog posts create more internal links to your website which helps a lot in SEO. Blog is a great way to have quality content on your website related to your products/services which is indexed by all major search engines like Google, Bing and Yahoo. The more original content you write in your blog post, the better ranking you will get in search engines. SEO is an on-going process and posting blog posts regularly keeps your site active all the time which is beneficial when it comes to search engine optimization.

    For example: Let’s say you sell “Sony Television Model XYZ” and you regularly publish blog posts about your product. Now, whenever someone searches for “Sony Television Model XYZ”, Google will crawl on your website knowing that you have something to do with this particular product. Hence, your website will show up on the search result page whenever this item is being searched.

    3) Blog helps in boosting your sales by convincing the potential customers to buy

    If you own an online business, there are so many ways you can share different stories with your audience in regard your products/services that you offer. Talk about how you started your business, share stories that educate your audience about what’s new in your industry, share stories about how your product/service was beneficial to someone or share anything that you think your audience might find interesting (it does not have to be related to your product). This kind of blogging shows that you are an expert in your industry and interested in educating your audience. It sets you apart in the competitive market. This gives you an opportunity to showcase your expertise by educating the visitors and it can turn your audience into buyers.

    Fun Fact: Did you know that 92% of companies who decided to blog acquired customers through their blog?

    nopCommerce is great e-Commerce solution that also offers a variety of CMS features including blog. A store owner has full access for managing the blog posts and related comments.

    ", + Tags = "e-commerce, blog, moey", + CreatedOnUtc = DateTime.UtcNow, + }, + new BlogPost + { + AllowComments = true, + Language = defaultLanguage, + Title = "Why your online store needs a wish list", + BodyOverview = "

    What comes to your mind, when you hear the term” wish list”? The application of this feature is exactly how it sounds like: a list of things that you wish to get. As an online store owner, would you like your customers to be able to save products in a wish list so that they review or buy them later? Would you like your customers to be able to share their wish list with friends and family for gift giving?

    Offering your customers a feature of wish list as part of shopping cart is a great way to build loyalty to your store site. Having the feature of wish list on a store site allows online businesses to engage with their customers in a smart way as it allows the shoppers to create a list of what they desire and their preferences for future purchase.

    ", + Body = "

    What comes to your mind, when you hear the term” wish list”? The application of this feature is exactly how it sounds like: a list of things that you wish to get. As an online store owner, would you like your customers to be able to save products in a wish list so that they review or buy them later? Would you like your customers to be able to share their wish list with friends and family for gift giving?

    Offering your customers a feature of wish list as part of shopping cart is a great way to build loyalty to your store site. Having the feature of wish list on a store site allows online businesses to engage with their customers in a smart way as it allows the shoppers to create a list of what they desire and their preferences for future purchase.

    Does every e-Commerce store needs a wish list? The answer to this question in most cases is yes, because of the following reasons:

    Understanding the needs of your customers - A wish list is a great way to know what is in your customer’s mind. Try to think the purchase history as a small portion of the customer’s preferences. But, the wish list is like a wide open door that can give any online business a lot of valuable information about their customer and what they like or desire.

    Shoppers like to share their wish list with friends and family - Providing your customers a way to email their wish list to their friends and family is a pleasant way to make online shopping enjoyable for the shoppers. It is always a good idea to make the wish list sharable by a unique link so that it can be easily shared though different channels like email or on social media sites.

    Wish list can be a great marketing tool – Another way to look at wish list is a great marketing tool because it is extremely targeted and the recipients are always motivated to use it. For example: when your younger brother tells you that his wish list is on a certain e-Commerce store. What is the first thing you are going to do? You are most likely to visit the e-Commerce store, check out the wish list and end up buying something for your younger brother.

    So, how a wish list is a marketing tool? The reason is quite simple, it introduce your online store to new customers just how it is explained in the above example.

    Encourage customers to return to the store site – Having a feature of wish list on the store site can increase the return traffic because it encourages customers to come back and buy later. Allowing the customers to save the wish list to their online accounts gives them a reason return to the store site and login to the account at any time to view or edit the wish list items.

    Wish list can be used for gifts for different occasions like weddings or birthdays. So, what kind of benefits a gift-giver gets from a wish list?

    • It gives them a surety that they didn’t buy a wrong gift
    • It guarantees that the recipient will like the gift
    • It avoids any awkward moments when the recipient unwraps the gift and as a gift-giver you got something that the recipient do not want

    Wish list is a great feature to have on a store site – So, what kind of benefits a business owner gets from a wish list

    • It is a great way to advertise an online store as many people do prefer to shop where their friend or family shop online
    • It allows the current customers to return to the store site and open doors for the new customers
    • It allows store admins to track what’s in customers wish list and run promotions accordingly to target specific customer segments

    nopCommerce offers the feature of wish list that allows customers to create a list of products that they desire or planning to buy in future.

    ", + Tags = "e-commerce, nopCommerce, sample tag, money", + CreatedOnUtc = DateTime.UtcNow.AddSeconds(1), + }, + }; + _blogPostRepository.Insert(blogPosts); + + //search engine names + foreach (var blogPost in blogPosts) + { + _urlRecordRepository.Insert(new UrlRecord + { + EntityId = blogPost.Id, + EntityName = "BlogPost", + LanguageId = blogPost.LanguageId, + IsActive = true, + Slug = blogPost.ValidateSeName("", blogPost.Title, true) + }); + } + + //comments + var defaultCustomer = _customerRepository.Table.FirstOrDefault(x => x.Email == defaultUserEmail); + if (defaultCustomer == null) + throw new Exception("Cannot load default customer"); + + //default store + var defaultStore = _storeRepository.Table.FirstOrDefault(); + if (defaultStore == null) + throw new Exception("No default store could be loaded"); + + foreach (var blogPost in blogPosts) + { + blogPost.BlogComments.Add(new BlogComment + { + BlogPostId = blogPost.Id, + CustomerId = defaultCustomer.Id, + CommentText = "This is a sample comment for this blog post", + IsApproved = true, + StoreId = defaultStore.Id, + CreatedOnUtc = DateTime.UtcNow + }); + } + _blogPostRepository.Update(blogPosts); + } + + protected virtual void InstallNews(string defaultUserEmail) + { + var defaultLanguage = _languageRepository.Table.FirstOrDefault(); + + var news = new List + { + new NewsItem + { + AllowComments = true, + Language = defaultLanguage, + Title = "About nopCommerce", + Short = "It's stable and highly usable. From downloads to documentation, www.nopCommerce.com offers a comprehensive base of information, resources, and support to the nopCommerce community.", + Full = "

    For full feature list go to nopCommerce.com

    Providing outstanding custom search engine optimization, web development services and e-commerce development solutions to our clients at a fair price in a professional manner.

    ", + Published = true, + CreatedOnUtc = DateTime.UtcNow, + }, + new NewsItem + { + AllowComments = true, + Language = defaultLanguage, + Title = "nopCommerce new release!", + Short = "nopCommerce includes everything you need to begin your e-commerce online store. We have thought of everything and it's all included! nopCommerce is a fully customizable shopping cart", + Full = "

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

    ", + Published = true, + CreatedOnUtc = DateTime.UtcNow.AddSeconds(1), + }, + new NewsItem + { + AllowComments = true, + Language = defaultLanguage, + Title = "New online store is open!", + Short = "The new nopCommerce store is open now! We are very excited to offer our new range of products. We will be constantly adding to our range so please register on our site.", + Full = "

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

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

    ", + Published = true, + CreatedOnUtc = DateTime.UtcNow.AddSeconds(2), + }, + + }; + _newsItemRepository.Insert(news); + + //search engine names + foreach (var newsItem in news) + { + _urlRecordRepository.Insert(new UrlRecord + { + EntityId = newsItem.Id, + EntityName = "NewsItem", + LanguageId = newsItem.LanguageId, + IsActive = true, + Slug = newsItem.ValidateSeName("", newsItem.Title, true) + }); + } + + //comments + var defaultCustomer = _customerRepository.Table.FirstOrDefault(x => x.Email == defaultUserEmail); + if (defaultCustomer == null) + throw new Exception("Cannot load default customer"); + + //default store + var defaultStore = _storeRepository.Table.FirstOrDefault(); + if (defaultStore == null) + throw new Exception("No default store could be loaded"); + + foreach (var newsItem in news) + { + newsItem.NewsComments.Add(new NewsComment + { + NewsItemId = newsItem.Id, + CustomerId = defaultCustomer.Id, + CommentTitle = "Sample comment title", + CommentText = "This is a sample comment...", + IsApproved = true, + StoreId = defaultStore.Id, + CreatedOnUtc = DateTime.UtcNow + }); + } + _newsItemRepository.Update(news); + } + + protected virtual void InstallPolls() + { + var defaultLanguage = _languageRepository.Table.FirstOrDefault(); + var poll1 = new Poll + { + Language = defaultLanguage, + Name = "Do you like nopCommerce?", + SystemKeyword = "", + Published = true, + ShowOnHomePage = true, + DisplayOrder = 1, + }; + poll1.PollAnswers.Add(new PollAnswer + { + Name = "Excellent", + DisplayOrder = 1, + }); + poll1.PollAnswers.Add(new PollAnswer + { + Name = "Good", + DisplayOrder = 2, + }); + poll1.PollAnswers.Add(new PollAnswer + { + Name = "Poor", + DisplayOrder = 3, + }); + poll1.PollAnswers.Add(new PollAnswer + { + Name = "Very bad", + DisplayOrder = 4, + }); + _pollRepository.Insert(poll1); + } + + protected virtual void InstallActivityLogTypes() + { + var activityLogTypes = new List + { + //admin area activities + new ActivityLogType + { + SystemKeyword = "AddNewAddressAttribute", + Enabled = true, + Name = "Add a new address attribute" + }, + new ActivityLogType + { + SystemKeyword = "AddNewAddressAttributeValue", + Enabled = true, + Name = "Add a new address attribute value" + }, + new ActivityLogType + { + SystemKeyword = "AddNewAffiliate", + Enabled = true, + Name = "Add a new affiliate" + }, + new ActivityLogType + { + SystemKeyword = "AddNewBlogPost", + Enabled = true, + Name = "Add a new blog post" + }, + new ActivityLogType + { + SystemKeyword = "AddNewCampaign", + Enabled = true, + Name = "Add a new campaign" + }, + new ActivityLogType + { + SystemKeyword = "AddNewCategory", + Enabled = true, + Name = "Add a new category" + }, + new ActivityLogType + { + SystemKeyword = "AddNewCheckoutAttribute", + Enabled = true, + Name = "Add a new checkout attribute" + }, + new ActivityLogType + { + SystemKeyword = "AddNewCountry", + Enabled = true, + Name = "Add a new country" + }, + new ActivityLogType + { + SystemKeyword = "AddNewCurrency", + Enabled = true, + Name = "Add a new currency" + }, + new ActivityLogType + { + SystemKeyword = "AddNewCustomer", + Enabled = true, + Name = "Add a new customer" + }, + new ActivityLogType + { + SystemKeyword = "AddNewCustomerAttribute", + Enabled = true, + Name = "Add a new customer attribute" + }, + new ActivityLogType + { + SystemKeyword = "AddNewCustomerAttributeValue", + Enabled = true, + Name = "Add a new customer attribute value" + }, + new ActivityLogType + { + SystemKeyword = "AddNewCustomerRole", + Enabled = true, + Name = "Add a new customer role" + }, + new ActivityLogType + { + SystemKeyword = "AddCustomerRewardPoints", + Enabled = true, + Name = "Add customer reward points" + }, + new ActivityLogType + { + SystemKeyword = "AddNewDiscount", + Enabled = true, + Name = "Add a new discount" + }, + new ActivityLogType + { + SystemKeyword = "AddNewEmailAccount", + Enabled = true, + Name = "Add a new email account" + }, + new ActivityLogType + { + SystemKeyword = "AddNewGiftCard", + Enabled = true, + Name = "Add a new gift card" + }, + new ActivityLogType + { + SystemKeyword = "AddNewLanguage", + Enabled = true, + Name = "Add a new language" + }, + new ActivityLogType + { + SystemKeyword = "AddNewManufacturer", + Enabled = true, + Name = "Add a new manufacturer" + }, + new ActivityLogType + { + SystemKeyword = "AddNewMeasureDimension", + Enabled = true, + Name = "Add a new measure dimension" + }, + new ActivityLogType + { + SystemKeyword = "AddNewMeasureWeight", + Enabled = true, + Name = "Add a new measure weight" + }, + new ActivityLogType + { + SystemKeyword = "AddNewNews", + Enabled = true, + Name = "Add a new news" + }, + new ActivityLogType + { + SystemKeyword = "AddNewProduct", + Enabled = true, + Name = "Add a new product" + }, + new ActivityLogType + { + SystemKeyword = "AddNewProductAttribute", + Enabled = true, + Name = "Add a new product attribute" + }, + new ActivityLogType + { + SystemKeyword = "AddNewSetting", + Enabled = true, + Name = "Add a new setting" + }, + new ActivityLogType + { + SystemKeyword = "AddNewSpecAttribute", + Enabled = true, + Name = "Add a new specification attribute" + }, + new ActivityLogType + { + SystemKeyword = "AddNewStateProvince", + Enabled = true, + Name = "Add a new state or province" + }, + new ActivityLogType + { + SystemKeyword = "AddNewStore", + Enabled = true, + Name = "Add a new store" + }, + new ActivityLogType + { + SystemKeyword = "AddNewTopic", + Enabled = true, + Name = "Add a new topic" + }, + new ActivityLogType + { + SystemKeyword = "AddNewVendor", + Enabled = true, + Name = "Add a new vendor" + }, + new ActivityLogType + { + SystemKeyword = "AddNewWarehouse", + Enabled = true, + Name = "Add a new warehouse" + }, + new ActivityLogType + { + SystemKeyword = "AddNewWidget", + Enabled = true, + Name = "Add a new widget" + }, + new ActivityLogType + { + SystemKeyword = "DeleteActivityLog", + Enabled = true, + Name = "Delete activity log" + }, + new ActivityLogType + { + SystemKeyword = "DeleteAddressAttribute", + Enabled = true, + Name = "Delete an address attribute" + }, + new ActivityLogType + { + SystemKeyword = "DeleteAddressAttributeValue", + Enabled = true, + Name = "Delete an address attribute value" + }, + new ActivityLogType + { + SystemKeyword = "DeleteAffiliate", + Enabled = true, + Name = "Delete an affiliate" + }, + new ActivityLogType + { + SystemKeyword = "DeleteBlogPost", + Enabled = true, + Name = "Delete a blog post" + }, + new ActivityLogType + { + SystemKeyword = "DeleteBlogPostComment", + Enabled = true, + Name = "Delete a blog post comment" + }, + new ActivityLogType + { + SystemKeyword = "DeleteCampaign", + Enabled = true, + Name = "Delete a campaign" + }, + new ActivityLogType + { + SystemKeyword = "DeleteCategory", + Enabled = true, + Name = "Delete category" + }, + new ActivityLogType + { + SystemKeyword = "DeleteCheckoutAttribute", + Enabled = true, + Name = "Delete a checkout attribute" + }, + new ActivityLogType + { + SystemKeyword = "DeleteCountry", + Enabled = true, + Name = "Delete a country" + }, + new ActivityLogType + { + SystemKeyword = "DeleteCurrency", + Enabled = true, + Name = "Delete a currency" + }, + new ActivityLogType + { + SystemKeyword = "DeleteCustomer", + Enabled = true, + Name = "Delete a customer" + }, + new ActivityLogType + { + SystemKeyword = "DeleteCustomerAttribute", + Enabled = true, + Name = "Delete a customer attribute" + }, + new ActivityLogType + { + SystemKeyword = "DeleteCustomerAttributeValue", + Enabled = true, + Name = "Delete a customer attribute value" + }, + new ActivityLogType + { + SystemKeyword = "DeleteCustomerRole", + Enabled = true, + Name = "Delete a customer role" + }, + new ActivityLogType + { + SystemKeyword = "DeleteDiscount", + Enabled = true, + Name = "Delete a discount" + }, + new ActivityLogType + { + SystemKeyword = "DeleteEmailAccount", + Enabled = true, + Name = "Delete an email account" + }, + new ActivityLogType + { + SystemKeyword = "DeleteGiftCard", + Enabled = true, + Name = "Delete a gift card" + }, + new ActivityLogType + { + SystemKeyword = "DeleteLanguage", + Enabled = true, + Name = "Delete a language" + }, + new ActivityLogType + { + SystemKeyword = "DeleteManufacturer", + Enabled = true, + Name = "Delete a manufacturer" + }, + new ActivityLogType + { + SystemKeyword = "DeleteMeasureDimension", + Enabled = true, + Name = "Delete a measure dimension" + }, + new ActivityLogType + { + SystemKeyword = "DeleteMeasureWeight", + Enabled = true, + Name = "Delete a measure weight" + }, + new ActivityLogType + { + SystemKeyword = "DeleteMessageTemplate", + Enabled = true, + Name = "Delete a message template" + }, + new ActivityLogType + { + SystemKeyword = "DeleteNews", + Enabled = true, + Name = "Delete a news" + }, + new ActivityLogType + { + SystemKeyword = "DeleteNewsComment", + Enabled = true, + Name = "Delete a news comment" + }, + new ActivityLogType + { + SystemKeyword = "DeleteOrder", + Enabled = true, + Name = "Delete an order" + }, + new ActivityLogType + { + SystemKeyword = "DeleteProduct", + Enabled = true, + Name = "Delete a product" + }, + new ActivityLogType + { + SystemKeyword = "DeleteProductAttribute", + Enabled = true, + Name = "Delete a product attribute" + }, + new ActivityLogType + { + SystemKeyword = "DeleteProductReview", + Enabled = true, + Name = "Delete a product review" + }, + new ActivityLogType + { + SystemKeyword = "DeleteReturnRequest", + Enabled = true, + Name = "Delete a return request" + }, + new ActivityLogType + { + SystemKeyword = "DeleteSetting", + Enabled = true, + Name = "Delete a setting" + }, + new ActivityLogType + { + SystemKeyword = "DeleteSpecAttribute", + Enabled = true, + Name = "Delete a specification attribute" + }, + new ActivityLogType + { + SystemKeyword = "DeleteStateProvince", + Enabled = true, + Name = "Delete a state or province" + }, + new ActivityLogType + { + SystemKeyword = "DeleteStore", + Enabled = true, + Name = "Delete a store" + }, + new ActivityLogType + { + SystemKeyword = "DeleteTopic", + Enabled = true, + Name = "Delete a topic" + }, + new ActivityLogType + { + SystemKeyword = "DeleteVendor", + Enabled = true, + Name = "Delete a vendor" + }, + new ActivityLogType + { + SystemKeyword = "DeleteWarehouse", + Enabled = true, + Name = "Delete a warehouse" + }, + new ActivityLogType + { + SystemKeyword = "DeleteWidget", + Enabled = true, + Name = "Delete a widget" + }, + new ActivityLogType + { + SystemKeyword = "EditActivityLogTypes", + Enabled = true, + Name = "Edit activity log types" + }, + new ActivityLogType + { + SystemKeyword = "EditAddressAttribute", + Enabled = true, + Name = "Edit an address attribute" + }, + new ActivityLogType + { + SystemKeyword = "EditAddressAttributeValue", + Enabled = true, + Name = "Edit an address attribute value" + }, + new ActivityLogType + { + SystemKeyword = "EditAffiliate", + Enabled = true, + Name = "Edit an affiliate" + }, + new ActivityLogType + { + SystemKeyword = "EditBlogPost", + Enabled = true, + Name = "Edit a blog post" + }, + new ActivityLogType + { + SystemKeyword = "EditCampaign", + Enabled = true, + Name = "Edit a campaign" + }, + new ActivityLogType + { + SystemKeyword = "EditCategory", + Enabled = true, + Name = "Edit category" + }, + new ActivityLogType + { + SystemKeyword = "EditCheckoutAttribute", + Enabled = true, + Name = "Edit a checkout attribute" + }, + new ActivityLogType + { + SystemKeyword = "EditCountry", + Enabled = true, + Name = "Edit a country" + }, + new ActivityLogType + { + SystemKeyword = "EditCurrency", + Enabled = true, + Name = "Edit a currency" + }, + new ActivityLogType + { + SystemKeyword = "EditCustomer", + Enabled = true, + Name = "Edit a customer" + }, + new ActivityLogType + { + SystemKeyword = "EditCustomerAttribute", + Enabled = true, + Name = "Edit a customer attribute" + }, + new ActivityLogType + { + SystemKeyword = "EditCustomerAttributeValue", + Enabled = true, + Name = "Edit a customer attribute value" + }, + new ActivityLogType + { + SystemKeyword = "EditCustomerRole", + Enabled = true, + Name = "Edit a customer role" + }, + new ActivityLogType + { + SystemKeyword = "EditCustomerRewardPoints", + Enabled = true, + Name = "Edit customer reward points" + }, + new ActivityLogType + { + SystemKeyword = "EditDiscount", + Enabled = true, + Name = "Edit a discount" + }, + new ActivityLogType + { + SystemKeyword = "EditEmailAccount", + Enabled = true, + Name = "Edit an email account" + }, + new ActivityLogType + { + SystemKeyword = "EditGiftCard", + Enabled = true, + Name = "Edit a gift card" + }, + new ActivityLogType + { + SystemKeyword = "EditLanguage", + Enabled = true, + Name = "Edit a language" + }, + new ActivityLogType + { + SystemKeyword = "EditManufacturer", + Enabled = true, + Name = "Edit a manufacturer" + }, + new ActivityLogType + { + SystemKeyword = "EditMeasureDimension", + Enabled = true, + Name = "Edit a measure dimension" + }, + new ActivityLogType + { + SystemKeyword = "EditMeasureWeight", + Enabled = true, + Name = "Edit a measure weight" + }, + new ActivityLogType + { + SystemKeyword = "EditMessageTemplate", + Enabled = true, + Name = "Edit a message template" + }, + new ActivityLogType + { + SystemKeyword = "EditNews", + Enabled = true, + Name = "Edit a news" + }, + new ActivityLogType + { + SystemKeyword = "EditOrder", + Enabled = true, + Name = "Edit an order" + }, + new ActivityLogType + { + SystemKeyword = "EditPlugin", + Enabled = true, + Name = "Edit a plugin" + }, + new ActivityLogType + { + SystemKeyword = "EditProduct", + Enabled = true, + Name = "Edit a product" + }, + new ActivityLogType + { + SystemKeyword = "EditProductAttribute", + Enabled = true, + Name = "Edit a product attribute" + }, + new ActivityLogType + { + SystemKeyword = "EditProductReview", + Enabled = true, + Name = "Edit a product review" + }, + new ActivityLogType + { + SystemKeyword = "EditPromotionProviders", + Enabled = true, + Name = "Edit promotion providers" + }, + new ActivityLogType + { + SystemKeyword = "EditReturnRequest", + Enabled = true, + Name = "Edit a return request" + }, + new ActivityLogType + { + SystemKeyword = "EditSettings", + Enabled = true, + Name = "Edit setting(s)" + }, + new ActivityLogType + { + SystemKeyword = "EditStateProvince", + Enabled = true, + Name = "Edit a state or province" + }, + new ActivityLogType + { + SystemKeyword = "EditStore", + Enabled = true, + Name = "Edit a store" + }, + new ActivityLogType + { + SystemKeyword = "EditTask", + Enabled = true, + Name = "Edit a task" + }, + new ActivityLogType + { + SystemKeyword = "EditSpecAttribute", + Enabled = true, + Name = "Edit a specification attribute" + }, + new ActivityLogType + { + SystemKeyword = "EditVendor", + Enabled = true, + Name = "Edit a vendor" + }, + new ActivityLogType + { + SystemKeyword = "EditWarehouse", + Enabled = true, + Name = "Edit a warehouse" + }, + new ActivityLogType + { + SystemKeyword = "EditTopic", + Enabled = true, + Name = "Edit a topic" + }, + new ActivityLogType + { + SystemKeyword = "EditWidget", + Enabled = true, + Name = "Edit a widget" + }, + new ActivityLogType + { + SystemKeyword = "Impersonation.Started", + Enabled = true, + Name = "Customer impersonation session. Started" + }, + new ActivityLogType + { + SystemKeyword = "Impersonation.Finished", + Enabled = true, + Name = "Customer impersonation session. Finished" + }, + new ActivityLogType + { + SystemKeyword = "ImportCategories", + Enabled = true, + Name = "Categories were imported" + }, + new ActivityLogType + { + SystemKeyword = "ImportManufacturers", + Enabled = true, + Name = "Manufacturers were imported" + }, + new ActivityLogType + { + SystemKeyword = "ImportProducts", + Enabled = true, + Name = "Products were imported" + }, + new ActivityLogType + { + SystemKeyword = "ImportStates", + Enabled = true, + Name = "States were imported" + }, + new ActivityLogType + { + SystemKeyword = "InstallNewPlugin", + Enabled = true, + Name = "Install a new plugin" + }, + new ActivityLogType + { + SystemKeyword = "UninstallPlugin", + Enabled = true, + Name = "Uninstall a plugin" + }, + //public store activities + new ActivityLogType + { + SystemKeyword = "PublicStore.ViewCategory", + Enabled = false, + Name = "Public store. View a category" + }, + new ActivityLogType + { + SystemKeyword = "PublicStore.ViewManufacturer", + Enabled = false, + Name = "Public store. View a manufacturer" + }, + new ActivityLogType + { + SystemKeyword = "PublicStore.ViewProduct", + Enabled = false, + Name = "Public store. View a product" + }, + new ActivityLogType + { + SystemKeyword = "PublicStore.PlaceOrder", + Enabled = false, + Name = "Public store. Place an order" + }, + new ActivityLogType + { + SystemKeyword = "PublicStore.SendPM", + Enabled = false, + Name = "Public store. Send PM" + }, + new ActivityLogType + { + SystemKeyword = "PublicStore.ContactUs", + Enabled = false, + Name = "Public store. Use contact us form" + }, + new ActivityLogType + { + SystemKeyword = "PublicStore.AddToCompareList", + Enabled = false, + Name = "Public store. Add to compare list" + }, + new ActivityLogType + { + SystemKeyword = "PublicStore.AddToShoppingCart", + Enabled = false, + Name = "Public store. Add to shopping cart" + }, + new ActivityLogType + { + SystemKeyword = "PublicStore.AddToWishlist", + Enabled = false, + Name = "Public store. Add to wishlist" + }, + new ActivityLogType + { + SystemKeyword = "PublicStore.Login", + Enabled = false, + Name = "Public store. Login" + }, + new ActivityLogType + { + SystemKeyword = "PublicStore.Logout", + Enabled = false, + Name = "Public store. Logout" + }, + new ActivityLogType + { + SystemKeyword = "PublicStore.AddProductReview", + Enabled = false, + Name = "Public store. Add product review" + }, + new ActivityLogType + { + SystemKeyword = "PublicStore.AddNewsComment", + Enabled = false, + Name = "Public store. Add news comment" + }, + new ActivityLogType + { + SystemKeyword = "PublicStore.AddBlogComment", + Enabled = false, + Name = "Public store. Add blog comment" + }, + new ActivityLogType + { + SystemKeyword = "PublicStore.AddForumTopic", + Enabled = false, + Name = "Public store. Add forum topic" + }, + new ActivityLogType + { + SystemKeyword = "PublicStore.EditForumTopic", + Enabled = false, + Name = "Public store. Edit forum topic" + }, + new ActivityLogType + { + SystemKeyword = "PublicStore.DeleteForumTopic", + Enabled = false, + Name = "Public store. Delete forum topic" + }, + new ActivityLogType + { + SystemKeyword = "PublicStore.AddForumPost", + Enabled = false, + Name = "Public store. Add forum post" + }, + new ActivityLogType + { + SystemKeyword = "PublicStore.EditForumPost", + Enabled = false, + Name = "Public store. Edit forum post" + }, + new ActivityLogType + { + SystemKeyword = "PublicStore.DeleteForumPost", + Enabled = false, + Name = "Public store. Delete forum post" + } + }; + _activityLogTypeRepository.Insert(activityLogTypes); + } + + protected virtual void InstallProductTemplates() + { + var productTemplates = new List + { + new ProductTemplate + { + Name = "Simple product", + ViewPath = "ProductTemplate.Simple", + DisplayOrder = 10, + IgnoredProductTypes = ((int)ProductType.GroupedProduct).ToString() + }, + new ProductTemplate + { + Name = "Grouped product (with variants)", + ViewPath = "ProductTemplate.Grouped", + DisplayOrder = 100, + IgnoredProductTypes = ((int)ProductType.SimpleProduct).ToString() + } + }; + _productTemplateRepository.Insert(productTemplates); + } + + protected virtual void InstallCategoryTemplates() + { + var categoryTemplates = new List + { + new CategoryTemplate + { + Name = "Products in Grid or Lines", + ViewPath = "CategoryTemplate.ProductsInGridOrLines", + DisplayOrder = 1 + }, + }; + _categoryTemplateRepository.Insert(categoryTemplates); + } + + protected virtual void InstallManufacturerTemplates() + { + var manufacturerTemplates = new List + { + new ManufacturerTemplate + { + Name = "Products in Grid or Lines", + ViewPath = "ManufacturerTemplate.ProductsInGridOrLines", + DisplayOrder = 1 + }, + }; + _manufacturerTemplateRepository.Insert(manufacturerTemplates); + } + + protected virtual void InstallTopicTemplates() + { + var topicTemplates = new List + { + new TopicTemplate + { + Name = "Default template", + ViewPath = "TopicDetails", + DisplayOrder = 1 + }, + }; + _topicTemplateRepository.Insert(topicTemplates); + } + + protected virtual void InstallScheduleTasks() + { + var tasks = new List + { + new ScheduleTask + { + Name = "Send emails", + Seconds = 60, + Type = "Nop.Services.Messages.QueuedMessagesSendTask, Nop.Services", + Enabled = true, + StopOnError = false, + }, + new ScheduleTask + { + Name = "Keep alive", + Seconds = 300, + Type = "Nop.Services.Common.KeepAliveTask, Nop.Services", + Enabled = true, + StopOnError = false, + }, + new ScheduleTask + { + Name = "Delete guests", + Seconds = 600, + Type = "Nop.Services.Customers.DeleteGuestsTask, Nop.Services", + Enabled = true, + StopOnError = false, + }, + new ScheduleTask + { + Name = "Clear cache", + Seconds = 600, + Type = "Nop.Services.Caching.ClearCacheTask, Nop.Services", + Enabled = false, + StopOnError = false, + }, + new ScheduleTask + { + Name = "Clear log", + //60 minutes + Seconds = 3600, + Type = "Nop.Services.Logging.ClearLogTask, Nop.Services", + Enabled = false, + StopOnError = false, + }, + new ScheduleTask + { + Name = "Update currency exchange rates", + //60 minutes + Seconds = 3600, + Type = "Nop.Services.Directory.UpdateExchangeRateTask, Nop.Services", + Enabled = true, + StopOnError = false, + }, + new ScheduleTask + { + Name = "Assign invoice ident", + //60 minutes + Seconds = 60, + Type = "Nop.Services.Orders.AssignInvoiceIdentTask, Nop.Services", + Enabled = false, + StopOnError = false, + }, + }; + + _scheduleTaskRepository.Insert(tasks); + } + + protected virtual void InstallReturnRequestReasons() + { + var returnRequestReasons = new List + { + new ReturnRequestReason + { + Name = "Received Wrong Product", + DisplayOrder = 1 + }, + new ReturnRequestReason + { + Name = "Wrong Product Ordered", + DisplayOrder = 2 + }, + new ReturnRequestReason + { + Name = "There Was A Problem With The Product", + DisplayOrder = 3 + } + }; + _returnRequestReasonRepository.Insert(returnRequestReasons); + } + protected virtual void InstallReturnRequestActions() + { + var returnRequestActions = new List + { + new ReturnRequestAction + { + Name = "Repair", + DisplayOrder = 1 + }, + new ReturnRequestAction + { + Name = "Replacement", + DisplayOrder = 2 + }, + new ReturnRequestAction + { + Name = "Store Credit", + DisplayOrder = 3 + } + }; + _returnRequestActionRepository.Insert(returnRequestActions); + } + + protected virtual void InstallWarehouses() + { + var warehouse1address = new Address + { + Address1 = "21 West 52nd Street", + City = "New York", + StateProvince = _stateProvinceRepository.Table.FirstOrDefault(sp => sp.Name == "New York"), + Country = _countryRepository.Table.FirstOrDefault(c => c.ThreeLetterIsoCode == "USA"), + ZipPostalCode = "10021", + CreatedOnUtc = DateTime.UtcNow, + }; + _addressRepository.Insert(warehouse1address); + var warehouse2address = new Address + { + Address1 = "300 South Spring Stree", + City = "Los Angeles", + StateProvince = _stateProvinceRepository.Table.FirstOrDefault(sp => sp.Name == "California"), + Country = _countryRepository.Table.FirstOrDefault(c => c.ThreeLetterIsoCode == "USA"), + ZipPostalCode = "90013", + CreatedOnUtc = DateTime.UtcNow, + }; + _addressRepository.Insert(warehouse2address); + var warehouses = new List + { + new Warehouse + { + Name = "Warehouse 1 (New York)", + AddressId = warehouse1address.Id + }, + new Warehouse + { + Name = "Warehouse 2 (Los Angeles)", + AddressId = warehouse2address.Id + } + }; + + _warehouseRepository.Insert(warehouses); + } + + protected virtual void InstallVendors() + { + var vendors = new List + { + new Vendor + { + Name = "Vendor 1", + Email = "vendor1email@gmail.com", + Description = "Some description...", + AdminComment = "", + PictureId = 0, + Active = true, + DisplayOrder = 1, + PageSize = 6, + AllowCustomersToSelectPageSize = true, + PageSizeOptions = "6, 3, 9, 18", + }, + new Vendor + { + Name = "Vendor 2", + Email = "vendor2email@gmail.com", + Description = "Some description...", + AdminComment = "", + PictureId = 0, + Active = true, + DisplayOrder = 2, + PageSize = 6, + AllowCustomersToSelectPageSize = true, + PageSizeOptions = "6, 3, 9, 18", + } + }; + + _vendorRepository.Insert(vendors); + + //search engine names + foreach (var vendor in vendors) + { + _urlRecordRepository.Insert(new UrlRecord + { + EntityId = vendor.Id, + EntityName = "Vendor", + LanguageId = 0, + IsActive = true, + Slug = vendor.ValidateSeName("", vendor.Name, true) + }); + } + } + + protected virtual void InstallAffiliates() + { + var affiliateAddress = new Address + { + FirstName = "John", + LastName = "Smith", + Email = "affiliate_email@gmail.com", + Company = "Company name here...", + City = "New York", + Address1 = "21 West 52nd Street", + ZipPostalCode = "10021", + PhoneNumber = "123456789", + StateProvince = _stateProvinceRepository.Table.FirstOrDefault(sp => sp.Name == "New York"), + Country = _countryRepository.Table.FirstOrDefault(c => c.ThreeLetterIsoCode == "USA"), + CreatedOnUtc = DateTime.UtcNow, + }; + _addressRepository.Insert(affiliateAddress); + var affilate = new Affiliate + { + Active = true, + Address = affiliateAddress + }; + _affiliateRepository.Insert(affilate); + } + + private void AddProductTag(Product product, string tag) + { + var productTag = _productTagRepository.Table.FirstOrDefault(pt => pt.Name == tag); + if (productTag == null) + { + productTag = new ProductTag + { + Name = tag, + }; + } + product.ProductTags.Add(productTag); + _productRepository.Update(product); + } + + #endregion + + #region Methods + + public virtual void InstallData(string defaultUserEmail, + string defaultUserPassword, bool installSampleData = true) + { + InstallStores(); + InstallMeasures(); + InstallTaxCategories(); + InstallLanguages(); + InstallCurrencies(); + InstallCountriesAndStates(); + InstallShippingMethods(); + InstallDeliveryDates(); + InstallProductAvailabilityRanges(); + InstallCustomersAndUsers(defaultUserEmail, defaultUserPassword); + InstallEmailAccounts(); + InstallMessageTemplates(); + InstallSettings(installSampleData); + InstallTopicTemplates(); + InstallTopics(); + InstallLocaleResources(); + InstallActivityLogTypes(); + InstallProductTemplates(); + InstallCategoryTemplates(); + InstallManufacturerTemplates(); + InstallScheduleTasks(); + InstallReturnRequestReasons(); + InstallReturnRequestActions(); + + if (installSampleData) + { + InstallCheckoutAttributes(); + InstallSpecificationAttributes(); + InstallProductAttributes(); + InstallCategories(); + InstallManufacturers(); + InstallProducts(defaultUserEmail); + InstallForums(); + InstallDiscounts(); + InstallBlogPosts(defaultUserEmail); + InstallNews(defaultUserEmail); + InstallPolls(); + InstallWarehouses(); + InstallVendors(); + InstallAffiliates(); + InstallOrders(); + InstallActivityLog(defaultUserEmail); + InstallSearchTerms(); + } + } + + #endregion + } } \ No newline at end of file diff --git a/src/Libraries/Nop.Services/Nop.Services.csproj b/src/Libraries/Nop.Services/Nop.Services.csproj index 48735dc6b33..7a4a382bc8f 100644 --- a/src/Libraries/Nop.Services/Nop.Services.csproj +++ b/src/Libraries/Nop.Services/Nop.Services.csproj @@ -293,6 +293,7 @@ + diff --git a/src/Libraries/Nop.Services/Orders/AssignInvoiceIdentTask.cs b/src/Libraries/Nop.Services/Orders/AssignInvoiceIdentTask.cs new file mode 100644 index 00000000000..156c68f8696 --- /dev/null +++ b/src/Libraries/Nop.Services/Orders/AssignInvoiceIdentTask.cs @@ -0,0 +1,34 @@ + +using Nop.Services.Logging; +using Nop.Services.Tasks; +using System; + +namespace Nop.Services.Orders +{ + public partial class AssignInvoiceIdentTask : ITask + { + private readonly IOrderProcessingService _orderProcessingService; + private readonly ILogger _logger; + public AssignInvoiceIdentTask(IOrderProcessingService orderProcessingService, + ILogger logger) + { + this._orderProcessingService = orderProcessingService; + this._logger = logger; + } + + /// + /// Executes a task + /// + public void Execute() + { + try + { + _orderProcessingService.AssignInvoiceIdentToOrders(); + } + catch (Exception exc) + { + _logger.Error(string.Format("Error assigning invoice ident. {0}", exc.Message), exc); + } + } + } +} diff --git a/src/Libraries/Nop.Services/Orders/IOrderProcessingService.cs b/src/Libraries/Nop.Services/Orders/IOrderProcessingService.cs index a9bd687887d..e00e62308c8 100644 --- a/src/Libraries/Nop.Services/Orders/IOrderProcessingService.cs +++ b/src/Libraries/Nop.Services/Orders/IOrderProcessingService.cs @@ -76,7 +76,7 @@ public partial interface IOrderProcessingService /// Shipment /// True to notify customer void Ship(Shipment shipment, bool notifyCustomer); - + /// /// Marks a shipment as delivered /// @@ -241,7 +241,7 @@ public partial interface IOrderProcessingService /// /// The order void ReOrder(Order order); - + /// /// Check whether return request is allowed /// @@ -282,5 +282,9 @@ public partial interface IOrderProcessingService /// /// string GetInvoiceId(int storeId); + /// + /// Set Invoice ID on orders which passed payment + /// + void AssignInvoiceIdentToOrders(); } } diff --git a/src/Libraries/Nop.Services/Orders/IOrderService.cs b/src/Libraries/Nop.Services/Orders/IOrderService.cs index bab91108fbd..8cffb224dd8 100644 --- a/src/Libraries/Nop.Services/Orders/IOrderService.cs +++ b/src/Libraries/Nop.Services/Orders/IOrderService.cs @@ -1,194 +1,199 @@ -using System; -using System.Collections.Generic; -using Nop.Core; -using Nop.Core.Domain.Orders; - -namespace Nop.Services.Orders -{ - /// - /// Order service interface - /// - public partial interface IOrderService - { - #region Orders - - /// - /// Gets an order - /// - /// The order identifier - /// Order - Order GetOrderById(int orderId); - - /// - /// Gets an order - /// - /// The custom order number - /// Order - Order GetOrderByCustomOrderNumber(string customOrderNumber); - - /// - /// Get orders by identifiers - /// - /// Order identifiers - /// Order - IList GetOrdersByIds(int[] orderIds); - - /// - /// Gets an order - /// - /// The order identifier - /// Order - Order GetOrderByGuid(Guid orderGuid); - - /// - /// Deletes an order - /// - /// The order - void DeleteOrder(Order order); - - /// - /// Search orders - /// - /// Store identifier; null to load all orders - /// Vendor identifier; null to load all orders - /// Customer identifier; null to load all orders - /// Product identifier which was purchased in an order; 0 to load all orders - /// Affiliate identifier; 0 to load all orders - /// Billing country identifier; 0 to load all orders - /// Warehouse identifier, only orders with products from a specified warehouse will be loaded; 0 to load all orders - /// Payment method system name; null to load all records - /// Created date from (UTC); null to load all records - /// Created date to (UTC); null to load all records - /// Order status identifiers; null to load all orders - /// Payment status identifiers; null to load all orders - /// Shipping status identifiers; null to load all orders - /// Billing email. Leave empty to load all records. - /// Billing last name. Leave empty to load all records. - /// Search in order notes. Leave empty to load all records. - /// Page index - /// Page size - /// Orders - IPagedList SearchOrders(int storeId = 0, - int vendorId = 0, int customerId = 0, - int productId = 0, int affiliateId = 0, int warehouseId = 0, - int billingCountryId = 0, string paymentMethodSystemName = null, - DateTime? createdFromUtc = null, DateTime? createdToUtc = null, - List osIds = null, List psIds = null, List ssIds = null, - string billingEmail = null, string billingLastName = "", - string orderNotes = null, int pageIndex = 0, int pageSize = int.MaxValue); - - /// - /// Inserts an order - /// - /// Order - void InsertOrder(Order order); - - /// - /// Updates the order - /// - /// The order - void UpdateOrder(Order order); - - /// - /// Get an order by authorization transaction ID and payment method system name - /// - /// Authorization transaction ID - /// 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); - - /// - /// Gets an order item - /// - /// Order item identifier - /// Order item - OrderItem GetOrderItemByGuid(Guid orderItemGuid); - - /// - /// Gets all downloadable order items - /// - /// Customer identifier; null to load all records - /// Order items - IList GetDownloadableOrderItems(int customerId); - - /// - /// Delete an order item - /// - /// The order item - void DeleteOrderItem(OrderItem orderItem); - - #endregion - - #region Order notes - - /// - /// Gets an order note - /// - /// The order note identifier - /// Order note - OrderNote GetOrderNoteById(int orderNoteId); - - /// - /// Deletes an order note - /// - /// The order note - void DeleteOrderNote(OrderNote orderNote); - - #endregion - - #region Recurring payments - - /// - /// Deletes a recurring payment - /// - /// Recurring payment - void DeleteRecurringPayment(RecurringPayment recurringPayment); - - /// - /// Gets a recurring payment - /// - /// The recurring payment identifier - /// Recurring payment - RecurringPayment GetRecurringPaymentById(int recurringPaymentId); - - /// - /// Inserts a recurring payment - /// - /// Recurring payment - void InsertRecurringPayment(RecurringPayment recurringPayment); - - /// - /// Updates the recurring payment - /// - /// Recurring payment - void UpdateRecurringPayment(RecurringPayment recurringPayment); - - /// - /// Search recurring payments - /// - /// The store identifier; 0 to load all records - /// The customer identifier; 0 to load all records - /// The initial order identifier; 0 to load all records - /// Initial order status identifier; null to load all records - /// Page index - /// Page size - /// A value indicating whether to show hidden records - /// Recurring payments - IPagedList SearchRecurringPayments(int storeId = 0, - int customerId = 0, int initialOrderId = 0, OrderStatus? initialOrderStatus = null, - int pageIndex = 0, int pageSize = int.MaxValue, bool showHidden = false); - - #endregion - } -} +using System; +using System.Collections.Generic; +using Nop.Core; +using Nop.Core.Domain.Orders; + +namespace Nop.Services.Orders +{ + /// + /// Order service interface + /// + public partial interface IOrderService + { + #region Orders + + /// + /// Gets an order + /// + /// The order identifier + /// Order + Order GetOrderById(int orderId); + + /// + /// Gets an order + /// + /// The custom order number + /// Order + Order GetOrderByCustomOrderNumber(string customOrderNumber); + + /// + /// Get orders by identifiers + /// + /// Order identifiers + /// Order + IList GetOrdersByIds(int[] orderIds); + + /// + /// Gets an order + /// + /// The order identifier + /// Order + Order GetOrderByGuid(Guid orderGuid); + + /// + /// Deletes an order + /// + /// The order + void DeleteOrder(Order order); + + /// + /// Search orders + /// + /// Store identifier; null to load all orders + /// Vendor identifier; null to load all orders + /// Customer identifier; null to load all orders + /// Product identifier which was purchased in an order; 0 to load all orders + /// Affiliate identifier; 0 to load all orders + /// Billing country identifier; 0 to load all orders + /// Warehouse identifier, only orders with products from a specified warehouse will be loaded; 0 to load all orders + /// Payment method system name; null to load all records + /// Created date from (UTC); null to load all records + /// Created date to (UTC); null to load all records + /// Order status identifiers; null to load all orders + /// Payment status identifiers; null to load all orders + /// Shipping status identifiers; null to load all orders + /// Billing email. Leave empty to load all records. + /// Billing last name. Leave empty to load all records. + /// Search in order notes. Leave empty to load all records. + /// Page index + /// Page size + /// Orders + IPagedList SearchOrders(int storeId = 0, + int vendorId = 0, int customerId = 0, + int productId = 0, int affiliateId = 0, int warehouseId = 0, + int billingCountryId = 0, string paymentMethodSystemName = null, + DateTime? createdFromUtc = null, DateTime? createdToUtc = null, + List osIds = null, List psIds = null, List ssIds = null, + string billingEmail = null, string billingLastName = "", + string orderNotes = null, int pageIndex = 0, int pageSize = int.MaxValue); + + /// + /// Inserts an order + /// + /// Order + void InsertOrder(Order order); + + /// + /// Updates the order + /// + /// The order + void UpdateOrder(Order order); + + /// + /// Get an order by authorization transaction ID and payment method system name + /// + /// Authorization transaction ID + /// Payment method system name + /// Order + Order GetOrderByAuthorizationTransactionIdAndPaymentMethod(string authorizationTransactionId, string paymentMethodSystemName); + + /// + /// Get all orders to whom an invoice id shouzld be assigned + /// + /// Order + IList GetOrdersToAssignInvoiceId(); + #endregion + + #region Orders items + + /// + /// Gets an order item + /// + /// Order item identifier + /// Order item + OrderItem GetOrderItemById(int orderItemId); + + /// + /// Gets an order item + /// + /// Order item identifier + /// Order item + OrderItem GetOrderItemByGuid(Guid orderItemGuid); + + /// + /// Gets all downloadable order items + /// + /// Customer identifier; null to load all records + /// Order items + IList GetDownloadableOrderItems(int customerId); + + /// + /// Delete an order item + /// + /// The order item + void DeleteOrderItem(OrderItem orderItem); + + #endregion + + #region Order notes + + /// + /// Gets an order note + /// + /// The order note identifier + /// Order note + OrderNote GetOrderNoteById(int orderNoteId); + + /// + /// Deletes an order note + /// + /// The order note + void DeleteOrderNote(OrderNote orderNote); + + #endregion + + #region Recurring payments + + /// + /// Deletes a recurring payment + /// + /// Recurring payment + void DeleteRecurringPayment(RecurringPayment recurringPayment); + + /// + /// Gets a recurring payment + /// + /// The recurring payment identifier + /// Recurring payment + RecurringPayment GetRecurringPaymentById(int recurringPaymentId); + + /// + /// Inserts a recurring payment + /// + /// Recurring payment + void InsertRecurringPayment(RecurringPayment recurringPayment); + + /// + /// Updates the recurring payment + /// + /// Recurring payment + void UpdateRecurringPayment(RecurringPayment recurringPayment); + + /// + /// Search recurring payments + /// + /// The store identifier; 0 to load all records + /// The customer identifier; 0 to load all records + /// The initial order identifier; 0 to load all records + /// Initial order status identifier; null to load all records + /// Page index + /// Page size + /// A value indicating whether to show hidden records + /// Recurring payments + IPagedList SearchRecurringPayments(int storeId = 0, + int customerId = 0, int initialOrderId = 0, OrderStatus? initialOrderStatus = null, + int pageIndex = 0, int pageSize = int.MaxValue, bool showHidden = false); + + #endregion + } +} diff --git a/src/Libraries/Nop.Services/Orders/OrderProcessingService.cs b/src/Libraries/Nop.Services/Orders/OrderProcessingService.cs index 4c5aca2284b..3a1ae9f7e3f 100644 --- a/src/Libraries/Nop.Services/Orders/OrderProcessingService.cs +++ b/src/Libraries/Nop.Services/Orders/OrderProcessingService.cs @@ -1240,8 +1240,13 @@ public virtual void CheckOrderStatus(Order order) //set invoice id if (order.PaymentStatus == PaymentStatus.Paid && order.InvoiceId == null) { - order.InvoiceDateUtc = DateTime.UtcNow; - order.InvoiceId = GetInvoiceId(order.StoreId); + if (_orderSettings.AssignInvoiceIdentFromTask) + order.InvoiceId = "ToBeAssigned"; + else + { + order.InvoiceDateUtc = DateTime.UtcNow; + order.InvoiceId = GetInvoiceId(order.StoreId); + } _orderService.UpdateOrder(order); } @@ -3290,11 +3295,24 @@ public virtual string GetInvoiceId(int storeId) return string.Format("I-{0}.{1}", DateTime.UtcNow.Year, ident.ToString("D5")); } /// - /// Gets a value indicating whether payment workflow is required + /// Set Invoice ID on orders which passed payment /// - /// Shopping cart - /// A value indicating reward points should be used; null to detect current choice of the customer - /// true - OK; false - minimum order total amount is not reached + public virtual void AssignInvoiceIdentToOrders() + { + var orders = _orderService.GetOrdersToAssignInvoiceId(); + foreach (var order in orders) + { + order.InvoiceDateUtc = DateTime.UtcNow; + order.InvoiceId = GetInvoiceId(order.StoreId); + _orderService.UpdateOrder(order); + } + } + /// + /// Gets a value indicating whether payment workflow is required + /// + /// Shopping cart + /// A value indicating reward points should be used; null to detect current choice of the customer + /// true - OK; false - minimum order total amount is not reached public virtual bool IsPaymentWorkflowRequired(IList cart, bool? useRewardPoints = null) { if (cart == null) diff --git a/src/Libraries/Nop.Services/Orders/OrderService.cs b/src/Libraries/Nop.Services/Orders/OrderService.cs index 496ca96fd26..a483fdf3631 100644 --- a/src/Libraries/Nop.Services/Orders/OrderService.cs +++ b/src/Libraries/Nop.Services/Orders/OrderService.cs @@ -1,508 +1,522 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Nop.Core; -using Nop.Core.Data; -using Nop.Core.Domain.Catalog; -using Nop.Core.Domain.Customers; -using Nop.Core.Domain.Orders; -using Nop.Services.Events; - -namespace Nop.Services.Orders -{ - /// - /// Order service - /// - public partial class OrderService : IOrderService - { - #region Fields - - private readonly IRepository _orderRepository; - private readonly IRepository _orderItemRepository; - private readonly IRepository _orderNoteRepository; - private readonly IRepository _productRepository; - private readonly IRepository _recurringPaymentRepository; - private readonly IRepository _customerRepository; - private readonly IEventPublisher _eventPublisher; - - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Order repository - /// Order item repository - /// Order note repository - /// Product repository - /// Recurring payment repository - /// Customer repository - /// Event published - public OrderService(IRepository orderRepository, - IRepository orderItemRepository, - IRepository orderNoteRepository, - IRepository productRepository, - IRepository recurringPaymentRepository, - IRepository customerRepository, - IEventPublisher eventPublisher) - { - this._orderRepository = orderRepository; - this._orderItemRepository = orderItemRepository; - this._orderNoteRepository = orderNoteRepository; - this._productRepository = productRepository; - this._recurringPaymentRepository = recurringPaymentRepository; - this._customerRepository = customerRepository; - this._eventPublisher = eventPublisher; - } - - #endregion - - #region Methods - - #region Orders - - /// - /// Gets an order - /// - /// The order identifier - /// Order - public virtual Order GetOrderById(int orderId) - { - if (orderId == 0) - return null; - - return _orderRepository.GetById(orderId); - } - - /// - /// Gets an order - /// - /// The custom order number - /// Order - public virtual Order GetOrderByCustomOrderNumber(string customOrderNumber) - { - if (string.IsNullOrEmpty(customOrderNumber)) - return null; - - return _orderRepository.Table.FirstOrDefault(o => o.CustomOrderNumber == customOrderNumber); - } - - /// - /// Get orders by identifiers - /// - /// Order identifiers - /// Order - public virtual IList GetOrdersByIds(int[] orderIds) - { - if (orderIds == null || orderIds.Length == 0) - return new List(); - - var query = from o in _orderRepository.Table - where orderIds.Contains(o.Id) && !o.Deleted - select o; - var orders = query.ToList(); - //sort by passed identifiers - var sortedOrders = new List(); - foreach (int id in orderIds) - { - var order = orders.Find(x => x.Id == id); - if (order != null) - sortedOrders.Add(order); - } - return sortedOrders; - } - - /// - /// Gets an order - /// - /// The order identifier - /// Order - public virtual Order GetOrderByGuid(Guid orderGuid) - { - if (orderGuid == Guid.Empty) - return null; - - var query = from o in _orderRepository.Table - where o.OrderGuid == orderGuid - select o; - var order = query.FirstOrDefault(); - return order; - } - - /// - /// Deletes an order - /// - /// The order - public virtual void DeleteOrder(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - order.Deleted = true; - UpdateOrder(order); - - //event notification - _eventPublisher.EntityDeleted(order); - } - - /// - /// Search orders - /// - /// Store identifier; 0 to load all orders - /// Vendor identifier; null to load all orders - /// Customer identifier; 0 to load all orders - /// Product identifier which was purchased in an order; 0 to load all orders - /// Affiliate identifier; 0 to load all orders - /// Billing country identifier; 0 to load all orders - /// Warehouse identifier, only orders with products from a specified warehouse will be loaded; 0 to load all orders - /// Payment method system name; null to load all records - /// Created date from (UTC); null to load all records - /// Created date to (UTC); null to load all records - /// Order status identifiers; null to load all orders - /// Payment status identifiers; null to load all orders - /// Shipping status identifiers; null to load all orders - /// Billing email. Leave empty to load all records. - /// Billing last name. Leave empty to load all records. - /// Search in order notes. Leave empty to load all records. - /// Page index - /// Page size - /// Orders - public virtual IPagedList SearchOrders(int storeId = 0, - int vendorId = 0, int customerId = 0, - int productId = 0, int affiliateId = 0, int warehouseId = 0, - int billingCountryId = 0, string paymentMethodSystemName = null, - DateTime? createdFromUtc = null, DateTime? createdToUtc = null, - List osIds = null, List psIds = null, List ssIds = null, - string billingEmail = null, string billingLastName = "", - string orderNotes = null, int pageIndex = 0, int pageSize = int.MaxValue) - { - var query = _orderRepository.Table; - if (storeId > 0) - query = query.Where(o => o.StoreId == storeId); - if (vendorId > 0) - { - query = query - .Where(o => o.OrderItems - .Any(orderItem => orderItem.Product.VendorId == vendorId)); - } - if (customerId > 0) - query = query.Where(o => o.CustomerId == customerId); - if (productId > 0) - { - query = query - .Where(o => o.OrderItems - .Any(orderItem => orderItem.Product.Id == productId)); - } - if (warehouseId > 0) - { - var manageStockInventoryMethodId = (int)ManageInventoryMethod.ManageStock; - query = query - .Where(o => o.OrderItems - .Any(orderItem => - //"Use multiple warehouses" enabled - //we search in each warehouse - (orderItem.Product.ManageInventoryMethodId == manageStockInventoryMethodId && - orderItem.Product.UseMultipleWarehouses && - orderItem.Product.ProductWarehouseInventory.Any(pwi => pwi.WarehouseId == warehouseId)) - || - //"Use multiple warehouses" disabled - //we use standard "warehouse" property - ((orderItem.Product.ManageInventoryMethodId != manageStockInventoryMethodId || - !orderItem.Product.UseMultipleWarehouses) && - orderItem.Product.WarehouseId == warehouseId)) - ); - } - if (billingCountryId > 0) - query = query.Where(o => o.BillingAddress != null && o.BillingAddress.CountryId == billingCountryId); - if (!String.IsNullOrEmpty(paymentMethodSystemName)) - query = query.Where(o => o.PaymentMethodSystemName == paymentMethodSystemName); - if (affiliateId > 0) - query = query.Where(o => o.AffiliateId == affiliateId); - if (createdFromUtc.HasValue) - query = query.Where(o => createdFromUtc.Value <= o.CreatedOnUtc); - if (createdToUtc.HasValue) - query = query.Where(o => createdToUtc.Value >= o.CreatedOnUtc); - if (osIds != null && osIds.Any()) - query = query.Where(o => osIds.Contains(o.OrderStatusId)); - if (psIds != null && psIds.Any()) - query = query.Where(o => psIds.Contains(o.PaymentStatusId)); - if (ssIds != null && ssIds.Any()) - query = query.Where(o => ssIds.Contains(o.ShippingStatusId)); - if (!String.IsNullOrEmpty(billingEmail)) - query = query.Where(o => o.BillingAddress != null && !String.IsNullOrEmpty(o.BillingAddress.Email) && o.BillingAddress.Email.Contains(billingEmail)); - if (!String.IsNullOrEmpty(billingLastName)) - query = query.Where(o => o.BillingAddress != null && !String.IsNullOrEmpty(o.BillingAddress.LastName) && o.BillingAddress.LastName.Contains(billingLastName)); - if (!String.IsNullOrEmpty(orderNotes)) - query = query.Where(o => o.OrderNotes.Any(on => on.Note.Contains(orderNotes))); - query = query.Where(o => !o.Deleted); - query = query.OrderByDescending(o => o.CreatedOnUtc); - - //database layer paging - return new PagedList(query, pageIndex, pageSize); - } - - /// - /// Inserts an order - /// - /// Order - public virtual void InsertOrder(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - _orderRepository.Insert(order); - - //event notification - _eventPublisher.EntityInserted(order); - } - - /// - /// Updates the order - /// - /// The order - public virtual void UpdateOrder(Order order) - { - if (order == null) - throw new ArgumentNullException("order"); - - _orderRepository.Update(order); - - //event notification - _eventPublisher.EntityUpdated(order); - } - - /// - /// Get an order by authorization transaction ID and payment method system name - /// - /// Authorization transaction ID - /// Payment method system name - /// Order - public virtual Order GetOrderByAuthorizationTransactionIdAndPaymentMethod(string authorizationTransactionId, - string paymentMethodSystemName) - { - var query = _orderRepository.Table; - if (!String.IsNullOrWhiteSpace(authorizationTransactionId)) - query = query.Where(o => o.AuthorizationTransactionId == authorizationTransactionId); - - if (!String.IsNullOrWhiteSpace(paymentMethodSystemName)) - query = query.Where(o => o.PaymentMethodSystemName == paymentMethodSystemName); - - query = query.OrderByDescending(o => o.CreatedOnUtc); - var order = query.FirstOrDefault(); - return order; - } - - #endregion - - #region Orders items - - /// - /// Gets an order item - /// - /// Order item identifier - /// Order item - public virtual OrderItem GetOrderItemById(int orderItemId) - { - if (orderItemId == 0) - return null; - - return _orderItemRepository.GetById(orderItemId); - } - - /// - /// Gets an item - /// - /// Order identifier - /// Order item - public virtual OrderItem GetOrderItemByGuid(Guid orderItemGuid) - { - if (orderItemGuid == Guid.Empty) - return null; - - var query = from orderItem in _orderItemRepository.Table - where orderItem.OrderItemGuid == orderItemGuid - select orderItem; - var item = query.FirstOrDefault(); - return item; - } - - /// - /// Gets all downloadable order items - /// - /// Customer identifier; null to load all records - /// Order items - public virtual IList GetDownloadableOrderItems(int customerId) - { - if (customerId == 0) - throw new ArgumentOutOfRangeException("customerId"); - - var query = from orderItem in _orderItemRepository.Table - join o in _orderRepository.Table on orderItem.OrderId equals o.Id - join p in _productRepository.Table on orderItem.ProductId equals p.Id - where customerId == o.CustomerId && - p.IsDownload && - !o.Deleted - orderby o.CreatedOnUtc descending, orderItem.Id - select orderItem; - - var orderItems = query.ToList(); - return orderItems; - } - - /// - /// Delete an order item - /// - /// The order item - public virtual void DeleteOrderItem(OrderItem orderItem) - { - if (orderItem == null) - throw new ArgumentNullException("orderItem"); - - _orderItemRepository.Delete(orderItem); - - //event notification - _eventPublisher.EntityDeleted(orderItem); - } - - #endregion - - #region Orders notes - - /// - /// Gets an order note - /// - /// The order note identifier - /// Order note - public virtual OrderNote GetOrderNoteById(int orderNoteId) - { - if (orderNoteId == 0) - return null; - - return _orderNoteRepository.GetById(orderNoteId); - } - - /// - /// Deletes an order note - /// - /// The order note - public virtual void DeleteOrderNote(OrderNote orderNote) - { - if (orderNote == null) - throw new ArgumentNullException("orderNote"); - - _orderNoteRepository.Delete(orderNote); - - //event notification - _eventPublisher.EntityDeleted(orderNote); - } - - #endregion - - #region Recurring payments - - /// - /// Deletes a recurring payment - /// - /// Recurring payment - public virtual void DeleteRecurringPayment(RecurringPayment recurringPayment) - { - if (recurringPayment == null) - throw new ArgumentNullException("recurringPayment"); - - recurringPayment.Deleted = true; - UpdateRecurringPayment(recurringPayment); - - //event notification - _eventPublisher.EntityDeleted(recurringPayment); - } - - /// - /// Gets a recurring payment - /// - /// The recurring payment identifier - /// Recurring payment - public virtual RecurringPayment GetRecurringPaymentById(int recurringPaymentId) - { - if (recurringPaymentId == 0) - return null; - - return _recurringPaymentRepository.GetById(recurringPaymentId); - } - - /// - /// Inserts a recurring payment - /// - /// Recurring payment - public virtual void InsertRecurringPayment(RecurringPayment recurringPayment) - { - if (recurringPayment == null) - throw new ArgumentNullException("recurringPayment"); - - _recurringPaymentRepository.Insert(recurringPayment); - - //event notification - _eventPublisher.EntityInserted(recurringPayment); - } - - /// - /// Updates the recurring payment - /// - /// Recurring payment - public virtual void UpdateRecurringPayment(RecurringPayment recurringPayment) - { - if (recurringPayment == null) - throw new ArgumentNullException("recurringPayment"); - - _recurringPaymentRepository.Update(recurringPayment); - - //event notification - _eventPublisher.EntityUpdated(recurringPayment); - } - - /// - /// Search recurring payments - /// - /// The store identifier; 0 to load all records - /// The customer identifier; 0 to load all records - /// The initial order identifier; 0 to load all records - /// Initial order status identifier; null to load all records - /// Page index - /// Page size - /// A value indicating whether to show hidden records - /// Recurring payments - public virtual IPagedList SearchRecurringPayments(int storeId = 0, - int customerId = 0, int initialOrderId = 0, OrderStatus? initialOrderStatus = null, - int pageIndex = 0, int pageSize = int.MaxValue, bool showHidden = false) - { - int? initialOrderStatusId = null; - if (initialOrderStatus.HasValue) - initialOrderStatusId = (int)initialOrderStatus.Value; - - var query1 = from rp in _recurringPaymentRepository.Table - join c in _customerRepository.Table on rp.InitialOrder.CustomerId equals c.Id - where - (!rp.Deleted) && - (showHidden || !rp.InitialOrder.Deleted) && - (showHidden || !c.Deleted) && - (showHidden || rp.IsActive) && - (customerId == 0 || rp.InitialOrder.CustomerId == customerId) && - (storeId == 0 || rp.InitialOrder.StoreId == storeId) && - (initialOrderId == 0 || rp.InitialOrder.Id == initialOrderId) && - (!initialOrderStatusId.HasValue || initialOrderStatusId.Value == 0 || rp.InitialOrder.OrderStatusId == initialOrderStatusId.Value) - select rp.Id; - - var query2 = from rp in _recurringPaymentRepository.Table - where query1.Contains(rp.Id) - orderby rp.StartDateUtc, rp.Id - select rp; - - var recurringPayments = new PagedList(query2, pageIndex, pageSize); - return recurringPayments; - } - - #endregion - - #endregion - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using Nop.Core; +using Nop.Core.Data; +using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Customers; +using Nop.Core.Domain.Orders; +using Nop.Services.Events; + +namespace Nop.Services.Orders +{ + /// + /// Order service + /// + public partial class OrderService : IOrderService + { + #region Fields + + private readonly IRepository _orderRepository; + private readonly IRepository _orderItemRepository; + private readonly IRepository _orderNoteRepository; + private readonly IRepository _productRepository; + private readonly IRepository _recurringPaymentRepository; + private readonly IRepository _customerRepository; + private readonly IEventPublisher _eventPublisher; + + #endregion + + #region Ctor + + /// + /// Ctor + /// + /// Order repository + /// Order item repository + /// Order note repository + /// Product repository + /// Recurring payment repository + /// Customer repository + /// Event published + public OrderService(IRepository orderRepository, + IRepository orderItemRepository, + IRepository orderNoteRepository, + IRepository productRepository, + IRepository recurringPaymentRepository, + IRepository customerRepository, + IEventPublisher eventPublisher) + { + this._orderRepository = orderRepository; + this._orderItemRepository = orderItemRepository; + this._orderNoteRepository = orderNoteRepository; + this._productRepository = productRepository; + this._recurringPaymentRepository = recurringPaymentRepository; + this._customerRepository = customerRepository; + this._eventPublisher = eventPublisher; + } + + #endregion + + #region Methods + + #region Orders + + /// + /// Gets an order + /// + /// The order identifier + /// Order + public virtual Order GetOrderById(int orderId) + { + if (orderId == 0) + return null; + + return _orderRepository.GetById(orderId); + } + + /// + /// Gets an order + /// + /// The custom order number + /// Order + public virtual Order GetOrderByCustomOrderNumber(string customOrderNumber) + { + if (string.IsNullOrEmpty(customOrderNumber)) + return null; + + return _orderRepository.Table.FirstOrDefault(o => o.CustomOrderNumber == customOrderNumber); + } + + /// + /// Get orders by identifiers + /// + /// Order identifiers + /// Order + public virtual IList GetOrdersByIds(int[] orderIds) + { + if (orderIds == null || orderIds.Length == 0) + return new List(); + + var query = from o in _orderRepository.Table + where orderIds.Contains(o.Id) && !o.Deleted + select o; + var orders = query.ToList(); + //sort by passed identifiers + var sortedOrders = new List(); + foreach (int id in orderIds) + { + var order = orders.Find(x => x.Id == id); + if (order != null) + sortedOrders.Add(order); + } + return sortedOrders; + } + + /// + /// Gets an order + /// + /// The order identifier + /// Order + public virtual Order GetOrderByGuid(Guid orderGuid) + { + if (orderGuid == Guid.Empty) + return null; + + var query = from o in _orderRepository.Table + where o.OrderGuid == orderGuid + select o; + var order = query.FirstOrDefault(); + return order; + } + + /// + /// Deletes an order + /// + /// The order + public virtual void DeleteOrder(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + order.Deleted = true; + UpdateOrder(order); + + //event notification + _eventPublisher.EntityDeleted(order); + } + + /// + /// Search orders + /// + /// Store identifier; 0 to load all orders + /// Vendor identifier; null to load all orders + /// Customer identifier; 0 to load all orders + /// Product identifier which was purchased in an order; 0 to load all orders + /// Affiliate identifier; 0 to load all orders + /// Billing country identifier; 0 to load all orders + /// Warehouse identifier, only orders with products from a specified warehouse will be loaded; 0 to load all orders + /// Payment method system name; null to load all records + /// Created date from (UTC); null to load all records + /// Created date to (UTC); null to load all records + /// Order status identifiers; null to load all orders + /// Payment status identifiers; null to load all orders + /// Shipping status identifiers; null to load all orders + /// Billing email. Leave empty to load all records. + /// Billing last name. Leave empty to load all records. + /// Search in order notes. Leave empty to load all records. + /// Page index + /// Page size + /// Orders + public virtual IPagedList SearchOrders(int storeId = 0, + int vendorId = 0, int customerId = 0, + int productId = 0, int affiliateId = 0, int warehouseId = 0, + int billingCountryId = 0, string paymentMethodSystemName = null, + DateTime? createdFromUtc = null, DateTime? createdToUtc = null, + List osIds = null, List psIds = null, List ssIds = null, + string billingEmail = null, string billingLastName = "", + string orderNotes = null, int pageIndex = 0, int pageSize = int.MaxValue) + { + var query = _orderRepository.Table; + if (storeId > 0) + query = query.Where(o => o.StoreId == storeId); + if (vendorId > 0) + { + query = query + .Where(o => o.OrderItems + .Any(orderItem => orderItem.Product.VendorId == vendorId)); + } + if (customerId > 0) + query = query.Where(o => o.CustomerId == customerId); + if (productId > 0) + { + query = query + .Where(o => o.OrderItems + .Any(orderItem => orderItem.Product.Id == productId)); + } + if (warehouseId > 0) + { + var manageStockInventoryMethodId = (int)ManageInventoryMethod.ManageStock; + query = query + .Where(o => o.OrderItems + .Any(orderItem => + //"Use multiple warehouses" enabled + //we search in each warehouse + (orderItem.Product.ManageInventoryMethodId == manageStockInventoryMethodId && + orderItem.Product.UseMultipleWarehouses && + orderItem.Product.ProductWarehouseInventory.Any(pwi => pwi.WarehouseId == warehouseId)) + || + //"Use multiple warehouses" disabled + //we use standard "warehouse" property + ((orderItem.Product.ManageInventoryMethodId != manageStockInventoryMethodId || + !orderItem.Product.UseMultipleWarehouses) && + orderItem.Product.WarehouseId == warehouseId)) + ); + } + if (billingCountryId > 0) + query = query.Where(o => o.BillingAddress != null && o.BillingAddress.CountryId == billingCountryId); + if (!String.IsNullOrEmpty(paymentMethodSystemName)) + query = query.Where(o => o.PaymentMethodSystemName == paymentMethodSystemName); + if (affiliateId > 0) + query = query.Where(o => o.AffiliateId == affiliateId); + if (createdFromUtc.HasValue) + query = query.Where(o => createdFromUtc.Value <= o.CreatedOnUtc); + if (createdToUtc.HasValue) + query = query.Where(o => createdToUtc.Value >= o.CreatedOnUtc); + if (osIds != null && osIds.Any()) + query = query.Where(o => osIds.Contains(o.OrderStatusId)); + if (psIds != null && psIds.Any()) + query = query.Where(o => psIds.Contains(o.PaymentStatusId)); + if (ssIds != null && ssIds.Any()) + query = query.Where(o => ssIds.Contains(o.ShippingStatusId)); + if (!String.IsNullOrEmpty(billingEmail)) + query = query.Where(o => o.BillingAddress != null && !String.IsNullOrEmpty(o.BillingAddress.Email) && o.BillingAddress.Email.Contains(billingEmail)); + if (!String.IsNullOrEmpty(billingLastName)) + query = query.Where(o => o.BillingAddress != null && !String.IsNullOrEmpty(o.BillingAddress.LastName) && o.BillingAddress.LastName.Contains(billingLastName)); + if (!String.IsNullOrEmpty(orderNotes)) + query = query.Where(o => o.OrderNotes.Any(on => on.Note.Contains(orderNotes))); + query = query.Where(o => !o.Deleted); + query = query.OrderByDescending(o => o.CreatedOnUtc); + + //database layer paging + return new PagedList(query, pageIndex, pageSize); + } + + /// + /// Inserts an order + /// + /// Order + public virtual void InsertOrder(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + _orderRepository.Insert(order); + + //event notification + _eventPublisher.EntityInserted(order); + } + + /// + /// Updates the order + /// + /// The order + public virtual void UpdateOrder(Order order) + { + if (order == null) + throw new ArgumentNullException("order"); + + _orderRepository.Update(order); + + //event notification + _eventPublisher.EntityUpdated(order); + } + + /// + /// Get an order by authorization transaction ID and payment method system name + /// + /// Authorization transaction ID + /// Payment method system name + /// Order + public virtual Order GetOrderByAuthorizationTransactionIdAndPaymentMethod(string authorizationTransactionId, + string paymentMethodSystemName) + { + var query = _orderRepository.Table; + if (!String.IsNullOrWhiteSpace(authorizationTransactionId)) + query = query.Where(o => o.AuthorizationTransactionId == authorizationTransactionId); + + if (!String.IsNullOrWhiteSpace(paymentMethodSystemName)) + query = query.Where(o => o.PaymentMethodSystemName == paymentMethodSystemName); + + query = query.OrderByDescending(o => o.CreatedOnUtc); + var order = query.FirstOrDefault(); + return order; + } + + /// + /// Get all orders to whom an invoice id should be assigned + /// + /// Order + public virtual IList GetOrdersToAssignInvoiceId() + { + + var query = from o in _orderRepository.Table + where (o.InvoiceId == "ToBeAssigned") + select o; + var orders = query.ToList(); + + return orders; + } + #endregion + + #region Orders items + + /// + /// Gets an order item + /// + /// Order item identifier + /// Order item + public virtual OrderItem GetOrderItemById(int orderItemId) + { + if (orderItemId == 0) + return null; + + return _orderItemRepository.GetById(orderItemId); + } + + /// + /// Gets an item + /// + /// Order identifier + /// Order item + public virtual OrderItem GetOrderItemByGuid(Guid orderItemGuid) + { + if (orderItemGuid == Guid.Empty) + return null; + + var query = from orderItem in _orderItemRepository.Table + where orderItem.OrderItemGuid == orderItemGuid + select orderItem; + var item = query.FirstOrDefault(); + return item; + } + + /// + /// Gets all downloadable order items + /// + /// Customer identifier; null to load all records + /// Order items + public virtual IList GetDownloadableOrderItems(int customerId) + { + if (customerId == 0) + throw new ArgumentOutOfRangeException("customerId"); + + var query = from orderItem in _orderItemRepository.Table + join o in _orderRepository.Table on orderItem.OrderId equals o.Id + join p in _productRepository.Table on orderItem.ProductId equals p.Id + where customerId == o.CustomerId && + p.IsDownload && + !o.Deleted + orderby o.CreatedOnUtc descending, orderItem.Id + select orderItem; + + var orderItems = query.ToList(); + return orderItems; + } + + /// + /// Delete an order item + /// + /// The order item + public virtual void DeleteOrderItem(OrderItem orderItem) + { + if (orderItem == null) + throw new ArgumentNullException("orderItem"); + + _orderItemRepository.Delete(orderItem); + + //event notification + _eventPublisher.EntityDeleted(orderItem); + } + + #endregion + + #region Orders notes + + /// + /// Gets an order note + /// + /// The order note identifier + /// Order note + public virtual OrderNote GetOrderNoteById(int orderNoteId) + { + if (orderNoteId == 0) + return null; + + return _orderNoteRepository.GetById(orderNoteId); + } + + /// + /// Deletes an order note + /// + /// The order note + public virtual void DeleteOrderNote(OrderNote orderNote) + { + if (orderNote == null) + throw new ArgumentNullException("orderNote"); + + _orderNoteRepository.Delete(orderNote); + + //event notification + _eventPublisher.EntityDeleted(orderNote); + } + + #endregion + + #region Recurring payments + + /// + /// Deletes a recurring payment + /// + /// Recurring payment + public virtual void DeleteRecurringPayment(RecurringPayment recurringPayment) + { + if (recurringPayment == null) + throw new ArgumentNullException("recurringPayment"); + + recurringPayment.Deleted = true; + UpdateRecurringPayment(recurringPayment); + + //event notification + _eventPublisher.EntityDeleted(recurringPayment); + } + + /// + /// Gets a recurring payment + /// + /// The recurring payment identifier + /// Recurring payment + public virtual RecurringPayment GetRecurringPaymentById(int recurringPaymentId) + { + if (recurringPaymentId == 0) + return null; + + return _recurringPaymentRepository.GetById(recurringPaymentId); + } + + /// + /// Inserts a recurring payment + /// + /// Recurring payment + public virtual void InsertRecurringPayment(RecurringPayment recurringPayment) + { + if (recurringPayment == null) + throw new ArgumentNullException("recurringPayment"); + + _recurringPaymentRepository.Insert(recurringPayment); + + //event notification + _eventPublisher.EntityInserted(recurringPayment); + } + + /// + /// Updates the recurring payment + /// + /// Recurring payment + public virtual void UpdateRecurringPayment(RecurringPayment recurringPayment) + { + if (recurringPayment == null) + throw new ArgumentNullException("recurringPayment"); + + _recurringPaymentRepository.Update(recurringPayment); + + //event notification + _eventPublisher.EntityUpdated(recurringPayment); + } + + /// + /// Search recurring payments + /// + /// The store identifier; 0 to load all records + /// The customer identifier; 0 to load all records + /// The initial order identifier; 0 to load all records + /// Initial order status identifier; null to load all records + /// Page index + /// Page size + /// A value indicating whether to show hidden records + /// Recurring payments + public virtual IPagedList SearchRecurringPayments(int storeId = 0, + int customerId = 0, int initialOrderId = 0, OrderStatus? initialOrderStatus = null, + int pageIndex = 0, int pageSize = int.MaxValue, bool showHidden = false) + { + int? initialOrderStatusId = null; + if (initialOrderStatus.HasValue) + initialOrderStatusId = (int)initialOrderStatus.Value; + + var query1 = from rp in _recurringPaymentRepository.Table + join c in _customerRepository.Table on rp.InitialOrder.CustomerId equals c.Id + where + (!rp.Deleted) && + (showHidden || !rp.InitialOrder.Deleted) && + (showHidden || !c.Deleted) && + (showHidden || rp.IsActive) && + (customerId == 0 || rp.InitialOrder.CustomerId == customerId) && + (storeId == 0 || rp.InitialOrder.StoreId == storeId) && + (initialOrderId == 0 || rp.InitialOrder.Id == initialOrderId) && + (!initialOrderStatusId.HasValue || initialOrderStatusId.Value == 0 || rp.InitialOrder.OrderStatusId == initialOrderStatusId.Value) + select rp.Id; + + var query2 = from rp in _recurringPaymentRepository.Table + where query1.Contains(rp.Id) + orderby rp.StartDateUtc, rp.Id + select rp; + + var recurringPayments = new PagedList(query2, pageIndex, pageSize); + return recurringPayments; + } + + #endregion + + #endregion + } +} diff --git a/src/Presentation/Nop.Web/Administration/Controllers/SettingController.cs b/src/Presentation/Nop.Web/Administration/Controllers/SettingController.cs index 303989f6b57..084ffc1b1c3 100644 --- a/src/Presentation/Nop.Web/Administration/Controllers/SettingController.cs +++ b/src/Presentation/Nop.Web/Administration/Controllers/SettingController.cs @@ -1173,6 +1173,7 @@ public virtual ActionResult Order(OrderSettingsModel model) _settingService.SaveSetting(orderSettings, x => x.DeactivateGiftCardsAfterCancellingOrder, 0, false); _settingService.SaveSetting(orderSettings, x => x.DeactivateGiftCardsAfterDeletingOrder, 0, false); _settingService.SaveSetting(orderSettings, x => x.CompleteOrderWhenDelivered, 0, false); + _settingService.SaveSetting(orderSettings, x => x.AssignInvoiceIdentFromTask, 0, false); //now clear settings cache _settingService.ClearCache(); diff --git a/src/Presentation/Nop.Web/Administration/Models/Settings/OrderSettingsModel.cs b/src/Presentation/Nop.Web/Administration/Models/Settings/OrderSettingsModel.cs index 07435d7b1e6..4b5536a3de1 100644 --- a/src/Presentation/Nop.Web/Administration/Models/Settings/OrderSettingsModel.cs +++ b/src/Presentation/Nop.Web/Administration/Models/Settings/OrderSettingsModel.cs @@ -112,5 +112,7 @@ public partial class OrderSettingsModel : BaseNopModel [NopResourceDisplayName("Admin.Configuration.Settings.Order.ExportWithProducts")] public bool ExportWithProducts { get; set; } public bool ExportWithProducts_OverrideForStore { get; set; } + [NopResourceDisplayName("Admin.Configuration.Settings.Order.AssignInvoiceIdentFromTask")] + public bool AssignInvoiceIdentFromTask { get; set; } } } \ No newline at end of file diff --git a/src/Presentation/Nop.Web/Administration/Views/Setting/Order.cshtml b/src/Presentation/Nop.Web/Administration/Views/Setting/Order.cshtml index f0f63396a98..800d1208f17 100644 --- a/src/Presentation/Nop.Web/Administration/Views/Setting/Order.cshtml +++ b/src/Presentation/Nop.Web/Administration/Views/Setting/Order.cshtml @@ -266,6 +266,15 @@ @Html.ValidationMessageFor(model => model.InvoiceYear) +
    +
    + @Html.NopLabelFor(model => model.AssignInvoiceIdentFromTask) +
    +
    + @Html.NopEditorFor(model => model.AssignInvoiceIdentFromTask) + @Html.ValidationMessageFor(model => model.AssignInvoiceIdentFromTask) +
    +
    @Html.OverrideStoreCheckboxFor(model => model.IsReOrderAllowed_OverrideForStore, model => model.IsReOrderAllowed, Model.ActiveStoreScopeConfiguration) diff --git a/src/Presentation/Nop.Web/App_Data/Localization/defaultResources.nopres.xml b/src/Presentation/Nop.Web/App_Data/Localization/defaultResources.nopres.xml index 19074f1f12f..3d91d965813 100644 --- a/src/Presentation/Nop.Web/App_Data/Localization/defaultResources.nopres.xml +++ b/src/Presentation/Nop.Web/App_Data/Localization/defaultResources.nopres.xml @@ -1,16981 +1,16987 @@ - - - - Account activation - - - Your account has been activated - - - Your account already has been activated - - - Administration - - - External authentication - - - You can associate your account with some external authentication systems on the following page (login once using them): - - - Authentication method - - - Email - - - External identifier - - - - or - - - - Remove - - - Account Association: Your new user account will be linked to - - - (remove) - - - Avatar - - - Maximum avatar size is {0} bytes - - - Remove avatar - - - Avatar must be in GIF or JPEG format with the maximum size of 20 KB - - - Back in stock subscriptions - - - Delete selected - - - You will receive an e-mail when a particular product is back in stock. - - - You are not currently subscribed to any Back In Stock notification lists - - - Product - - - Change password - - - Change password - - - Email is not entered - - - The specified email could not be found - - - Old password doesn't match - - - Password is not entered - - - You entered the password that is the same as one of the last passwords you used. Please create a new password. - - - Confirm password - - - Password is required. - - - New password - - - The new password and confirmation password do not match. - - - The password should have at least {0} characters. - - - New password is required. - - - Old password - - - Old password is required. - - - Your password has expired, please create a new one - - - Password was changed - - - Username available - - - Check Availability - - - Current username - - - Please enter username - - - Username not available - - - Company Details - - - Addresses - - - Add new address - - - Edit address - - - No addresses - - - Customer info - - - Orders - - - No orders - - - Order Date - - - Details - - - Order Number - - - Order status - - - Order Total - - - Recurring payments - - - Cancel payment - - - Cycle info - - - Cycles remaining - - - Initial order - - - Next payment - - - Retry last payment - - - Start date - - - Total cycles - - - View order (Order number - {0}) - - - Return Item(s) - - - My product reviews - - - Approved - - - Pending - - - You haven't written any reviews yet - - - Product review for - - - Return requests - - - Return Action: - - - Your Comments: - - - Date Requested: - - - Returned Item: - - - Return Reason: - - - Return #{0} - {1} - - - Uploaded file: - - - Download - - - Downloadable products - - - The e-mail address is already in use - - - E-mail address is too long - - - New email is not valid - - - The username is already in use - - - Username is too long - - - Email validation - - - Your email already has been validated - - - Your email has been validated - - - I accept privacy policy - - - (read) - - - Please accept privacy policy - - - City - - - City is required - - - Company name - - - Company name is required - - - Confirm email - - - Email is required. - - - Confirm password - - - Password is required. - - - Country - - - Country is required - - - Date of birth - - - You have to be {0} - - - Date of birth is required - - - Email - - - The email and confirmation email do not match. - - - Email is required. - - - New email - - - (not validated yet) - - - Fax - - - Fax is required - - - First name - - - First name is required. - - - Gender - - - Female - - - Male - - - Last name - - - Last name is required. - - - Newsletter - - - Password - - - The password and confirmation password do not match. - - - The password should have at least {0} characters. - - - Password is required. - - - Phone - - - Phone is required - - - Signature - - - State / province - - - State / province is required - - - Street address - - - Street address is required - - - Street address 2 - - - Street address 2 is required - - - Time zone - - - Username - - - Username is required. - - - VAT number - - - NOTE: Enter VAT number with country code (e.g. GB 111 111 11) - - - Status - - - status: {0} - - - Zip / postal code - - - Zip / postal code is required - - - Forum subscriptions - - - Delete Selected - - - You will receive an e-mail when a new forum topic/post is created. - - - Forum/Topic - - - You are not currently subscribed to any forums - - - Impersonated as {0} - - - finish impersonated session - - - Click here to finish impersonated session. - - - Log in - - - Checkout as Guest - - - Checkout as a guest or register - - - Email - - - Please enter your email - - - Password - - - Remember me? - - - Username - - - Forgot password? - - - Log in - - - New Customer - - - By creating an account on our website you will be able to shop faster, be up to date on an orders status, and keep track of the orders you have previously made. - - - Returning Customer - - - Login was unsuccessful. Please correct the errors and try again. - - - Welcome, Please Sign In! - - - The credentials provided are incorrect - - - No customer account found - - - Customer is deleted - - - Customer is locked out - - - Account is not active - - - Account is not registered - - - Log out - - - My account - - - My account - - - Options - - - Password recovery - - - Recover - - - Confirm password - - - Password is required. - - - Your email address - - - Enter your email - - - Email with instructions has been sent to you. - - - Email not found. - - - Your password recovery link is expired - - - New password - - - The new password and confirmation password do not match. - - - The password should have at least {0} characters. - - - New password is required. - - - Old password - - - Old password is required. - - - Your password already has been changed. For changing it once more, you need to again recover the password. - - - Your password has been changed - - - Recover - - - Please enter your email address below. You will receive a link to reset your password. - - - Wrong password recovery token - - - Preferences - - - Register - - - Register - - - The specified email already exists - - - Email is required. - - - Password is not provided - - - The specified username already exists - - - Username is required. - - - Your account will be activated after approving by administrator. - - - Continue - - - Registration not allowed. You can edit this in the admin area. - - - Your registration has been successfully completed. You have just been sent an email containing membership activation instructions. - - - Your registration completed - - - Registration was not complete. Please correct the errors and try again. - - - Reward points - - - Shopping Cart - - - Vendor info - - - Description - - - Email - - - Email is required. - - - Name - - - Vendor name is required. - - - Picture - - - You can add only picture file - - - Remove picture - - - Your Address - - - Your Contact Information - - - Your Password - - - Your Personal Details - - - Added a new address attribute (ID = {0}) - - - Added a new address attribute value (ID = {0}) - - - Added a new affiliate (ID = {0}) - - - Added a new blog post (ID = {0}) - - - Added a new campaign (ID = {0}) - - - Added a new category ('{0}') - - - Added a new checkout attribute ('{0}') - - - Added a new country (ID = {0}) - - - Added a new currency (ID = {0}) - - - Added a new customer (ID = {0}) - - - Added a new customer attribute (ID = {0}) - - - Added a new customer attribute value (ID = {0}) - - - Add customer reward points (points = {0}, purchased points = {1}) - - - Added a new customer role ('{0}') - - - Added a new discount ('{0}') - - - Added a new email account (ID = {0}) - - - Added a new gift card ('{0}') - - - Added a new language (ID = {0}) - - - Added a new manufacturer ('{0}') - - - Added a new measure dimension (ID = {0}) - - - Added a new measure weight (ID = {0}) - - - Added a new news (ID = {0}) - - - Added a new product ('{0}') - - - Added a new product attribute ('{0}') - - - Added a new setting ('{0}') - - - Added a new specification attribute ('{0}') - - - Added a new state or province (ID = {0}) - - - Added a new store (ID = {0}) - - - Added a new topic ('{0}') - - - Added a new vendor (ID = {0}) - - - Added a new warehouse (ID = {0}) - - - Added a new widget (ID = {0}) - - - Deleted activity log - - - Deleted an address attribute (ID = {0}) - - - Deleted an address attribute value (ID = {0}) - - - Deleted an affiliate (ID = {0}) - - - Deleted a blog post (ID = {0}) - - - Deleted a blog post comment (ID = {0}) - - - Deleted a campaign (ID = {0}) - - - Deleted a category ('{0}') - - - Deleted a checkout attribute ('{0}') - - - Deleted a country (ID = {0}) - - - Deleted a currency (ID = {0}) - - - Deleted a customer (ID = {0}) - - - Deleted a customer attribute (ID = {0}) - - - Deleted a customer attribute value (ID = {0}) - - - Deleted a customer role ('{0}') - - - Deleted a discount ('{0}') - - - Deleted an email account (ID = {0}) - - - Deleted a gift card ('{0}') - - - Deleted a language (ID = {0}) - - - Deleted a manufacturer ('{0}') - - - Deleted a measure dimension (ID = {0}) - - - Deleted a measure weight (ID = {0}) - - - Deleted a message template (ID = {0}) - - - Deleted a news (ID = {0}) - - - Deleted a news comment (ID = {0}) - - - Deleted an order (ID = {0}) - - - Deleted a product ('{0}') - - - Deleted a product attribute ('{0}') - - - Deleted a product review (ID = {0}) - - - Deleted a return request (ID = {0}) - - - Deleted a setting ('{0}') - - - Deleted a specification attribute ('{0}') - - - Deleted a state or province (ID = {0}) - - - Deleted a store (ID = {0}) - - - Deleted a topic ('{0}') - - - Deleted a vendor (ID = {0}) - - - Deleted a warehouse (ID = {0}) - - - Deleted a widget (ID = {0}) - - - Edited activity log types - - - Edited an address attribute (ID = {0}) - - - Edited an address attribute value (ID = {0}) - - - Edited an affiliate (ID = {0}) - - - Edited a blog comment (ID = {0}) - - - Edited a blog post (ID = {0}) - - - Edited a campaign (ID = {0}) - - - Edited a category ('{0}') - - - Edited a checkout attribute ('{0}') - - - Edited a country (ID = {0}) - - - Edited a currency (ID = {0}) - - - Edited a customer (ID = {0}) - - - Edited a customer attribute (ID = {0}) - - - Edited a customer attribute value (ID = {0}) - - - Edited a customer role ('{0}') - - - Edited customer reward points (ID = {0}) - - - Edited a discount ('{0}') - - - Edited an email account (ID = {0}) - - - Edited a gift card ('{0}') - - - Edited a language (ID = {0}) - - - Edited a manufacturer ('{0}') - - - Edited a measure dimension (ID = {0}) - - - Edited a measure weight (ID = {0}) - - - Edited a message template (ID = {0}) - - - Edited a news (ID = {0}) - - - Edited a news comment (ID = {0}) - - - Edited an order (Order number = {0}). See order notes for details - - - Edited a plugin (FriendlyName: '{0}') - - - Edited a product ('{0}') - - - Edited a product attribute ('{0}') - - - Edited a product review (ID = {0}) - - - Edited a return request (ID = {0}) - - - Edited settings - - - Edited a specification attribute ('{0}') - - - Edited a state or province (ID = {0}) - - - Edited a store (ID = {0}) - - - Edited a task (ID = {0}) - - - Edited a topic ('{0}') - - - Edited a vendor (ID = {0}) - - - Edited a warehouse (ID = {0}) - - - Edited a widget (ID = {0}) - - - Started customer impersonation (Email: {0}, ID = {1}) - - - Finished customer impersonation (Email: {0}, ID = {1}) - - - Impersonated by store owner (Email: {0}, ID = {1}) - - - Impersonation by store owner was finished (Email: {0}, ID = {1}) - - - {0} categories were imported - - - {0} manufacturers were imported - - - {0} products were imported - - - {0} states and provinces were imported - - - Installed a new plugin (FriendlyName: '{0}') - - - Uninstalled a plugin (FriendlyName: '{0}') - - - Added a blog comment - - - Added a news comment - - - Added a product review ('{0}') - - - Added a product to compare list ('{0}') - - - Added a product to shopping cart ('{0}') - - - Added a product to wishlist ('{0}') - - - Used contact us form - - - Login - - - Logout - - - Placed a new order (ID = {0}) - - - Sent PM to customer ('{0}') - - - Public store. Viewed a category details page ('{0}') - - - Public store. Viewed a manufacturer details page ('{0}') - - - Public store. Viewed a product details page ('{0}') - - - Address - - - Address 1 - - - Address 1 is required. - - - Address 2 - - - Address 2 is required. - - - City - - - City is required. - - - Company - - - Company is required. - - - Country - - - Country is required. - - - Email - - - Email is required. - - - Fax number - - - Fax number is required. - - - First name - - - First name is required. - - - Last name - - - Last name is required. - - - Phone number - - - Phone number is required. - - - State / province - - - State / province is required. - - - Zip / postal code - - - Zip / postal code is required. - - - Other (Non US) - - - Select country - - - Select state - - - Admin - - - You do not have permission to perform the selected operation. - - - Access denied. - - - Address - - - Custom address attributes - - - Add a new address attribute - - - back to address attribute list - - - If the default form fields are not enough for your needs, then you can manage additional address attributes below. - - - Edit address attribute details - - - Attribute info - - - The new attribute has been added successfully. - - - The attribute has been deleted successfully. - - - The attribute has been updated successfully. - - - Name - - - Please provide a name. - - - The name of the address attribute. - - - Required - - - When an attribute is required, the customer must choose an appropriate attribute value before they can continue. - - - Control type - - - Choose how to display your attribute values. - - - Display order - - - The address attribute display order. 1 represents the first item in the list. - - - Attribute values - - - Add a new address value - - - Edit address value details - - - You need to save the address attribute before you can add values for this address attribute page. - - - Name - - - Please provide a name. - - - The name of the address value. - - - Pre-selected - - - Determines whether this attribute value is pre selected for the address. - - - Display order - - - The display order of the attribute value. 1 represents the first item in attribute value list. - - - Address 1 - - - Enter address 1. - - - Address 1 is required. - - - Address 2 - - - Enter address 2. - - - Address 2 is required. - - - City - - - Enter city. - - - City is required. - - - Company - - - Enter company. - - - Company is required. - - - Country - - - Select country. - - - Country is required. - - - Email - - - Enter email. - - - Email is required. - - - Fax number - - - Enter fax number. - - - Fax number is required. - - - First name - - - Enter first name. - - - First name is required. - - - Last name - - - Enter last name. - - - Last name is required. - - - Phone number - - - Enter phone number. - - - Phone number is required. - - - State / province - - - Select state / province. - - - State / province is required. - - - Zip / postal code - - - Enter zip / postal code. - - - Zip / postal code is required. - - - Other (Non US) - - - Select country - - - Select state - - - Affiliates - - - The new affiliate has been added successfully. - - - Add a new affiliate - - - back to affiliate list - - - Affiliated customers - - - Name - - - The affiliate has been deleted successfully. - - - Affiliate is an Internet-based marketing practice in which a business rewards one or more affiliates for each visitor or customer. It is a web-based pay-for-performance program designed to compensate affiliate partner for driving qualified leads or sales to a merchant web site. - - - Edit affiliate details - - - Active - - - A value indicating whether the affiliate is active. - - - Admin comment - - - Admin comment. For internal use. - - - Friendly URL name - - - A friendly name for generated affiliate URL (by default affiliate ID is used). It's more friendly for marketing purposes. Leave empty to use affiliate identifier. - - - Affiliate URL - - - When this hyperlink is clicked from the affiliate site, this site looks for an Affiliate ID query string parameter. If one exists, the customer is tagged with that affiliate. - - - Affiliate info - - - Load only with orders - - - Check to load affiliates only with orders placed (by affiliated customers). - - - Orders start date - - - The start date for the order search. - - - Orders end date - - - The end date for the order search. - - - First name - - - Search by a first name. - - - Friendly URL name - - - Search by a friendly URL name. - - - Last name - - - Search by a last name. - - - Affiliated orders - - - Created on - - - Order # - - - End date - - - The end date for the search. - - - Order status - - - Search by a specific order status e.g. Complete. - - - Order total - - - Payment status - - - Search by a specific payment status e.g. Paid. - - - Shipping status - - - Search by a specific shipping status e.g. Not yet shipped. - - - Start date - - - The start date for the search. - - - The affiliate has been updated successfully. - - - Catalog - - - Attributes - - - Checkout attributes - - - The new attribute has been added successfully. - - - Add a new checkout attribute - - - back to checkout attribute list - - - Condition - - - Attribute - - - Choose an attribute. - - - Enable condition - - - Check to specify a condition (depending on other attribute) when this attribute should be enabled (visible). - - - No attribute exists that could be used as condition. - - - You need to save the checkout attribute before you can edit conditional attributes. - - - The attribute has been deleted successfully. - - - Checkout attributes are displayed on the shopping cart page and provide the opportunity to offer more services to customers, i.e. gift wrapping, before placing the order. - - - Edit checkout attribute details - - - Control type - - - Choose how to display your attribute values. - - - Default value - - - Enter default value for attribute. - - - Display order - - - The checkout attribute display order. 1 represents the first item in the list. - - - Allowed file extensions - - - Specify a comma-separated list of allowed file extensions. Leave empty to allow any file extension. - - - Maximum file size (KB) - - - Specify maximum file size in kilobytes. Leave empty to skip this validation. - - - Required - - - When an attribute is required, the customer must choose an appropriate attribute value before they can continue. - - - Tax exempt - - - Determines whether this option is tax exempt (tax will not be applied to this option at checkout). - - - Limited to stores - - - Option to limit this attribute to a certain store. If you have multiple stores, choose one or several from the list. If you don't use this option just leave this field empty. - - - Maximum length - - - Specify maximum length. Leave empty to skip this validation. - - - Minimum length - - - Specify minimum length. Leave empty to skip this validation. - - - Name - - - The name of the checkout attribute. - - - Please provide a name. - - - Shippable product required - - - An option indicating whether shippable products are required in order to display this attribute. - - - Tax category - - - The tax classification for this attribute (used to calculate tax). You can manage tax categories by selecting Configuration : Tax : Tax Categories. - - - Text prompt - - - Enter text prompt. - - - Attribute info - - - The attribute has been updated successfully. - - - Attribute values - - - Add a new checkout value - - - Edit checkout value details - - - RGB color - - - Choose color to be used with the color squares attribute control. - - - Display order - - - The display order of the attribute value. 1 represents the first item in attribute value list. - - - Pre-selected - - - Determines whether this attribute value is pre selected for the customer. - - - Name - - - The name of the checkout value. - - - Please provide a name. - - - Price adjustment - - - The price adjustment applied when choosing this attribute value e.g. '10' to add 10 dollars. - - - Weight adjustment - - - The weight adjustment applied when choosing this attribute value. - - - You need to save the checkout attribute before you can add values for this checkout attribute page. - - - Product attributes - - - The new attribute has been added successfully. - - - Add a new product attribute - - - back to product attribute list - - - The attribute has been deleted successfully. - - - Product attributes are quantifiable or descriptive aspects of a product (such as, color). For example, if you were to create an attribute for color, with the values of blue, green, yellow, and so on, you may want to apply this attribute to shirts, which you sell in various colors (you can adjust a price or weight for any of existing attribute values). You can add attributes to existing product on a product details page. - - - Edit product attribute details - - - Description - - - The description of the product attribute. - - - Name - - - The name of the product attribute. - - - Please provide a name. - - - Info - - - Predefined values - - - Add a new value - - - Edit value - - - Cost - - - The attribute value cost is the cost of all the different components which make up this value. This may be either the purchase price if the components are bought from outside suppliers, or the combined cost of materials and manufacturing processes if the component is made in-house. - - - Display order - - - The display order of the attribute value. 1 represents the first item in attribute value list. - - - Is pre-selected - - - Determines whether this attribute value is pre selected for the customer. - - - Name - - - The attribute value name e.g. 'Blue' for Color attributes. - - - Please provide a name. - - - Price adjustment - - - The price adjustment applied when choosing this attribute value e.g. '10' to add 10 dollars. - - - Weight adjustment - - - The weight adjustment applied when choosing this attribute value. - - - Predefined (default) values are helpful for a store owner when creating new products. Then when you add the attribute to a product, you don't have to create the values again. - - - You need to save the product attribute before you can add values for this page. - - - The attribute has been updated successfully. - - - Used by products - - - Here you can see a list of products which use this attribute. - - - Product - - - Published - - - Specification attributes - - - The new attribute has been added successfully. - - - Add a new specification attribute - - - back to specification attribute list - - - The attribute has been deleted successfully. - - - Specification attributes are product features i.e, screen size, number of USB-ports, visible on product details page. Specification attributes can be used for filtering products on the category details page. Unlike product attributes, specification attributes are used for information purposes only. You can add attributes to existing product on a product details page. - - - Edit specification attribute details - - - Display order - - - The display order of the specification attribute. - - - Name - - - The name of the specification attribute. - - - Please provide a name. - - - Attribute info - - - Options - - - Add a new option - - - Edit option details - - - RGB color - - - Choose color to be used instead of an option text name (it'll be displayed as "color square"). - - - Display order - - - The display order of the option. - - - Specify color - - - Check to choose color to be used instead of an option text name (it'll be displayed as "color square"). - - - Name - - - The name of the option. - - - Please provide a name. - - - Number of associated products - - - You need to save the specification attribute before you can add options for this specification attribute page. - - - The attribute has been updated successfully. - - - Bulk edit products - - - Manage inventory - - - Name - - - Old price - - - Price - - - Published - - - SKU - - - Stock qty - - - Category - - - Search by a specific category. - - - Manufacturer - - - Search by a specific manufacturer. - - - Product name - - - A product name. - - - Categories - - - The new category has been added successfully. - - - Add a new category - - - back to category list - - - Breadcrumb - - - The category has been deleted successfully. - - - Edit category details - - - Limited to customer roles - - - Select customer roles for which the category will be shown. Leave empty if you want this category to be visible to all users. - - - Allow customers to select page size - - - Whether customers are allowed to select the page size from a predefined list of options. - - - Category template - - - Choose a category template. This template defines how this category (and its products) will be displayed. - - - Created on - - - Deleted - - - Description - - - The description of the category. - - - Discounts - - - Select discounts to apply to this category. You can manage discounts by selecting Discounts from the Promotions menu. - - - No discounts available. Create at least one discount before mapping. - - - Display order - - - Set the category's display order. 1 represents the top of the list. - - - Include in top menu - - - Display in the top menu bar. If this category is a subcategory, then ensure that its parent category also has this property enabled. - - - Limited to stores - - - Option to limit this category to a certain store. If you have multiple stores, choose one or several from the list. If you don't use this option just leave this field empty. - - - Meta description - - - Meta description to be added to category page header. - - - Meta keywords - - - Meta keywords to be added to category page header. - - - Meta title - - - Override the page title. The default is the name of the category. - - - Name - - - The category's name. - - - Please provide a name. - - - Page size - - - Set the page size for products in this category e.g. '4' products per page. - - - Page size should be positive. - - - Page Size options (comma separated) - - - Comma separated list of page size options (e.g. 10, 5, 15, 20). First option is the default page size if none are selected. - - - Page Size options should not have duplicate items. - - - Parent category - - - Select a parent category for this category. Leave this field empty to make this the root level category. - - - [None] - - - Picture - - - The category picture. - - - Price ranges - - - Define the price ranges for the store price range filter. Separate ranges with a semicolon e.g. '0-199;200-300;301-;' (301- means 301 and over). - - - Published - - - Check to publish this category (visible in store). Uncheck to unpublish (category not available in store). - - - Search engine friendly page name - - - Set a search engine friendly page name e.g. 'the-best-category' to make your page URL 'http://www.yourStore.com/the-best-category'. Leave empty to generate it automatically based on the name of the category. - - - Show on home page - - - Check if you want to show a category on home page. - - - Categories have been imported successfully. - - - Category info - - - Imported categories are distinguished by ID. If the ID already exists, then its corresponding category will be updated. You should not specify ID (leave 0) for new categories. - - - Category name - - - A category name. - - - Store - - - Search by a specific store. - - - Manage Categories - - - Products - - - Add a new product - - - Display order - - - Is featured product? - - - Product - - - You need to save the category before you can add products for this category page. - - - switch to list view - - - The category has been updated successfully. - - - Low stock report - - - Manufacturers - - - The new manufacturer has been added successfully. - - - Add a new manufacturer - - - back to manufacturer list - - - The manufacturer has been deleted successfully. - - - Discounts - - - Select discounts to apply to this manufacturer. You can manage discounts by selecting Discounts from the Promotions menu. - - - No discounts available. Create at least one discount before mapping. - - - Edit manufacturer details - - - Limited to customer roles - - - Select customer roles for which the manufacturer will be shown. Leave empty if you want this manufacturer to be visible to all users. - - - Allow customers to select page size - - - Whether customers are allowed to select the page size from a predefined list of options. - - - Created on - - - Deleted - - - Description - - - The description of the manufacturer. - - - Display order - - - Set the manufacturer's display order. 1 represents the top of the list. - - - Limited to stores - - - Option to limit this manufacturer to a certain store. If you have multiple stores, choose one or several from the list. If you don't use this option just leave this field empty. - - - Manufacturer template - - - Choose a manufacturer template. This template defines how this manufacturer (and its products) will be displayed. - - - Meta description - - - Meta description to be added to manufacturer page header. - - - Meta keywords - - - Meta keywords to be added to manufacturer page header. - - - Meta title - - - Override the page title. The default is the name of the manufacturer. - - - Name - - - The manufacturer's name. - - - Please provide a name. - - - Page size - - - Set the page size for products in this manufacturer e.g. '4' products per page. - - - Page size should be positive. - - - Page Size options (comma separated) - - - Comma separated list of page size options (e.g. 10, 5, 15, 20). First option is the default page size if none are selected. - - - Page Size options should have unique items. - - - Picture - - - The manufacturer picture. - - - Price ranges - - - Define the price ranges for the store price range filter. Separate ranges with a semicolon e.g. '0-199;200-300;301-;' (301- means 301 and over). - - - Published - - - Check to publish this manufacturer (visible in store). Uncheck to unpublish (manufacturer not available in store). - - - Search engine friendly page name - - - Set a search engine friendly page name e.g. 'the-best-manufacturer' to make your page URL 'http://www.yourStore.com/the-best-manufacturer'. Leave empty to generate it automatically based on the name of the manufacturer. - - - Manufacturers have been imported successfully. - - - Manufacturer info - - - Imported manufacturers are distinguished by ID. If the ID already exists, then its corresponding manufacturer will be updated. You should not specify ID (leave 0) for new manufacturers. - - - Manufacturer name - - - A manufacturer name. - - - Store - - - Search by a specific store. - - - Products - - - Add a new product - - - Display order - - - Is featured product? - - - Product - - - You need to save the manufacturer before you can add products for this manufacturer page. - - - The manufacturer has been updated successfully. - - - Product reviews - - - Approve selected - - - back to product review list - - - The product review has been deleted successfully. - - - Delete selected - - - Disapprove selected - - - Edit product review details - - - Created on - - - The date/time that the review was created. - - - Customer - - - The customer who created the review. - - - Is approved - - - Is the review approved? Marking it as approved means that it is visible to all your site's visitors. - - - Product - - - The name of the product that the review is for. - - - Rating - - - The customer's product rating. - - - Reply text - - - The reply text (by a store owner). If specified, then it'll be visible to a customer. Leave empty to ignore this functionality. - - - Review text - - - The review text. - - - Review text is required. - - - Store - - - A store name in which this review was written. - - - Title - - - The title of the product review. - - - Title is required. - - - Created from - - - The creation from date for the search. - - - Created to - - - The creation to date for the search. - - - Approved - - - Search by a "Approved" property. - - - All - - - Approved only - - - Disapproved only - - - Store - - - Search by a specific store. - - - Message - - - Search in title and review text. - - - Product - - - Search by a specific product. - - - The product review has been updated successfully. - - - Products - - - The new product has been added successfully. - - - Add a new product - - - Associated products (variants) - - - Add new associated product - - - Display order - - - Product - - - Associated products are used only with "grouped" products. - - - A product could be associated to only one "grouped" product. - - - You need to save the product before you can add associated products for this product page. - - - back to product list - - - Categories - - - Choose categories. You can manage product categories by selecting Catalog > Categories. - - - No categories available. - - - General information - - - The product has been copied successfully - - - Copy product - - - Copy images - - - Check to copy the images. - - - New product name - - - {0} - copy - - - The name of the new product. - - - Published - - - Check to mark a product as published. - - - {0}-copy - - - Cross-sells - - - Add new cross-sell product - - - Product - - - You need to save the product before you can add cross-sell products for this product page. - - - The Cross-sell products option provides the opportunity to buy additional products that generally go with the selected product. They are displayed at the bottom of the checkout page. - - - The product has been deleted successfully. - - - Downloadable product - - - Edit product details - - - The maximum allowed number of products has been exceeded ({0}) - - - Customer roles - - - Choose one or several customer roles i.e. administrators, vendors, guests, who will be able to see this product in catalog. If you don't need this option just leave this field empty. In order to use this functionality you have to disable the following setting: Configuration > Catalog settings > Ignore ACL rules (sitewide). - - - Additional shipping charge - - - The additional shipping charge. - - - Admin comment - - - This comment is for internal use only, not visible for customers. - - - Allow only existing attribute combinations - - - Check to allow adding to the cart/wishlist only attribute combinations that exist and have stock greater than zero. In this case you have to create all existing product attribute combinations that you have in stock. - - - Allow back in stock subscriptions - - - Allow customers to subscribe to a notification list for a product that has gone out of stock. - - - Allow customer reviews - - - Check to allow customers to review this product. - - - Allowed quantities - - - 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. - - - Associated to product - - - A "grouped" parent product which this one is associated to. - - - Automatically add these products to the cart - - - Check to automatically add these products to the cart. - - - Available end date - - - The end of the product's availability in Coordinated Universal Time (UTC). - - - Available for pre-order - - - Check if this item is available for Pre-Order. It also displays "Pre-order" button instead of "Add to cart". - - - Available start date - - - The start of the product's availability in Coordinated Universal Time (UTC). - - - Backorders - - - Select backorder mode. - - - PAngV (base price) enabled - - - Check to display baseprice of a product. This is required according to the German law (PAngV). If you sell 500ml of beer for 1,50 euro, then you have to display baseprice: 3.00 euro per 1L. - - - Amount in product - - - Enter an amount in product. - - - Unit of product - - - Enter a unit of product. - - - Reference amount - - - Enter a reference amount. - - - Reference unit - - - Enter a reference unit. - - - Call for price - - - Check to show "Call for Pricing" or "Call for quote" instead of price. - - - Created on - - - Date and time when this product was created. - - - Customer enters price - - - An option indicating whether customer should enter price. - - - Delivery date - - - Choose a delivery date which will be displayed in the public store. You can manage delivery dates by selecting Configuration > Shipping > Dates and ranges. - - - None - - - Disable buy button - - - Check 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. - - - Discounts - - - Select discounts to apply to this product. You can manage discounts by selecting Discounts from the Promotions menu. - - - No discounts available. Create at least one discount before mapping. - - - Display order - - - Display order of the product. 1 represents the top of the list. - - - Display availability - - - Check 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. - - - Download file - - - You can download file using URL or uploading from the computer. If you want to download file using URL check the box Use download URL. - - - Download activation type - - - A value indicating when download links will be enabled. - - - Number of days - - - The number of days during customers keeps access to the file (e.g. 14). Leave this field blank to allow continuous downloads. - - - Full description - - - Full description is the text that is displayed in product page. - - - Gift card type - - - There are two gift card types: virtual and physical. WARNING: not recommended to change the gift card type from one to another in a "live" store. - - - GTIN (global trade item number) - - - Enter global trade item number (GTIN). These identifiers include UPC (in North America), EAN (in Europe), JAN (in Japan), and ISBN (for books). - - - Has sample download file - - - You can download file using URL or uploading from the computer. If you want to download file using URL check the box Use download URL. - - - Has user agreement - - - Select this checkbox if the customer has a user agreement (a customer must agree with this user agreement when trying to download the product). - - - Height - - - The product height. - - - ID - - - The product identifier. - - - Downloadable product - - - Check if the product is downloadable. When customers purchase a downloadable product, they can download it direct from your store. The link will be visible after checkout. - - - Free shipping - - - Check if this product comes with FREE shipping. - - - Is gift card - - - Check if it is a gift card. After adding gift card products to the shopping cart and completing the purchases, you can then search and view the list of all the purchased gift cards by selecting Gift Cards from the Sales menu. - - - Recurring product - - - Check if it is a recurring product. For any product, you can define a recurring cycle to enable the system to automatically create orders that repeat when a customer purchases such products. - - - Is rental - - - Check if this is a rental product (price is set for some period). Please note that inventory management is not fully supported for rental products yet. It's recommended to set 'Manage inventory method' to 'Don't track inventory' now. - - - Shipping enabled - - - Check if the product can be shipped. You can manage shipping settings by selecting Configuration > Shipping. - - - Tax exempt - - - Determines whether this product is tax exempt (tax will not be applied to this product at checkout). - - - Telecommunications, broadcasting and electronic services - - - Check if it's telecommunications, broadcasting and electronic services. It's used for tax calculation in Europe Union. - - - Length - - - The product length. - - - Low stock activity - - - Action to be taken when your current stock quantity falls below (reaches) the 'Minimum stock quantity'. - - - Inventory method - - - Select inventory method. There are three methods: Don’t track inventory, Track inventory and Track inventory by attributes. You should use Track inventory by attributes when the product has different combinations of these attributes and then manage inventory for this combinations. - - - Manufacturer part number - - - The manufacturer's part number for this product. - - - Manufacturers - - - Choose the manufacturer. You can manage manufacturers by selecting Catalog > Manufacturers. - - - No manufacturers available. - - - Mark as new - - - Check to mark the product as new. Use this option for promoting new products. - - - Mark as new. Start date - - - Set Product as New from Date in Coordinated Universal Time (UTC). - - - Mark as new. End date - - - Set Product as New to Date in Coordinated Universal Time (UTC). - - - Maximum amount - - - Enter a maximum amount. - - - Max. downloads - - - The maximum number of downloads. - - - Meta description - - - Meta description to be added to product page header. - - - Meta keywords - - - Meta keywords to be added to product page header. - - - Meta title - - - Override the page title. The default is the name of the product. - - - Minimum amount - - - Enter a minimum amount. - - - Minimum stock qty - - - If you have enabled 'Manage Stock' you can perform a number of different actions when the current stock quantity falls below (reaches) your minimum stock quantity. - - - Product availability range - - - Choose the product availability range that indicates when the product is expected to be available when out of stock (e.g. Available in 10-14 days). You can manage availability ranges by selecting Configuration > Shipping > Dates and ranges. - - - None - - - Multiple warehouses - - - Check if you want to support shipping and inventory management from multiple warehouses. - - - Product name - - - The name of the product. - - - Please provide a name. - - - Notify for qty below - - - When the current stock quantity falls below (reaches) this quantity, a store owner will receive a notification. - - - Not returnable - - - Check if this product is not returnable. In this case a customer won't be allowed to submit return request. - - - Old price - - - The old price of the product. If you set an old price, this will display alongside the current price on the product page to show the difference in price. - - - Maximum cart qty - - - Set the maximum quantity allowed in a customer's shopping cart e.g. set to 5 to only allow customers to purchase 5 of this product. - - - Minimum cart qty - - - Set the minimum quantity allowed in a customer's shopping cart e.g. set to 3 to only allow customers to purchase 3 or more of this product. - - - Overridden gift card amount - - - Enter gift card amount that can be used after purchase. If not specified, then product price will be used. - - - Picture - - - Pre-order availability start date - - - The availability start date of the product configured for pre-order in Coordinated Universal Time (UTC). 'Pre-order' button will automatically be changed to 'Add to cart' at this moment. - - - Price - - - The price of the product. You can manage currency by selecting Configuration > Currencies. - - - Product cost - - - Product cost is a prime product cost. This field is only for internal use, not visible for customers. - - - Product tags - - - Product tags are the keywords for product identification. The more products associated with a particular tag, the larger it will show on the tag cloud. - - - Enter tags ... - - - Product template - - - Choose a product template. This template defines how this product will be displayed in public store. - - - Product type - - - Product type can be simple or grouped. In most cases your product will have the Simple product type. You need to use Grouped product type when a new product consists of one or more existing products that will be displayed on one single product details page. - - - Published - - - Check to publish this product (visible in store). Uncheck to unpublish (product not available in store). - - - Cycle length - - - Specify the cycle length. It is a time period recurring order can be repeated. - - - Cycle period - - - Specify the cycle period. It defines units time period can be measured in. - - - Total cycles - - - Total cycles are number of times customer will receive the recurring product. - - - Rental period length - - - Specify period length for rental product. Price is specified for this period. - - - Rental period - - - Specify period for rental product. Price is specified for this period. - - - Required product IDs - - - Add required product - - - Choose - - - Specify comma separated list of required product IDs. NOTE: Ensure that there are no circular references (for example, A requires B, and B requires A). - - - Require other products - - - Check if the product requires adding other products to the cart. - - - Sample download file - - - The sample download file. - - - Search engine friendly page name - - - Set a search engine friendly page name e.g. 'the-best-product' to make your page URL 'http://www.yourStore.com/the-best-product'. Leave empty to generate it automatically based on the name of the product. - - - Limited to stores - - - Option to limit this product to a certain store. If you have multiple stores, choose one or several from the list. If you don't use this option just leave this field empty. In order to use this functionality you have to disable the following setting: Configuration > Catalog settings > Ignore "limit per store" rules (sitewide). - - - Ship separately - - - Check if the product should be shipped separately from other products (in single box). Notice that if the order includes several items of this product, all of them will be shipped separately. - - - Short description - - - Short description is the text that is displayed in product list i.e. сategory / manufacturer pages. - - - Show on home page - - - Check to display this product on your store's home page. Recommended for your most popular products. - - - SKU - - - Product stock keeping unit (SKU). Your internal unique identifier that can be used to track this product. - - - Stock quantity - - - The current stock quantity of this product. - - - Quantity has been changed while you were editing the product. Changes haven't been saved. Please ensure that everything is correct and click "Save" button one more time. - - - Tax category - - - The tax classification for the product. You can manage tax categories by selecting Configuration > Tax > Tax Categories. - - - Unlimited downloads - - - When a customer purchases a download product, they can download the item unlimited number of times. - - - Updated on - - - Date and time when this product was updated. - - - User agreement text - - - The text of the user agreement. - - - Vendor - - - Choose a vendor associated with this product. This can be useful when running a multi-vendor store to keep track of goods associated with vendor. - - - No vendor - - - Visible individually - - - Check it if you want the product to be on catalog or search results. You can uncheck this box to hide associated products from catalog and make them accessible only from grouped product details page. - - - Warehouse - - - Choose the warehouse which will be used when calculating shipping rates. You can manage warehouses by selecting Configuration > Shipping > Warehouses. - - - None - - - Weight - - - The product weight. - - - Width - - - The product width. - - - Is reward point(s) - - - If this field is checked, reward points can be pruchased. - - - Overridden reward points exchange rate - - - Use this field to override reward points exchange rate setting when converting product price to reward points (points = [overridden exchange rate] * price). If not specified, then reward points exchange rate will be used. - - - Exclude from reward points - - - If this field is checked, product will be excluded from reward points calculation. - - - Gift card - - - Reward points - - - Products have been imported successfully. - - - Product info - - - Inventory - - - Download catalog as PDF - - - Go directly to product SKU - - - Enter product SKU and click Go. - - - Imported products are distinguished by SKU. If the SKU already exists, then its corresponding product will be updated. - - - Category - - - Search by a specific category. - - - Search sub categories - - - Check to search in sub categories. - - - Manufacturer - - - Search by a specific manufacturer. - - - Product name - - - A product name. - - - Product type - - - Search by a product type. - - - Published - - - Search by a "Published" property. - - - All - - - Published only - - - Unpublished only - - - Store - - - Search by a specific store. - - - Vendor - - - Search by a specific vendor. - - - Warehouse - - - Search by a specific warehouse. - - - Mappings - - - Pictures - - - Add product picture - - - Add a new picture - - - Display order - - - Display order of the picture. 1 represents the top of the list. - - - Alt - - - Override "alt" attribute for "img" HTML element. If empty, then a default rule will be used (e.g. product name). - - - Title - - - Override "title" attribute for "img" HTML element. If empty, then a default rule will be used (e.g. product name). - - - Picture - - - Choose a picture to upload. If the picture size exceeds your stores max image size setting, it will be automatically resized. - - - You need to save the product before you can upload pictures for this product page. - - - Prices - - - Product attributes - - - Attribute combinations - - - Add combination - - - Select new combination and enter details below - - - Note that some attribute control types that support custom user input (e.g. file upload, textboxes, date picker) are useless with attribute combinations - - - Allow out of stock - - - A value indicating whether to allow orders when out of stock. - - - Attributes - - - GTIN - - - Enter global trade item number (GTIN). These identifiers include UPC (in North America), EAN (in Europe), JAN (in Japan), and ISBN (for books). - - - Manufacturer part number - - - The manufacturer's part number for this attribute combination. - - - Notify admin for quantity below - - - When the current stock quantity falls below (reaches) this quantity, a store owner will receive a notification. - - - Overridden price - - - Override price for this attribute combination. This way a store owner can override the default product price when this attribute combination is added to the cart. For example, you can give a discount this way. Leave empty to ignore field. All other applied discounts will be ignored when this field is specified. - - - Sku - - - Product stock keeping unit (SKU). Your internal unique identifier that can be used to track this attribute combination. - - - Stock quantity - - - The current stock quantity of this combination. - - - Generate all possible combinations - - - Attributes - - - This attribute is already added to this product - - - Condition - - - Attribute - - - Choose an attribute. - - - Conditional attributes appear if a previous attribute is selected, such as having an option for personalizing clothing with a name and only providing the text input box if the "Personalize" radio button is checked - - - Enable condition - - - Check to specify a condition (depending on other attribute) when this attribute should be enabled (visible). - - - Edit condition - - - Attribute - - - Control type - - - Display order - - - Is Required - - - Text prompt - - - - Product attributes are quantifiable or descriptive aspects of a product (such as, color). For example, if you were to create an attribute for color, with the values of blue, green, yellow, and so on, you may want to apply this attribute to shirts, which you sell in various colors (you can adjust a price or weight for any of existing attribute values). - You can add attribute for your product using existing list of attributes, or if you need to create a new one go to Catalog > Attributes > Product attributes. Please notice that if you want to manage inventory by product attributes (e.g. 5 green shirts and 3 blue ones), then ensure that "Inventory method" is set to "Track inventory by product attributes". - - - - Validation rules - - - Default value - - - Enter default value for attribute. - - - Allowed file extensions - - - Specify a comma-separated list of allowed file extensions. Leave empty to allow any file extension. - - - Maximum file size (KB) - - - Specify maximum file size in kilobytes. Leave empty to skip this validation. - - - Maximum length - - - Specify maximum length. Leave empty to skip this validation. - - - Minimum length - - - Specify minimum length. Leave empty to skip this validation. - - - Edit rules - - - Values - - - Add a new value - - - back to product details - - - Add/Edit values for [{0}] attribute. Product: {1} - - - Edit value - - - Associated product - - - Associate a product - - - Choose an associated product - - - The associated product is downloadable, keep in mind that won't be able to download it. - - - The associated product is a gift card, keep in mind that customers can not specify its details in the product details page. - - - The associated product has attributes, keep in mind that customers can not select them in the product details page. - - - The associated product has required product attributes, so customers won't be able to choose this product attribute value. - - - Associated product. - - - Attribute value type - - - Choose your attribute value type. - - - RGB color - - - Choose color to be used with the color squares attribute control. - - - Cost - - - The attribute value cost is the cost of all the different components which make up this value. This may be either the purchase price if the components are bought from outside suppliers, or the combined cost of materials and manufacturing processes if the component is made in-house. - - - Display order - - - The display order of the attribute value. 1 represents the first item in attribute value list. - - - Square picture - - - Upload a picture to be used with the image squares attribute control. - - - Is pre-selected - - - Determines whether this attribute value is pre selected for the customer. - - - Name - - - The attribute value name e.g. 'Blue' for Color attributes. - - - Please provide a name. - - - Picture - - - Choose a picture associated to this attribute value. This picture will replace the main product image when this product attribute value is clicked (selected). - - - No picture - - - Price adjustment - - - The price adjustment applied when choosing this attribute value e.g. '10' to add 10 dollars. - - - Product quantity - - - Quantity should be greater than or equal to 1 - - - Specify quantity of the associated product which will be added. Minimum allowed value is 1. - - - Customer enters quantity - - - Allow customers enter the quantity of associated product. - - - Weight adjustment - - - The weight adjustment applied when choosing this attribute value. - - - Total: - - - Edit values - - - No product attributes available. Create at least one product attribute before mapping. - - - You need to save the product before you can add attributes for this page. - - - Warehouses - - - "Stock quantity" is total quantity. It's reduced from when a shipment is shipped. - - - "Reserved qty" is product quantity that is ordered but not shipped yet. - - - "Planned qty" is product quantity that is ordered and already added to a shipment but not shipped yet. - - - Planned qty - - - Reserved qty - - - Stock qty - - - Warehouse - - - No warehouses defined - - - Use - - - Manage inventory per warehouse. - - - Purchased with orders - - - Here you can see a list of orders in which this product was purchased. - - - Recurring product - - - Related products - - - Add new related product - - - Display order - - - Product - - - You need to save the product before you can add related products for this product page. - - - The Related Products option provides the opportunity to advertise products that are not part of the selected category, to your visitors. These products are displayed on the product details pages. - - - Require other products - - - Rental - - - Access control list - - - Shipping - - - Specification attributes - - - Add attribute - - - Add a new product specification attribute - - - Allow filtering - - - Allow product filtering by this attribute. - - - Attribute type - - - Choose attribute type. - - - Value - - - Custom value (text, hyperlink, etc). - - - Display order - - - Display order of the specification attribute. 1 represents the top of the list. - - - Show on product page - - - The value of the specification attribute. Be visible on the product page. - - - Attribute - - - Choose a product specification attribute. You can manage specification attributes from Catalog : Attributes : Product Specifications. - - - Attribute option - - - The value of the specification attribute. - - - Value - - - No specification attributes defined. - - - You need to save the product before you can add product specification attributes for this product page. - - - Specification attributes are product features i.e, screen size, number of USB-ports, visible on product details page. Specification attributes can be used for filtering products on the category details page. Unlike product attributes, specification attributes are used for information purposes only. - - - Stock quantity history - - - Here you can see a history of the product stock quantity changes. - - - Attribute combination - - - Created On - - - Message - - - Stock quantity - - - Quantity adjustment - - - Warehouse - - - Tier prices - - - Add new tier price - - - Edit tier price details - - - Customer role - - - All customer roles - - - Select customer role for which the tier price will be available. - - - End date - - - The end date of the tier price in Coordinated Universal Time (UTC). - - - Price - - - Specify the price. - - - Quantity - - - Specify quantity for which this tier price will be available. - - - Start date - - - The start date of the tier price in Coordinated Universal Time (UTC). - - - Store - - - All stores - - - Option to limit this tier price to a certain store. If you have multiple stores, choose one from the list. - - - You need to save the product before you can add tier prices for this product page. - - - Tier pricing is a promotional tool that allows a store owner to price items differently for higher quantities. - - - The product has been updated successfully. - - - Product tags - - - Edit product tag details - - - Tag name - - - The name of the product tag. - - - Please provide a name. - - - Tagged products - - - Are you sure you want to perform this action? - - - Add new - - - Add new record - - - All - - - Are you sure? - - - Cancel - - - Change - - - Check - - - Clear - - - Configuration is not required - - - CSV file - - - Delete - - - Delete (selected) - - - Are you sure you want to delete this item? - - - Are you sure you want to delete "{0}"? - - - Edit - - - Excel file - - - Export - - - Export to CSV - - - Export to Excel - - - Export to Excel (all found) - - - Export to Excel (selected) - - - Export to XML - - - Export to XML (all found) - - - Export to XML (selected) - - - Go - - - Hide - - - Import - - - Import from CSV - - - Import from Excel - - - Import requires a lot of memory resources. That's why it's not recommended to import more than 500 - 1,000 records at once. If you have more records, it's better to split them to multiple Excel files and import separately. - - - List - - - You are going to lose any unsaved changes. Are you sure? - - - No - - - No, cancel - - - Preview - - - Save - - - Save and Continue Edit - - - Search - - - Select - - - SEO - - - Show - - - Standard - - - Update - - - Please upload a file - - - View - - - Wrong email - - - Yes - - - Configuration - - - Access control list - - - Access control list is a list of permissions attached to customer roles. This list specifies the access rights of users to objects. - - - Permission name - - - The ACL has been updated successfully. - - - Activity Log - - - Activity Log - - - Activity Log Type - - - The Activity Log Type. - - - Activity Log Type - - - Message - - - Created On - - - Created from - - - The creation from date for the search. - - - Created to - - - The creation to date for the search. - - - Customer - - - Customer Email - - - A customer Email. - - - Activity Types - - - Is Enabled - - - Name - - - The types have been updated successfully. - - - Countries - - - The new country has been added successfully. - - - Add a new country - - - back to country list - - - The country has been deleted successfully. - - - Edit country details - - - Export states to CSV - - - Allows billing - - - Allow billing to customers located in this country. - - - Allows shipping - - - Allow shipping to customers located in this country. - - - Display order - - - The display order for this country. 1 represents the top of the list. - - - Limited to stores - - - Option to limit this country to a certain store. If you have multiple stores, choose one or several from the list. If you don't use this option just leave this field empty. - - - Name - - - The name of the country. - - - Please provide a name. - - - Number of states - - - Numeric ISO code - - - The numeric ISO code for this country. For a complete list of ISO codes go to: http://en.wikipedia.org/wiki/ISO_3166-1_numeric. - - - Published - - - Determines whether this country is published (visible for creation of shipping/billing addresses). - - - Subject to VAT - - - Value indicating whether customers in this country must be charged EU VAT (the European Union Value Added Tax). - - - Three letter ISO code - - - The three letter ISO code for this country. For a complete list of ISO codes go to: http://en.wikipedia.org/wiki/ISO_3166-1_alpha-3. - - - Three letter ISO code should be 3 characters long. - - - Three letter ISO code is required. - - - Two letter ISO code - - - The two letter ISO code for this country. For a complete list of ISO codes go to: http://en.wikipedia.org/wiki/ISO_3166-1_alpha. - - - Two letter ISO code should be 2 characters long. - - - Two letter ISO code is required. - - - Import states from CSV - - - You can download a CSV file with a list of states for other countries on the following page: - - - {0} states have been successfully imported - - - Country info - - - Publish selected - - - States and provinces - - - Add a new state/province - - - The state can't be deleted. It has associated addresses. - - - Edit state/province - - - Abbreviation - - - An abbreviation for this state/province. - - - Display order - - - The display order for this state. 1 represents the top of the list. - - - Name - - - The name of the state. - - - Please provide a name. - - - Published - - - Determines whether this state is published. - - - You need to save the country before you can add states for this country page. - - - Unpublish selected - - - The country has been updated successfully. - - - Currencies - - - The new currency has been added successfully. - - - Add a new currency - - - Apply rate - - - 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. - - - Edit currency details - - - Created on - - - The date/time the currency was created. - - - Currency code - - - The currency code. For a list of currency codes, go to: http://www.iso.org/iso/support/currency_codes_list-1.htm. - - - Currency code must be less than or equal to 5 characters. - - - Please provide a currency code. - - - Auto update enabled - - - Custom formatting - - - Custom formatting to be applied to the currency values. - - - Custom formatting must be less than or equal to 50 characters. - - - Display locale - - - The currency specific culture code. - - - Select locale - - - Currency culture must be valid. - - - Display order - - - The display order of this currency. 1 represents the top of the list. - - - Current exchange rate provider - - - Is primary exchange rate currency - - - Is primary store currency - - - Limited to stores - - - Option to limit this currency to a certain store. If you have multiple stores, choose one or several from the list. If you don't use this option just leave this field empty. - - - Mark as primary exchange rate currency - - - Mark as primary store currency - - - Name - - - The name of the currency. - - - Name must be less than or equal to 50 characters. - - - Please provide a name. - - - Published - - - Determines whether the currency is published. - - - Rate - - - The exchange rate against the primary exchange rate currency. - - - Exchange rate must be greater than 0. - - - Rounding type - - - The rounding type. - - - Get live rates - - - Live currency rates - - - Localization - - - At least one published currency is required - - - Select a currency - - - The currency has been updated successfully. - - - Email accounts - - - The new email account has been added successfully. - - - Add a new email account - - - back to email account list - - - The email account has been deleted successfully. - - - Edit email account details - - - Email display name - - - This is the friendly display name for outgoing emails from your store e.g. 'Your Store Sales Department'. - - - Email address - - - This is the from address for all outgoing emails from your store e.g. 'sales@yourstore.com'. - - - SSL - - - Check to use Secure Sockets Layer (SSL) to encrypt the SMTP connection. - - - Host - - - This is the host name or IP address of your mail server. You can normally find this out from your ISP or web host. - - - Is default email account - - - Mark as default email account - - - Password - - - Change password - - - This is the password you use to authenticate to your mail server. - - - The password has been changed successfully. - - - Port - - - This is the SMTP port of your mail server. This is usually port 25. - - - Send email to - - - The email address to which you want to send your test email. - - - Send test email to ensure that everything is properly configured. - - - Use default credentials - - - Check to use default credentials for the connection. - - - User - - - This is the username you use to authenticate to your mail server. - - - Send Test Email (save settings first by clicking "Save" button) - - - Send test email - - - Email has been successfully sent. - - - The email account has been updated successfully. - - - External authentication - - - back to extenal authentication method list - - - Configure - - - Display order - - - Friendly name - - - Is active - - - System name - - - Languages - - - The new language has been added successfully. - - - Add a new language - - - back to language list - - - The language has been deleted successfully. - - - Edit language details - - - Export resources - - - Default currency - - - This property allows a store owner to specify a default currency for a language. If not specified, then the default currency display order will be used. - - - Display order - - - The display order of this language. 1 represents the top of the list. - - - Flag image - - - Flag image file name - - - The flag image file name. The image should be saved into \images\flags\ directory. - - - Language culture - - - The language specific culture code. - - - Language culture must be valid. - - - Limited to stores - - - Option to limit this language to a certain store. If you have multiple stores, choose one or several from the list. If you don't use this option just leave this field empty. - - - Name - - - The name of the language. - - - Please provide a name. - - - Flag - - - Published - - - Determines whether this language is published and can therefore be selected by visitors to your store. - - - 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. - - - Unique SEO code - - - The unique two letter SEO code. It's used to generate URLs like 'http://www.yourStore.com/en/' when you have more than one published language. 'SEO friendly URLs with multiple languages' option should also be enabled. - - - Two letter SEO code should be 2 characters long. - - - Please provide a unique SEO code. - - - Import resources - - - The resources have been imported. - - - Info - - - At least one published language is required - - - String resources - - - Resource name - - - Please provide a resource name. - - - Value - - - Please provide a resource value. - - - A resource already exists with the name: {0} - - - You need to save the language before you can make or change resources for this language. - - - Resource name - - - Search by a specific resource. - - - Value - - - Search by a specific resource value. - - - The language has been updated successfully. - - - Xml file - - - NOTE: It can take up to several minutes. - - - NOTE: DO NOT click twice. - - - Measures - - - Dimensions - - - The primary dimension can't be deleted. - - - NOTE: if you change your primary dimension, then do not forget to update the appropriate ratios of the units - - - Display order - - - Is primary dimension - - - Mark as primary dimension - - - Name - - - Please provide a name. - - - Ratio to primary dimension - - - System keyword - - - Please provide a system keyword. - - - Weights - - - The primary weight can't be deleted. - - - NOTE: if you change your primary weight, then do not forget to update the appropriate ratios of the units - - - Display order - - - Is primary weight - - - Mark as primary weight - - - Name - - - Please provide a name. - - - Ratio to primary weight - - - System keyword - - - Please provide a system keyword. - - - Payment - - - Payment restrictions - - - Country - - - Please mark the checkbox(es) for the country or countries in which you want the payment method(s) not available - - - Settings have been updated successfully - - - Payment methods - - - back to payment method list - - - Configure - - - Display order - - - Friendly name - - - Is active - - - Logo - - - Recurring support - - - Supports capture - - - Partial refund - - - Refund - - - Void - - - System name - - - Plugins - - - Additional info - - - Manual plugin installation - - - You can download more nopCommerce plugins in our marketplace

    ]]>
    -
    - - Upload the plugin to the /plugins folder in your nopCommerce directory. - - - Restart your application (or click 'Reload list of plugins' button). - - - Scroll down through the list of plugins to find the newly installed plugin. - - - Click on the 'Install' link to install the plugin. - - - Note: If you're running nopCommerce in medium trust, then it's recommended to clear your \Plugins\bin\ directory - - - Edit plugin details - - - Limited to customer roles - - - Choose one or several customer roles i.e. administrators, vendors, guests, who will be able to use this plugin. If you don't need this option just leave this field empty. - - - Author - - - Configure - - - Display order - - - The display order of the plugin. 1 represents the top of the list. - - - Friendly name - - - The friendly name of the plugin. - - - Friendly name is required. - - - Group - - - Install - - - Installing plugin... - - - Installation - - - Installed - - - Is enabled - - - Indicates whether the plugin is enabled/active. - - - Limited to stores - - - Option to limit this plugin to a certain store. If you have multiple stores, choose one or several from the list. If you don't use this option just leave this field empty. - - - Logo - - - System name - - - Uninstall - - - Uninstalling plugin... - - - Version - - - Group - - - Search by a group. - - - Plugin Info - - - The plugin has been installed. - - - Load mode - - - Local plugins - - - Search by a load mode. - - - back to plugin list - - - Configure - - - All plugins and themes - - - Category - - - Search by category. - - - Download - - - Here you can find third-party extensions and themes which are developed by our community and partners.They are also available in our marketplace

    ]]>
    -
    - - Name - - - Search by name. - - - Picture - - - Price - - - Free - - - Search by price. - - - Commercial - - - Supported versions - - - Version - - - Search by version. - - - Reload list of plugins - - - Reloading plugin list... - - - The plugin has been uninstalled. - - - Settings - - - All settings (advanced) - - - Setting name - - - Please provide a setting name. - - - Store - - - All stores - - - Value - - - Name - - - Search by a specific setting name. - - - Value - - - Search by a specific setting value. - - - Blog settings - - - Allow guests to leave comments - - - Check to allow guests to leave comments. - - - Blog comments - - - Common - - - Blog comments must be approved - - - Check if blog comments must be approved by administrator. - - - Blog enabled - - - Check to enable the blog in your store. - - - Notify about new blog comments - - - Check to notify store owner about new blog comments. - - - Number of tags (cloud) - - - The number of blog tags that appear in the tag cloud. - - - Posts page size - - - Set the page size for posts. - - - Display blog RSS feed link in the browser address bar - - - Check to enable the blog RSS feed link in customers browser address bar. - - - Blog comments per store - - - Check to display blog comments written in the current store only. - - - Catalog settings - - - Allow anonymous users to email a friend - - - Check 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. - - - Allow product sorting - - - Check 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. - - - Allow viewing of unpublished product details page - - - Check to allow viewing of unpublished product details page. This way SEO won't be affected by search crawlers when a product is temporary unpublished. Please note that a store owner always has access to unpublished products. - - - Additional sections - - - Catalog pages - - - Compare products - - - Export/Import - - - Performance - - - Product fields - - - Product page - - - Product sorting - - - Product reviews - - - Search - - - Share - - - Tax - - - Tags - - - Cache product prices - - - Check to cache product prices. It can significantly improve performance. But you not should enable it if you use some complex discounts, discount requirement rules, or coupon codes. - - - Category breadcrumb enabled - - - Check to show category breadcrumb. - - - 'Compare Products' enabled - - - Check to allow customers to use the 'Compare Products' option in your store. - - - Discontinued message for unpublished products - - - Check to display "a product has been discontinued" message when viewing details pages of unpublished products. - - - Display tax/shipping info (footer) - - - Check to display tax and shipping info in the footer. This option is used in Germany. - - - Display tax/shipping info (order details page) - - - Check to display tax and shipping info on the order details page. This option is used in Germany. - - - Display tax/shipping info (product boxes) - - - Check to display tax and shipping info in product boxes (catalog pages). This option is used in Germany. - - - Display tax/shipping info (product details page) - - - Check to display tax and shipping info on product details pages. This option is used in Germany. - - - Display tax/shipping info (shopping cart) - - - Check to display tax and shipping info on the shopping cart page. This option is used in Germany. - - - Display tax/shipping info (wishlist) - - - Check to display tax and shipping info on the wishlist page. This option is used in Germany. - - - 'Email a friend' enabled - - - Check to allow customers to use the 'Email a friend' option in your store. - - - Export/Import products with attributes - - - Check if products should be exported/imported with product attributes. - - - Ignore discounts (sitewide) - - - Check to ignore discounts (sitewide). It can significantly improve performance. - - - Ignore featured products (sitewide) - - - Check to ignore featured products (sitewide). It can significantly improve performance. - - - Ignore ACL rules (sitewide) - - - Check to ignore ACL rules configured for entities (sitewide). Recommended to enable this setting if you don't use it. It can significantly improve performance. - - - Ignore "limit per store" rules (sitewide) - - - Check to ignore "limit per store" rules configured for entities (sitewide). Recommended to enable this setting if you have only one store or don't use it. It can significantly improve performance. - - - Include full description in compare products - - - Check 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. - - - Number of manufacturers to display - - - Enter the number of manufacturers to display in manufacturer navigation block. - - - Notify about new product reviews - - - Check to notify store owner about new product reviews. - - - 'New products' page enabled - - - Check to enable the 'New products' page in your store. - - - Number of products on 'New products' page - - - The number of products to display when 'New products' page is enabled. - - - Number of best sellers on home page - - - The number of best sellers on home page to display when 'Show best sellers on home page' option is enabled. - - - Number of product tags (cloud) - - - The number of product tags that appear in the tag cloud. - - - Share button code - - - A page share button code. By default, we're using AddThis service. - - - Product review possible only after product purchasing - - - Check if product can be reviewed only by customer who have already ordered it. - - - Product reviews must be approved - - - Check 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. - - - Number of also purchased products to display - - - The number of products also purchased by other customers to display when 'Products also purchased' option is enabled. - - - Allow customers to select 'Products by tag' page size - - - Whether customers are allowed to select the 'Products by tag' page size from a predefined list of options. - - - 'Products by tag' page. Products per page - - - Set the page size for products on 'Products by tag' page. - - - 'Products by tag' Page Size options (comma separated) - - - Comma separated list of page size options (e.g. 10, 5, 15, 20). First option is the default page size if none are selected. - - - Search autocomplete enabled - - - Check to enabled autocomplete in the search box. - - - Number of 'autocomplete' products to display - - - Change number of visible results shown in autocomplete dropdown when searching. - - - Search term minimum length - - - Specify minimum length of search term. - - - 'Recently viewed products' enabled - - - Check to allow customers to use the 'Recently viewed products' feature in your store. - - - Number of 'Recently viewed products' - - - The number of 'Recently viewed products' to display when 'Recently viewed products' option is enabled. - - - Search page. Allow customers to select page size - - - Search page. Check to allow customers to select the page size from a predefined list of options. - - - Search page. Page size options (comma separated) - - - Search page. Comma separated list of page size options (e.g. 10, 5, 15, 20). First option is the default page size if none are selected. - - - Search page. Products per page - - - Set the page size for products on 'Search' page. - - - Show best sellers on home page - - - Check 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). - - - Include subcategories (number of distinct products) - - - Check to including when showing the number of products besides each category. - - - Show "free shipping" icon - - - Check to show "free shipping" notification for products with this option enabled. - - - Show GTIN - - - Check to show GTIN in public store. - - - Show manufacturer part number - - - Check to show manufacturer part numbers in public store. - - - Show product images in autocomplete box - - - Determines whether product images should be displayed in the autocomplete search box. - - - Include products from subcategories - - - Check if you want a category details page to include products from subcategories. - - - Show SKU on catalog pages - - - Check to show product SKU on catalog pages in public store. - - - Show SKU on product details page - - - Check to show product SKU on the product details page in public store. - - - Reviews per store - - - Check to display reviews written in the current store only (on a product details page). - - - Show product reviews tab on 'My account' page - - - Check to show product reviews tab on 'My account' page. - - - Product reviews page size - - - Set the page size for product reviews e.g. '10' reviews per page. - - - Show a share button - - - Check to show share button on product details page. - - - Display order - - - Is active - - - Name - - - Customer settings - - - 'Accept privacy policy' enabled - - - Ask customers to accept privacy policy during registration. - - - Address form fields - - - 'City' enabled - - - Set if 'City' is enabled. - - - 'City' required - - - Check if 'City' is required. - - - 'Company' enabled - - - Set if 'Company' is enabled. - - - 'Company' required - - - Check if 'Company' is required. - - - 'Country' enabled - - - Set if 'Country' is enabled. - - - You can create and manage the address form fields available during checkout. - - - 'Fax number' enabled - - - Set if 'Fax number' is enabled. - - - 'Fax number' required - - - Check if 'Fax number' is required. - - - 'Phone number' enabled - - - Set if 'Phone number' is enabled. - - - 'Phone number' required - - - Check if 'Phone number' is required. - - - 'State/province' enabled - - - Set if 'State/province' is enabled. - - - 'Street address 2' enabled - - - Set if 'Street address 2' is enabled. - - - 'Street address 2' required - - - Check if 'Street address 2' is required. - - - 'Street address' enabled - - - Set if 'Street address' is enabled. - - - 'Street address' required - - - Check if 'Street address' is required. - - - 'Zip / postal code' enabled - - - Set if 'Zip / postal code' is enabled. - - - 'Zip / postal code' required - - - Check 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. - - - Allow customers to upload avatars - - - A value indicating whether customers are allowed to upload avatars. - - - Allow customers to change their usernames - - - A value indicating whether customers are allowed to change their usernames. - - - Allow viewing of customer profiles - - - A value indicating whether the viewing of customer profiles is allowed. - - - Account - - - Common - - - Default fields - - - External authentication - - - Password and security - - - Profile - - - Time zone - - - Allow customers to check the availability of usernames - - - A value indicating whether customers are allowed to check the availability of usernames (when registering or changing in 'My Account'). - - - 'City' enabled - - - Set if 'City' is enabled. - - - 'City' required - - - Check if 'City' is required. - - - 'Company' enabled - - - Set if 'Company' is enabled. - - - 'Company' required - - - Check if 'Company' is required. - - - 'Country' enabled - - - Set if 'Country' is enabled. - - - 'Country' required - - - Check if 'Country' is required. - - - Customer form fields - - - You can create and manage the customer form fields available during registration below. - - - Customer name format - - - Customer name format. - - - Customer settings - - - 'Date of Birth' enabled - - - Set if 'Date of Birth' is enabled. - - - Customer minimum age - - - Enter minimum allowed age. Leave empty if customers of all ages are allowed. - - - 'Date of Birth' required - - - Check if 'Date of Birth' is required. - - - Default avatar enabled - - - A value indicating whether to display default user avatar. - - - Default password format - - - Choose default password format. Please keep in mind that this setting will be applied only to the newly registered customers. - - - Default store time zone - - - The default store time zone used to display dates. - - - Force entering email twice - - - Force entering email twice during registration. - - - External authentication. Auto register enabled - - - Check to enable auto registration when using external authentication (e.g. using Facebokk or Twitter). - - - 'Fax number' enabled - - - Set if 'Fax number' is enabled. - - - 'Fax number' required - - - Check if 'Fax number' is required. - - - Maximum login failures - - - Maximum login failures to lockout account. Set 0 to disable this feature. - - - Lockout time (login failures) - - - Enter number of minutes to lockout users (for login failures). - - - 'Gender' enabled - - - Set if 'Gender' is enabled. - - - Hide 'Back in stock subscriptions' tab - - - Check 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. - - - Hide newsletter box - - - Check if you want to hide the newsletter subscription box. - - - Newsletter box. Allow to unsubscribe - - - Check if you want to allow customers to display "unsubscribe" option in the newsletter block. - - - 'Newsletter' enabled - - - Set if 'Newsletter' is enabled. - - - Newsletter ticked by default - - - A value indicating whether 'Newsletter' checkbox is ticked by default on the registration page. - - - Notify about new customer registration - - - Notify the store owner when a new customer is registered. - - - Password lifetime - - - Specify number of days for password expiration. Don't forget to check "EnablePasswordLifetime" property on customer role edit page for those roles, who will have to change passwords. - - - Password minimum length - - - Specify password minimum length. - - - Password recovery link. Days valid - - - Enter number of days for password recovery link. Set to 0 if it doesn't expire. - - - 'Phone number' enabled - - - Set if 'Phone number' is enabled. - - - 'Phone number' required - - - Check if 'Phone number' is required. - - - Require registration for downloadable products - - - Require account creation to purchase downloadable products. - - - Show customers' join date - - - A value indicating whether to show customers' join date. - - - Show customers' location - - - A value indicating whether customers' location is shown. - - - 'State/province' enabled - - - Set if 'State/province' is enabled. - - - 'State/province' required - - - Check if 'State/province' is required. - - - Store last visited page - - - When enabled, the last visited page will be stored. When disabled, it can improved performance. - - - 'Street address 2' enabled - - - Set if 'Street address 2' is enabled. - - - 'Street address 2' required - - - Check if 'Street address 2' is required. - - - 'Street address' enabled - - - Set if 'Street address' is enabled. - - - 'Street address' required - - - Check if 'Street address' is required. - - - Unduplicated passwords number - - - Specify the number of customer passwords that mustn't be the same as the previous one, enter 0 if the customer can use the same password time after time. - - - 'Usernames' enabled - - - Check to use usernames for login/registration instead of emails. 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. - - - 'Zip / postal code' enabled - - - Set if 'Zip / postal code' is enabled. - - - 'Zip / postal code' required - - - Check if 'Zip / postal code' is required. - - - Forum settings - - - Active discussions feed count - - - The count of topics to include in the Active Discussion feed. - - - Active discussions feed enabled - - - Enables RSS feed for Active Discussions topics. - - - Active discussions page size - - - Set the page size for active discussions page e.g. '10' results per page. - - - Allow customers to delete posts - - - A value indicating whether customers are allowed to delete posts that they created. - - - Allow customers to edit posts - - - A value indicating whether customers are allowed to edit posts that they created. - - - Allow customers to manage forum subscriptions - - - Check if you want to allow customers to manage forum subscriptions. - - - Allow guests to create posts - - - Set if you want to allow guests to create posts. - - - Allow guests to create topics - - - Set if you want to allow guests to create topics. - - - Allow users to vote for posts - - - Set if you want to allow users to vote for posts. - - - Allow private messages - - - Determines whether to allow private messages. - - - Common - - - Feeds - - - Page sizes - - - Permissions - - - Forum editor - - - Forum editor type. WARNING: not recommended to change in production environment. - - - Forum feed count - - - The count of topics to include in the forum feed. - - - Forum feeds enabled - - - Enables RSS feed for each forum. - - - Forums enabled - - - Check to enable forums. - - - Maximum votes per day - - - Maximum number of votes for user per day. - - - Notify about private messages - - - Indicates whether a customer should be notified by email about new private messages. - - - Posts page size - - - Set the page size for posts in topics e.g. '10' posts per page. - - - Relative date and time formatting - - - Click to enable relative date and time formatting (e.g. 2 hours ago, a month ago). - - - Search results page size - - - Set the page size for search results e.g. '10' results per page. - - - Show alert for PM - - - Shows an alert for new Private Messages. - - - Show customers post count - - - A value indicating whether to show customers post count. - - - Signature enabled - - - Add an opportunity for customers to specify signature. Signature will be displayed below each forum post. - - - Topics page size - - - Set the page size for topics in forums e.g. '10' topics per page. - - - General settings - - - Admin area allowed IP - - - 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 customers to select a theme - - - Check to allow customers to select a store theme. - - - Automatically detect language - - - Check to automatically detect language based on a customer browser settings. - - - Full-Text - - - CAPTCHA - - - Common - - - Localization - - - Pdf - - - Security - - - SEO - - - Sitemap - - - Social media - - - Top menu items - - - Enable canonical URLs - - - The goal of the canonicalization process is to transform a URL into a canonical URL so it is possible to determine if two syntactically different URLs may be equivalent. - - - A CAPTCHA is a program that can tell whether its user is a human or a computer.You've probably seen them — colorful images with distorted text at the bottom ofWeb registration forms. CAPTCHAs are used by many websites to prevent abuse from"bots," or automated programs usually written to generate spam. No computer programcan read distorted text as well as humans can, so bots cannot navigate sites protectedby CAPTCHAs. nopCommerce uses reCAPTCHA.

    ]]>
    -
    - - Captcha is enabled but the appropriate keys are not entered - - - CAPTCHA enabled - - - Check to enable CAPTCHA. - - - Show on apply for vendor account page - - - Check to show CAPTCHA on apply for vendor account page. - - - Show on blog page (comments) - - - Check to show CAPTCHA on blog page when writing a comment. - - - Show on contact us page - - - Check 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. - - - Show on 'email wishlist to a friend' page - - - Check to show CAPTCHA on 'email wishlist to a friend' page. - - - Show on login page - - - Check to show CAPTCHA on login page. - - - Show on news page (comments) - - - Check 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. - - - Show on registration page - - - Check to show CAPTCHA on registration page. - - - Convert non-western chars - - - Check to take out the accent marks in the letters of SEO names while keeping the letter. - - - tag]]> - - - tag(s) here. For example, some custom tag. Or leave empty if ignore this setting.]]> - - - Default meta description - - - The default meta description for pages in your store. You can override this for individual categories / products / manufacturer pages. - - - Default meta keywords - - - The default meta keywords for pages in your store. You can override these for individual categories / products / manufacturer pages. - - - Default store theme - - - You can get more themes on - - - The public store theme. You can download themes from the extensions page at www.nopcommerce.com. - - - Default page title - - - The default title for pages in your store. - - - Disable PDF invoices for pending orders - - - If checked, customers won't be allowed to print PDF invoices for pending orders. - - - Display "Blog" - - - Check if "Blog" menu item should be displayed in the top menu. - - - Display "Forums" - - - Check if "Forums" menu item should be displayed in the top menu. - - - Display "Contact us" - - - Check if "Contact us" menu item should be displayed in the top menu. - - - Display "My account" - - - Check if "My account" menu item should be displayed in the top menu. - - - Display "Home page" - - - Check if "Home page" menu item should be displayed in the top menu. - - - Display "New products" - - - Check if "New products" menu item should be displayed in the top menu. - - - Display "Search" - - - Check if "Search" menu item should be displayed in the top menu. - - - Display EU cookie law warning - - - Make the site EU cookie law compliant. When enabled, new customers will see an appropriate warning box. - - - CSS bundling and minification - - - Enable to combine (bundle) multiple CSS files into a single file. Do not enable if you're running nopCommerce in IIS virtual directory. Note that this functionality requires significant server resources (not recommended to use with cheap shared hosting plans). - - - JavaScript bundling and minification - - - Enable to combine (bundle) multiple JavaScript files into a single file. Note that this functionality requires significant server resources (not recommended to use with cheap shared hosting plans). - - - Enable XSRF protection for admin area - - - Check to enable XSRF protection for admin area. - - - Enable XSRF protection for public store - - - Check to enable XSRF protection for public store. - - - Encryption private key - - - Encryption key changed - - - The encryption private key used for storing sensitive data. - - - The new encryption key is the same as the old one - - - Encryption private key must be 16 characters long - - - Facebook page URL - - - Specify your Facebook page URL. Leave empty if you have no such page. - - - Force SSL for all site pages - - - By default not all site pages are SSL protected. Check to force SSL for the entire site. This setting is useful only when you have SSL enabled on your store details pages. - - - Full-Text is disabled - - - Full-Text is enabled - - - Disable Full-Text - - - Full-Text support has been disabled successfully. - - - Enable Full-Text - - - Full-Text support has been enabled successfully. - - - To prevent a full-text index from becoming bloated, Microsoft SQL Server has a mechanism that discards commonly occurring words that do not help the search. These words are called noise words, or stop words. Noise words are listed in the locale specific noise word files. For example, in the English locale, words such as "a," "and," "is," and "the" are in the English noise word file and are left out of the full-text index since they are empirically known to be useless to a search. Please contact your SQL Server administrator to get more information about it. - - - Full-Text is not supported by your database - - - Search mode - - - Choose Full-Text search mode. - - - Full-Text is supported by your database - - - Generate product META description - - - When enabled, product META descriptions will be automatically generated (if not specified on the product details page) based on product short description. - - - Google+ page URL - - - Specify your Google+ page URL. Leave empty if you have no such page. - - - Enable honeypot - - - Check to enable honeypot technique for registration page. - - - Invoice footer text (left column) - - - Enter the text that will appear at the bottom of generated invoices (left column). - - - Invoice footer text (right column) - - - Enter the text that will appear at the bottom of generated invoices (right column). - - - Load all locale resources on startup - - - When enabled, all locale resources will be loaded on application startup. The application start will be slower, but then all pages could be opened much faster. - - - It seems that you use Redis server for caching, keep in mind that enabling this setting create a lot of traffic between the Redis server and the application because of the large number of locales. - - - Load all localized properties on startup - - - When enabled, all localized properties (such as localized product properties) will be loaded on application startup. The application start will be slower, but then all pages could be opened much faster. It's used only when you have two or more languages enabled. Not recommended to enable when you have a large catalog (several thousand localized entities). - - - Load all search engine friendly names on startup - - - When enabled, all slugs (search engine friendly names) will be loaded on application startup. The application start will be slower, but then all pages could be opened much faster. Not recommended to enable when you have a large catalog (several thousand entities). - - - Logo - - - Upload your store logo. If not uploaded, then the default one will be used. - - - Open Graph META tags - - - Check to generate Open Graph META tags on the product details page. - - - Page title SEO adjustment - - - Select a page title SEO adjustment. For example, generated page title could be (PAGE NAME | YOURSTORE.COM) instead of (YOURSTORE.COM | PAGE NAME). - - - Page title separator - - - Specify page title separator. - - - Use Letter page size - - - If checked, uses Letter page size for PDF documents. Uses A4 page size if unchecked. - - - PDF logo - - - Image file that will be displayed in PDF order invoices. A small image is recommended. - - - reCAPTCHA private key - - - Enter reCAPTCHA private key (if enabled). - - - reCAPTCHA public key - - - Enter reCAPTCHA public key (if enabled). - - - reCAPTCHA version - - - Select version of the reCAPTCHA. - - - SEO friendly URLs with multiple languages enabled - - - When enabled, your URLs will be http://www.yourStore.com/en/ or http://www.yourStore.com/ru/ (SEO friendly). - - - Sitemap enabled - - - Check to enable sitemap. - - - Sitemap includes categories - - - Check if you want to include categories in sitemap. - - - Sitemap includes manufacturers - - - Check if you want to include manufacturers in sitemap. - - - Sitemap includes products - - - Check if you want to include products in sitemap. - - - Store closed - - - Check to close the store. Uncheck to re-open. - - - Contact us page. 'Subject' field - - - Check to allow a customer to type a subject on the contact us page. - - - Twitter page URL - - - Specify your Twitter page URL. Leave empty if you have no such page. - - - Twitter META tags - - - Check to generate Twitter META tags on the product details page. - - - Use images for language selection - - - Check if you want to use images for language selection. - - - Contact us page. Use system email - - - Check to use your system email as "From" field when sending emails from contact us page. Otherwise, customer email will be used (please note that some email services do not allow it). - - - WWW prefix requirement - - - Choose your store WWW prefix requirement. For example, http://yourStore.com/ could be automatically redirected to http://www.yourStore.com/. - - - YouTube channel URL - - - Specify your YouTube channel URL. Leave empty if you have no such page. - - - Media settings - - - Associated product image size - - - The default size (pixels) for associated product images (part of 'grouped' products). - - - Avatar image size - - - The default size (pixels) for avatar images. - - - Common - - - Other pages - - - Product - - - Cart/Wishlist thumbnail image size - - - The default size (pixels) for product thumbnail images on the shopping cart and wishlist. - - - Category thumbnail image size - - - The default size (pixels) for product thumbnail images on category pages. - - - Default image quality (0 - 100) - - - The image quality to be used for uploaded images. Once changed you have to manually delete already generated thumbs. - - - Picture zoom - - - Check to enable picture zoom on product details page. - - - Import product images using hash - - - Check to use fast HASHBYTES (hash sum) database function to compare pictures when importing products. Please note that this functionality is not supported by some database. - - - Manufacturer thumbnail image size - - - The default size (pixels) for product thumbnail images on manufacturer pages. - - - Maximum image size - - - The maximum image size (longest side) allowed for image uploads. - - - Mini-shopping cart thumbnail image size - - - The default size (pixels) for product thumbnail images in the mini-shopping cart block. - - - Multiple thumb directories - - - Check to enable multiple thumb directories. It can be helpful if your hosting company has some limitations to the number of allowed files per directory. - - - Pictures are stored into... - - - Change - - - database - - - file system - - - A value indicating whether pictures are stored in database or file system. - - - NOTE: Do not forget to backup your database before changing this option - - - Product detail image size - - - The default size (pixels) for product detail images. - - - Product thumbnail image size (catalog) - - - The default size (pixels) for product thumbnail images displayed on category or manufacturer pages. - - - Product thumbnail image size (product page) - - - The default size (pixels) for product thumbnail images displayed on product details page when you have more than one product image. - - - Vendor thumbnail image size - - - The default size (pixels) for vendor thumbnail images. - - - Basic - - - Advanced - - - News settings - - - Allow guests to leave comments - - - Check to allow guests to leave comments. - - - News comments - - - Common - - - News enabled - - - Check to enable the news in your store. - - - Number of items to display - - - The number of news items to display on your home page. - - - News archive page size - - - A number of news displayed on one page. - - - News comments must be approved - - - Check if news comments must be approved by administrator. - - - Notify about new news comments - - - Check 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. - - - News comments per store - - - Check to display news comments written in the current store only. - - - Show on home page - - - Check to display your news items on your store home page. - - - Order settings - - - Activate gift cards after completing of an order - - - Check to activate related gift cards when an order is completed. - - - Anonymous checkout allowed - - - Check to enable anonymous checkout (customers are not required to login/register when purchasing products). - - - Attach PDF invoice ("order completed" email) - - - Check to attach PDF invoice to the "order completed" email sent to a customer. - - - Attach PDF invoice ("order paid" email) - - - Check to attach PDF invoice to the "order paid" email sent to a customer. - - - Attach PDF invoice ("order placed" email) - - - Check to attach PDF invoice to the "order placed" email sent to a customer. - - - Auto update order totals - - - Check to automatically update order totals on editing an order in admin area. IMPORANT: currently this functionality is in BETA testing status. - - - Checkout - - - Common - - - Gift cards - - - Order totals - - - Pdf invoice - - - Complete order when delivered - - - Check if an order status should be set to "Complete" only when its shipping status is "Delivered". Otherwise, "Shipped" status will be enough. - - - Order number mask - - - {DD} - day of order creation date - - - {ID} -Order identifier - - - {MM} - month of order creation date - - - {YY} - last two digits of year of order creation date - - - {YYYY} - year of order creation date - - - Order number mask, for creating custom order number. For example, RE-{YYYY}-{MM}. Leave this field empty if you don't want to use custom order numbers. - - - Deactivate gift cards after cancelling of an order - - - Check to deactivate related gift cards when an order is cancelled. - - - Deactivate gift cards after deleting of an order - - - Check to deactivate related gift cards when an order is deleted. - - - Disable "Billing address" step - - - Check to disable "Billing address" step during checkout. Billing address will be pre-filled and saved using the default registration data (this option cannot be used with guest checkout enabled). Also ensure that appropriate address fields that cannot be pre-filled are not required (or disabled). - - - Disable "Order completed" page - - - When disabled, customers will be automatically redirected to the order details page. - - - Export orders with products - - - Check if orders should be exported with products. - - - Is re-order allowed - - - Check if you want to allow customers to make re-orders. - - - Min order sub-total amount - - - Min order total amount. - - - Calculate 'Min order sub-total amount' including tax - - - Check to calculate 'Min order sub-total amount' value including tax; otherwise excluding tax. - - - Min order total amount - - - Enter minimum order total amount. - - - Number of days that the return request is available - - - Set a certain number of days that the Return Request Link will be available in the customer area. For example, if the store owner allows returns within 30 days of purchase, they would set this to 30. Logged in customers, viewing orders in My Account, would then not see Return Request buttons for orders completed more than thirty days ago. - - - Order totals on payment info tab - - - Check to display a product list and order totals on the payment info tab (one-page checkout). - - - Use one page checkout - - - One page checkout is a single web page your customers can use to buy a products/service from you. - - - Order ID - - - 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. - - - Order settings - - - Return request actions - - - The new return request action has been added successfully. - - - Add new return request action - - - back to return request action list - - - The return request action has been deleted successfully. - - - Display order - - - The return request action display order. 1 represents the first item in the list. - - - Edit return request action details - - - List of actions a customer will be able to choose when submitting a return request. - - - Please provide a name. - - - Name - - - The return request action name. - - - The return request action has been updated successfully. - - - Return request number mask - - - Return request number mask. For example, RMA-{ID}-{YYYY}-{MM}-{DD}. - - - {DD} - day of return request creation date - - - {ID} - Return request identifier - - - {MM} - month of return request creation date - - - {YYYY} - year of return request creation date - - - {YY} - last two digits of year of return request creation date - - - Return request reasons - - - The new return request reason has been added successfully. - - - Add new return request reason - - - back to return request reason list - - - The return request reason has been deleted successfully. - - - Display order - - - The return request reason display order. 1 represents the first item in the list. - - - Edit return request reason details - - - List of reasons a customer will be able to choose when submitting a return request. - - - Please provide a name. - - - Name - - - The return request reason name. - - - The return request reason has been updated successfully. - - - Allow file uploads - - - Check if you want to allow customers to upload files when submitting return requests. - - - The returns system will allow your customers to request a return on items they've purchased. These are also known as RMA requests. - - - NOTE: This option is available for completed orders. - - - Enable Returns System - - - Check if you want to allow customers to submit return requests for items they've previously purchased. - - - Return request settings - - - Terms of service (confirm order page) - - - Require customers to accept or decline terms of service before processing the order (on the confirm order page). - - - Terms of service (shopping cart page) - - - Require customers to accept or decline terms of service before processing the order (on the shopping cart page). - - - Last used Invoice No. - - - Invoice ID counter. This is useful if you want your invoices to start from certain number. This only affects invoices henceforward. The value must be greater than the current maximum invoice ID. - - - Year of last invoice - - - Year of last invoice. If actual year is different from this setting, then InvoiceIdent starts from one. - - - Customer roles - - - Additional shipping charge - - - Admin comment - - - Allow back in stock subscriptions - - - Allow customer reviews - - - Allowed quantities - - - Allow only existing attribute combinations - - - Available end date - - - Available for pre-order - - - Available start date - - - Backorders - - - General information - - - Advanced product types - - - Advanced product types - - - Price - - - Tabs and display options - - - Shipping - - - Access control list - - - Inventory - - - Mappings - - - Linked products - - - Settings - - - Call for price - - - Created on - - - Cross-sells products - - - Customer enters price - - - Delivery date - - - Dimensions - - - Disable buy button - - - Disable wishlist button - - - Discounts - - - Display order - - - Display availability - - - Display stock qty - - - Downloadable product - - - Free shipping - - - GTIN (global trade item number) - - - ID - - - Is gift card - - - Is rental - - - Low stock activity - - - Manufacturer part number - - - Manufacturers - - - Mark as new - - - Mark as new. End date - - - Mark as new. Start date - - - Maximum cart qty - - - Minimum cart qty - - - Minimum stock qty - - - Check fields you want to see on the product details page in the "basic" mode. - - - Settings - - - Notify for qty below - - - Not returnable - - - Old price - - - One column product page - - - PAngV (base price) enabled - - - Product attributes - - - Product availability range - - - Product cost - - - Product tags - - - Product template - - - Product type - - - Published - - - Purchased with orders - - - Recurring product - - - Related products - - - Require other products - - - SEO - - - Ship separately - - - Show on home page - - - Specification attributes - - - Stock quantity history - - - Stores - - - Telecommunications, broadcasting and electronic services - - - Tier prices - - - Vendor - - - Visible individually - - - Updated on - - - Multiple warehouses - - - Warehouses - - - Weight - - - Reward point(s) - - - Reward points - - - Common - - - The Reward Points Program allows customers to earn points for certain actions they take on the site. Points are awarded based on making purchases and customer actions such as registration. - - - Display how much will be earned - - - Check to display how much point will be earned before checkout. - - - Earning Reward Points - - - Each - - - spent will earn - - - reward points - - - Enabled - - - Check if you want to enable the Reward Points Program. - - - Exchange rate - - - Specify reward points exchange rate. - - - 1 reward point = - - - Minimum reward points to use - - - Customers won't be able to use reward points before they have X amount of points. Set to 0 if you do not want to use this setting. - - - Page size - - - Set the page size for history of reward points on 'My account' page. - - - Points accumulated for all stores - - - Check to accumulate all reward points in one balance for all stores so they can be used in any store. Otherwise, each store has its own rewards points and they can only be used in that store. WARNING: not recommended to change in production environment with several stores already created. - - - Points for purchases - - - Specify number of points awarded for purchases. - - - Activate points immediately - - - Activates bonus points immediately after their calculation - - - Reward points activation - - - Specify how many days (hours) must elapse before earned points become active. Points earned by purchase cannot be redeemed until activated. For example, you may set the days before the points become available to 7. In this case, the points earned will be available for spending 7 days after the order gets chosen awarded status. - - - Points for registration - - - Specify number of points awarded for registration. - - - Specify if earned reward points are taxable - - - If Earned reward points are taxable, then OrderAmount and tax will be reduced (like discounts do). Otherwise payment amount is reduced like with gift cards. - - - Include shipping - - - Specify if shipping amount should be included in reward points calculation. - - - Include payment method fee - - - Specify if payment method additional fee should be included in reward points calculation. - - - Exclude gift card(s) - - - Specify if gift cards should be excluded in reward points calculation. This setting allows to earn reward points, although order was paid or partially paid with giftcards. - - - Exclude purchased reward points - - - Specify if purchased points should be excluded in reward points calculation. This setting allows to earn reward points, although order total is reduced or zero. - - - Earn points only when using purchased points - - - Specify if reward points can only be earned when using purchased reward points for payment. With this earning of reward points is connected to the use of purchased points. - - - Shipping settings - - - "Pick Up in Store" enabled - - - A value indicating whether "Pick Up in Store" option is enabled during checkout. Please ensure that you have at least one active pickup point provider. - - - Checkout - - - Common - - - Notifications - - - Bypass shipping method page if there's only one - - - Check to bypass a shipping method page during checkout if there's only one shipping method available. - - - Consider associated products dimensions and weight - - - Check to consider associated products dimensions and weight on shipping, uncheck for example if the main product already includes them. - - - Display pickup points on the map - - - Check to display pickup points on the map. - - - Display shipment events (customers) - - - Check if you want your customers to see shipment events on their shipment details pages (if supported by your shipping rate computation method). - - - Display shipment events (store owner) - - - Check if you want a store owner to see shipment events on the shipment details pages of admin area (if supported by your shipping rate computation method). - - - Estimate shipping enabled - - - Check 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. - - - Calculate 'X' including tax - - - Check to calculate 'X' value including tax; otherwise excluding tax. - - - Value of 'X' - - - All orders with a total greater than the value of 'X' will qualify for free shipping. - - - Google maps API key - - - Specify Google maps API key. - - - Hide shipping total if shipping not required - - - Check if you want Hide 'Shipping total' label if shipping not required. - - - Notify customer about shipping from multiple locations - - - Check if you want customers to be notified when shipping from multiple locations. - - - Ship to the same address - - - Check to display "ship to the same address" option during checkout ("billing address" step). In this case "shipping address" with appropriate options (e.g. pick up in store) will be skipped. Also note that all billing countries should support shipping ("Allow shipping" checkbox ticked). - - - Shipping origin - - - Use warehouse location - - - Check to use warehouse location when requesting shipping rates. This is useful when you ship from multiple warehouses. - - - Shopping cart settings - - - Allow guests to email their wishlists - - - Check to allow guests to email their wishlists to friends. - - - Allow cart item editing - - - Check to allow customers to edit items already placed in the cart or wishlist. It could be useful when your products have attributes or any other fields entered by a customer. - - - Allow 'out of stock' items to be added to wishlist - - - Check to allow 'out of stock' products to be added to wishlist. - - - Common - - - Mini shopping cart - - - Wishlist - - - Carts shared between storest - - - Determines whether shopping carts (and wishlist) are shared between stores (in multi-store environment). - - - Number of 'Cross-Sells' - - - The number of 'Cross-Sells' to display on shopping cart page; 0 if you don't want to load cross-sells. - - - Display cart after adding product - - - If checked, a customer will be taken to the Shopping Cart page immediately after adding a product to their cart. If unchecked, a customer will stay on the same page that they are adding the product to the cart from. - - - Display wishlist after adding product - - - If checked, a customer will be taken to the Wishlist page immediately after adding a product to their wishlist. If unchecked, a customer will stay on the same page that they are adding the product to the wishlist from. - - - Allow customers to email their wishlists - - - Check to allow customers to email their wishlists to friends. - - - Maximum shopping cart items - - - Maximum number of distinct products allowed in a shopping cart. - - - Maximum wishlist items - - - Maximum number of distinct products allowed in a wishlist. - - - Show mini-shopping cart - - - Check to enable mini-shopping cart. - - - Number of products in mini-shopping cart - - - Specify the maximum number of products which can be displayed in the mini-shopping cart block. - - - 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. - - - Show discount box - - - Check if you want the discount coupon box to be displayed on shopping cart page. - - - Show gift card box - - - Check if you want the gift card coupon box to be displayed on shopping cart page. - - - Show product images in mini-shopping cart - - - Determines whether product images should be displayed in the mini-shopping cart block. - - - Show product images on cart - - - Determines whether product images should be displayed in your store shopping cart. - - - Show product images on wishlist - - - Determines whether product images should be displayed on customer wishlists. - - - Multi-store configuration for - - - All stores - - - Check/uncheck all - - - (check boxes if you want to set a custom value for this shop). - - - Tax settings - - - Allow customers to select tax display type - - - A value indicating whether customers are allowed to select tax display type. - - - Common - - - Payment - - - Shipping - - - Tax dispaying - - - VAT - - - Default tax address (used for tax calculation) - - - Default tax category - - - Select default tax category for products. - - - Display all applied tax rates - - - A value indicating whether each tax rate should be displayed on a separate line (shopping cart page). - - - Display tax suffix - - - A value indicating whether to display tax suffix (incl tax/excl tax). - - - Allow VAT exemption - - - Check if this store will exempt eligible VAT-registered customers from VAT. - - - Assume VAT always valid - - - Check to skip VAT validation. Entered VAT numbers will always be valid. It will be a client's responsibility to provide the correct VAT number. - - - 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. - - - EU VAT enabled - - - Check to enable EU VAT (the European Union Value Added Tax). - - - Your shop country - - - Select your shop country for VAT calculation. - - - 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). - - - Force tax exclusion from order subtotal - - - Check to always exclude tax from order subtotal (no matter of selected tax dispay type). This setting effects only pages where order totals are displayed. - - - Hide tax in order summary - - - A value indicating whether to hide tax in order summary when prices are shown tax inclusive. - - - Hide zero tax - - - A value indicating whether to hide zero tax in order summary. - - - Payment method additional fee includes tax - - - A value indicating whether payment method additional fee includes tax. - - - Payment method additional fee is taxable - - - A value indicating whether payment method additional fee is taxable. - - - Payment method additional fee tax category - - - Select tax category used for payment method additional fee tax calculation. - - - Prices include tax - - - A value indicating whether entered prices include tax. - - - Shipping is taxable - - - A value indicating whether shipping is taxable. - - - Shipping price includes tax - - - A value indicating whether shipping price includes tax. - - - Shipping tax category - - - Select tax category used for shipping tax calculation. - - - Tax based on - - - Tax based on. - - - Tax based on pickup point address - - - A value indicating whether to use pickup point address (when pickup point is chosen) for tax calculation. - - - [None] - - - Tax display type - - - Tax display type. - - - Vendor settings - - - Allow customers to apply for vendor account - - - Check to allow customers users to fill a form to become a new vendor. Then a store owner will have to manually approve it. - - - Allow customers to contact vendors - - - Check to allow customers to contact vendors. - - - Allow search by vendor - - - Check to allow customers to search by vendor on advanced search page. - - - Allow vendors to edit info - - - Check to allow vendors to edit information about themselves (in public store). Please note that localizable properties (name, description) are not supported in case if you have multiple languages (only standard values can be edited in this case). - - - Allow vendors to import products - - - Check if vendors are allowed to import products. - - - Catalog - - - Common - - - Maximum number of products - - - Sets a maximum number of products per vendor. - - - Notify about vendor information changes - - - Check to notify a store owner about vendor information changes. - - - Show vendor on product details page - - - Check to display a vendor name on the product details page (if associated). - - - Number of vendors to display - - - Enter the number of vendors to display in vendor navigation block. - - - Shipping - - - Dates and ranges - - - Delivery dates - - - The new delivery date has been added successfully. - - - Add a new delivery date - - - back to delivery date list - - - The delivery date has been deleted successfully. - - - Edit delivery date details - - - Display order - - - The display order of this delivery date. 1 represents the top of the list. - - - Name - - - Enter delivery date name. - - - Please provide a name. - - - List of delivery dates which will be available for choice in product details. - - - The delivery date has been updated successfully. - - - Shipping methods - - - The new shipping method has been added successfully. - - - Add a new shipping method - - - back to shipping method list - - - The shipping method has been deleted successfully. - - - Shipping methods used by offline shipping providers. For example, "Manual (Fixed or By Weight)". - - - Edit shipping method details - - - Description - - - Enter shipping method description. - - - Display order - - - The display order of this shipping method. 1 represents the top of the list. - - - Name - - - Enter shipping method name. - - - Please provide a name. - - - Manage shipping methods - - - The shipping method has been updated successfully. - - - Pickup point providers - - - back to pickup point provider list - - - Configure - - - Display order - - - Friendly name - - - Is active - - - Logo - - - System name - - - Pickup points - - - Product availability ranges - - - The new product availability range has been added successfully. - - - Add a new product availability range - - - back to product availability range list - - - The product availability range has been deleted successfully. - - - Edit product availability range details - - - Display order - - - The display order of this product availability range. 1 represents the top of the list. - - - Name - - - Enter product availability range name. - - - Please provide a name. - - - List of availability ranges which will be available for choice in product details. - - - The product availability range has been updated successfully. - - - Shipping providers - - - back to shipping rate computation method list - - - Configure - - - Display order - - - Friendly name - - - Is active - - - Logo - - - System name - - - Shipping rate computation methods (providers) - - - Shipping method restrictions - - - Country - - - Please mark the checkbox(es) for the country or countries in which you want the shipping method(s) not available - - - Shipping method restrictions - - - The settings have been updated successfully. - - - Warehouses - - - The new warehouse has been added successfully. - - - Add a new warehouse - - - back to warehouse list - - - The warehouse has been deleted successfully. - - - Edit warehouse details - - - Address - - - Name - - - Enter a warehouse name. - - - Please provide a name. - - - Admin comment - - - Admin comment. For internal use. - - - The warehouse has been updated successfully. - - - SMS providers - - - back to SMS provider list - - - Configure - - - Friendly name - - - Is active - - - System name - - - Stores - - - The new store has been added successfully. - - - Add a new store - - - back to store list - - - The store has been deleted successfully. - - - Edit store details - - - Company address - - - Enter your company address. - - - Company name - - - Enter your company name. - - - Company phone number - - - Enter your company phone number. - - - Company VAT - - - Enter your company VAT (the European Union Value Added Tax). - - - Default language - - - This property allows a store owner to specify a default language for a store. If not specified, then the default language display order will be used. - - - Display order - - - The display order for this store. 1 represents the top of the list. - - - HOST values - - - The comma separated list of possible HTTP_HOST values (for example, "yourstore.com,www.yourstore.com"). This property is required only when you run a multi-store solution to determine the current store. - - - Store name - - - Enter the name of your store e.g. Your Store. - - - Please provide a name. - - - Secure URL - - - The secure URL of your store e.g. https://www.yourstore.com/ or http://sharedssl.yourstore.com/. Leave it empty if you want nopCommerce to detect secure URL automatically. - - - SSL enabled - - - Check if your store will be SSL secured. - - - WARNING: Do not enable it until you have SSL certificate installed on the server. - - - Store URL - - - The URL of your store e.g. http://www.yourstore.com/. - - - Please provide a store URL. - - - The store has been updated successfully. - - - Tax - - - Tax categories - - - Display order - - - Name - - - Please provide a name. - - - Tax providers - - - back to tax provider list - - - Configure - - - Friendly name - - - Is primary provider - - - Mark as primary provider - - - System name - - - The settings have been updated successfully. - - - Content management - - - Blog - - - Blog posts - - - The new blog post has been added successfully. - - - Add a new blog post - - - back to blog post list - - - The blog post has been deleted successfully. - - - Edit blog post details - - - Allow comments - - - When checked, customers can leave comments about your blog entry. - - - Body - - - The body of this blog entry. - - - Body is required. - - - Body overview - - - Brief overview of blog post. If specified, then it will be used instead of full body on the main blog page. HTML is supported. - - - View comments - - - Created on - - - End date - - - Set the blog post end date in Coordinated Universal Time (UTC). You can also leave it empty. - - - Language - - - The language of this blog entry. A customer will only see the blog entries for their selected language. - - - Limited to stores - - - Option to limit this blog post to a certain store. If you have multiple stores, choose one or several from the list. If you don't use this option just leave this field empty. - - - Meta description - - - Meta description to be added to blog post page header. - - - Meta keywords - - - Meta keywords to be added to blog post page header. - - - Meta title - - - Override the page title. The default is the title of the blog post. - - - Search engine friendly page name - - - Set a search engine friendly page name e.g. 'the-best-blog-post' to make your page URL 'http://www.yourStore.com/the-best-blog-post'. Leave empty to generate it automatically based on the title of the blog post. - - - Start date - - - Set the blog post start date in Coordinated Universal Time (UTC). You can also leave it empty. - - - Tags - - - Tags are keywords that this blog post can also be identified by. Enter a comma separated list of the tags to be associated with this blog post. - - - Dots are not supported by tags. - - - Enter tags ... - - - Title - - - The title of this blog entry. - - - Title is required. - - - Info - - - Store - - - Search by a specific store. - - - The blog post has been updated successfully. - - - Blog comments - - - Approve selected - - - Delete selected - - - Disapprove selected - - - Blog post - - - Comment - - - Created on - - - Customer - - - Is approved - - - Store name - - - Created from - - - The creation from date for the search. - - - Created to - - - The creation to date for the search. - - - Approved - - - Search by a "Approved" property. - - - All - - - Approved only - - - Disapproved only - - - Message - - - Search in comment text. - - - Forums - - - Forum - - - The new forum has been added successfully. - - - back to forum list - - - Add New Forum - - - The forum has been deleted successfully. - - - Edit forum details - - - Created on - - - Description - - - The description of the forum. This is the description that the customer will see. - - - Display Order - - - The display order of the forum. 1 represents the top of the list. - - - Forum Group - - - Choose a forum group. - - - Forum group is required. - - - Name - - - The name of this forum. This is the name that the customer will see. - - - Forum group name is required. - - - The forum has been updated successfully. - - - Forum Group - - - The new forum group has been added successfully. - - - back to forum group list - - - Add New Forum Group - - - The forum group has been deleted successfully. - - - Edit Forum Group Details - - - Created on - - - Display Order - - - The display order of the forum group. 1 represents the top of the list. - - - Name - - - The name of this forum group. This is the name that the customer will see. - - - Forum Group Name is required. - - - The forum group has been updated successfully. - - - Manage Forums - - - Message templates - - - back to return message template list - - - The message template has been copied successfully - - - Copy template - - - The message template has been deleted successfully. - - - Notify about new blog comments in Configuration - Settings - Blog settings.]]> - - - Allow back in stock subscriptions in Product info tab - Inventory section.]]> - - - This message template is used when a customer changes an email address in his account. The customer receives a message to confirm an email address used when changing email address. - - - This message template is used the option Email notification from Registration method dropdown list in Configuration - Settings - Customer settings is selected. The customer receives a message to confirm an email address used when registering. - - - This message template is used when the customer gets a notification about a new order being placed from this account. - - - Show alert for PM in Configuration - Settings - Forum settings.]]> - - - This message template is used when a customer forgets a password needed to log in the account. The customer receives a message with a link for entering a new password. - - - This message template is used to welcome a new customer after registration. - - - This message template is used when a new forum post in certain forum topic is created. The message is received by a store owner. - - - This message template is used when a new forum topic is created. The message is received by a store owner. - - - This message template is used to send a notification to a customer about getting a gift card. - - - Notify about new customer registration in Configuration - Settings - Customer settings.]]> - - - This message template is used to notify a customer about a new return request submitted from his/her account. - - - This message template is used when a new return request is created. The message is received by a store owner. - - - Notify about new news comments in Configuration - Settings - News settings.]]> - - - This message template is used when a customer subscribes to the newsletter to confirm the subscription. - - - Newsletter box. Allow to unsubscribe in Configuration - Settings - Customer settings.]]> - - - Notify admin when a new VAT number is submitted in Configuration - Settings - Tax settings.]]> - - - This message template is used to notify a customer that the certain order was canceled. The order can ba canceled by a customer on the account page or by store owner in Customers - Customers in Orders tab or in Sales - Orders. - - - This message template is used to notify a customer that the certain order was completed. The order gets the order status Complete when it''s paid and delivered, or it can be changed manually to Complete in Sales - Orders. - - - This message template is used to notify a customer that the certain order was paid. The order gets the payment status Paid when the amount was charged, or it can be changed manually Sales - Orders by clicking Mark as paid button in Payment status. - - - This message template is used to notify a store owner that the certain order was paid. The order gets the status Paid when the amount was charged, or it can be changed manually Sales - Orders by clicking Mark as paid button in Payment status. - - - This message template is used to notify a vendor that the certain order was paid. The order gets the status Paid when the amount was charged. - - - This message template is used to notify a customer that the certain order was placed. Orders can be viewed by a customer on the account page. - - - This message template is used to notify a store owner that the certain order was placed. Orders can be viewed by a store owner in Sales - Orders or in Customers menu. - - - This message template is used to notify a vendor that the certain order was placed. Orders can be viewed by a vendor in Sales - Orders. You can allow them to access this part of Admin Area in Configuration - Access control list. - - - This message template is used to notify a customer that the certain order was refunded. The customer can submit to refund the order when the payment status is paid on the account page, or it can be done by a store owner in Sales - Orders by clicking "Refund" button. - - - This message template is used to notify a store owner that the certain order was refunded. The customer can submit to refund the order when the payment status is paid on the account page, or it can be done by a store owner in Sales - Orders by clicking "Refund" button. - - - Notify about new product reviews in Configuration - Settings - Catalog settings.]]> - - - This message template is used to notify a store owner that the certain product attribute combination is getting low stock. You can set up the combination minimum quantity when creating or editing the product in Product attribute tab - Attributes combinations tab in Notify admin for quantity below field. - - - Minimum stock qty field.]]> - - - This message template is used to notify a customer that the certain recurring payment is canceled. Payment can be canceled by a customer in the account page or by a store owner in Sales - Recurring payments in History tab by clicking "Cancel recurring payment" button. - - - This message template is used to notify a store owner that the certain recurring payment is canceled. Payment can be canceled by a customer in the account page or by a store owner in Sales - Recurring payments in History tab by clicking "Cancel recurring payment" button. - - - This message template is used to notify a customer that the certain recurring payment is failed. For example, the amount can''t be charged from the provided credit card. - - - This message template is used to notify a customer that the request status to the certain order is changed. You can set up this option in Sales - Return requests by clicking "Notify customer about status change" button. - - - This message template is used to notify a store owner about a message sent through the contact form. - - - This message template is used to notify a vendor about a message sent through the contact form. - - - This message template is used to send "email a friend" message. - - - Display shipment events (customers).]]> - - - Display shipment events (customers).]]> - - - Allow customers to apply for vendor account.]]> - - - Allow vendors to edit info.]]> - - - This message template is used when a customer wants to share some product from the wishlist with a friend by sending an email. You can set up this option by ticking the checkbox Allow customers to email their wishlists in Configuration - Settings - Shopping cart settings. - - - Edit message template details - - - Allowed message tokens - - - This is a list of the message tokens you can use in your emails. - - - Attached static file - - - Upload a static file you want to attach to each sent email. - - - Has attached file - - - BCC - - - The blind carbon copy (BCC) recipients for this e-mail message. - - - Body - - - The body of your message. - - - Body is required. - - - Delay send - - - The delay before sending message. - - - Email account - - - Standard - - - The email account that will be used to send this message template. - - - Is active - - - Indicating whether the message template is active. - - - Limited to stores - - - Option to limit this template to a certain store. If you have multiple stores, choose one or several from the list. If you don't use this option just leave this field empty. - - - Name - - - The name of this template (read only). - - - Send immediately - - - Send message immediately. - - - Subject - - - The subject of the message (email). TIP - You can include tokens in your subject. - - - Subject is required. - - - Store - - - Search by a specific store. - - - Test template - - - back to template - - - Send - - - Send email to - - - Send test email to ensure that everything is properly configured. - - - Email has been successfully queued. - - - Tokens - - - Please enter tokens you want to be replaced below - - - Enter tokens. - - - Send test email - - - For conditional expressions use the token %if (your conditions ) ... endif% - - - The message template has been updated successfully. - - - News - - - News comments - - - Approve selected - - - Delete selected - - - Disapprove selected - - - Comment text - - - Comment title - - - Created on - - - Customer - - - Is approved - - - News item - - - Store name - - - Created from - - - The creation from date for the search. - - - Created to - - - The creation to date for the search. - - - Approved - - - Search by a "Approved" property. - - - All - - - Approved only - - - Disapproved only - - - Message - - - Search in title and comment text. - - - News items - - - The new news item has been added successfully. - - - Add a new news item - - - back to news item list - - - The news item has been deleted successfully. - - - Edit news item details - - - Allow comments - - - When checked, customers can leave comments about your news entry. - - - View comments - - - Created on - - - End date - - - Set the news item end date in Coordinated Universal Time (UTC). You can also leave it empty. - - - Full description - - - The full description of this news entry. - - - Full description is required. - - - Language - - - The language of this news entry. A customer will only see the news entries for their selected language. - - - Limited to stores - - - Option to limit this news item to a certain store. If you have multiple stores, choose one or several from the list. If you don't use this option just leave this field empty. - - - Meta description - - - Meta description to be added to news page header. - - - Meta keywords - - - Meta keywords to be added to news page header. - - - Meta title - - - Override the page title. The default is the title of the news. - - - Published - - - Determines whether the news item is published (visible) in your store. - - - Search engine friendly page name - - - Set a search engine friendly page name e.g. 'the-best-news' to make your page URL 'http://www.yourStore.com/the-best-news'. Leave empty to generate it automatically based on the title of the news. - - - Short description - - - The short description of this news entry. - - - Short description is required. - - - Start date - - - Set the news item start date in Coordinated Universal Time (UTC). You can also leave it empty. - - - Title - - - The title of this news entry. - - - Title is required. - - - Info - - - Store - - - Search by a specific store. - - - The news item has been updated successfully. - - - Polls - - - The new poll has been added successfully. - - - Add a new poll - - - Poll answers - - - Display order - - - Name - - - Name is required. - - - Number of votes - - - You need to save the poll before you can add answers for this poll page. - - - back to poll list - - - The poll has been deleted successfully. - - - Edit poll details - - - Allow guests to vote - - - Check to allow guests to vote. - - - Display order - - - The display order of this poll. 1 represents the top of the list. - - - End date - - - Set the poll end date in Coordinated Universal Time (UTC). You can also leave it empty. - - - Language - - - The language of this poll. A customer will only see the polls for their selected language. - - - Name - - - The name of this poll. - - - Name is required. - - - Published - - - Determines whether this poll is published (visible) in your store. - - - Show on home page - - - Check if you want to show poll on home page. - - - Start date - - - Set the poll start date in Coordinated Universal Time (UTC). You can also leave it empty. - - - System keyword - - - The system keyword for this poll. For example, you can enter 'LeftColumnPoll' in order to display it in the left column. - - - Poll Info - - - The poll has been updated successfully. - - - Topics (pages) - - - The new topic has been added successfully. - - - Add a new topic - - - back to topic list - - - The topic has been deleted successfully. - - - Edit topic details - - - Accessible when store closed - - - Check to allow customer to view this topic details page when the store is closed. - - - Customer roles - - - Select customer roles for which the topic will be shown. Leave empty if you want this topic to be visible to all users. - - - Body - - - The body of your topic. - - - Display order - - - The topic display order. 1 represents the first item in the list. It's used with properties such as "Include in top menu" or "Include in footer". - - - Include in footer (column 1) - - - Check to include this topic in the footer (column 1). Ensure that your theme supports it. - - - Include in footer (column 2) - - - Check to include this topic in the footer (column 2). Ensure that your theme supports it. - - - Include in footer (column 3) - - - Check to include this topic in the footer (column 3). Ensure that your theme supports it. - - - Include in sitemap - - - Check to include this topic in the sitemap. - - - Include in top menu - - - Check to include this topic in the top menu. - - - Password protected - - - Check if this topic is password protected. - - - Limited to stores - - - Option to limit this topic to a certain store. If you have multiple stores, choose one or several from the list. If you don't use this option just leave this field empty. - - - Meta description - - - Meta description to be added to topic page header. - - - Meta keywords - - - Meta keywords to be added to topic page header. - - - Meta title - - - Override the page title. The default is the name of the topic. - - - Password - - - The password to access the content of this topic. - - - Published - - - Determines whether this topic is published (visible) in your store. - - - Search engine friendly page name - - - Set a search engine friendly page name e.g. 'some-topic-name' to make your page URL 'http://www.yourStore.com/some-topic-name'. Leave empty to generate it automatically based on the title of the topic. - - - Store - - - All stores - - - Choose a store this topic is assigned to. - - - System name - - - System name of this topic. - - - Title - - - The title of your topic. - - - Topic template - - - Choose a topic template. This template defines how this topic will be displayed. - - - URL - - - The URL of this topic. - - - Info - - - Store - - - Search by a specific store. - - - The topic has been updated successfully. - - - Widgets - - - back to widget list - - - Widget zone - - - Choose widget zone. - - - Configure - - - Display order - - - Friendly name - - - Is active - - - System name - - - Current shopping carts - - - Customer - - - Product - - - Quantity - - - Store - - - Total - - - Total items - - - Unit price - - - Updated on - - - Current wishlists - - - Customers - - - Custom customer attributes - - - The new attribute has been added successfully. - - - Add a new customer attribute - - - back to customer attribute list - - - The attribute has been deleted successfully. - - - If the default form fields are not enough for your needs, then you can manage additional customer attributes below. - - - Edit customer attribute details - - - Control type - - - Choose how to display your attribute values. - - - Display order - - - The customer attribute display order. 1 represents the first item in the list. - - - Required - - - When an attribute is required, the customer must choose an appropriate attribute value before they can continue. - - - Name - - - The name of the customer attribute. - - - Please provide a name. - - - Attribute info - - - The attribute has been updated successfully. - - - Attribute values - - - Add a new customer value - - - Edit customer value details - - - Display order - - - The display order of the attribute value. 1 represents the first item in attribute value list. - - - Pre-selected - - - Determines whether this attribute value is pre selected for the customer. - - - Name - - - The name of the customer value. - - - Please provide a name. - - - You need to save the customer attribute before you can add values for this customer attribute page. - - - Customer roles - - - The new customer role has been added successfully. - - - Add a new customer role - - - back to customer role list - - - The customer role has been deleted successfully. - - - Edit customer role details - - - Active - - - System customer roles can't be disabled. - - - Check to make this role active. - - - Enable password lifetime - - - Check to force customers to change their passwords after a specified time. - - - Free shipping - - - Check to allow customers in this role to get free shipping on their orders. - - - Is system role - - - A value indicating whether it's a system role. - - - Name - - - The name of the customer role. - - - Please provide a name. - - - Purchased with product - - - Choose product - - - A customer is added to this customer role once a specified product is purchased (paid). Please note that in case of refund or order cancellation you have to manually remove a customer from this role. - - - You cannot specify "Purchased with product" value for "Registered" customer role - - - Remove - - - System name - - - The system name of system customer roles can't be edited. - - - The system name of the customer role. - - - Tax exempt - - - Check to allow customers in this role to make tax free purchases. - - - The customer role has been updated successfully. - - - Customers - - - Activity log - - - Activity Log Type - - - Comment - - - Created on - - - IP address - - - The IP address for the search. - - - Add the customer to 'Guests' or 'Registered' customer role - - - The new customer has been added successfully. - - - Add a new customer - - - Addresses - - - Add new address - - - The new address has been added successfully. - - - Add a new address - - - back to customer details - - - Edit address - - - The address has been updated successfully. - - - You can't deactivate the last administrator. At least one administrator account should exists. - - - You can't remove the Administrator role. At least one administrator account should exists. - - - You can't delete the last administrator. At least one administrator account should exists. - - - A customer with a vendor associated could not be in "Administrators" role. - - - External authentication - - - Authentication method - - - Email - - - External identifier - - - A list of external authentication identifiers. - - - Back in stock subscriptions - - - Store - - - Product - - - Subscribed on - - - back to customer list - - - A customer in the Vendors role should have a vendor account associated. - - - You're not allowed to change passwords of administrators. Only administrators can do it. - - - You're not allowed to delete administrators. Only administrators can do it. - - - Current shopping cart - - - Current wishlist - - - The customer has been deleted successfully. - - - Edit customer details - - - Active - - - Is this customer's account active. To disable/suspend a customer's account, uncheck this. - - - Admin comment - - - Admin comment. For internal use. - - - Affiliate - - - The affiliate who the customer is linked to. - - - Remove affiliate - - - City - - - The city. - - - City is required - - - Company name - - - The company name. - - - Company is required. - - - Country - - - The country. - - - Created on - - - The date the customer record is created. - - - Customer roles - - - Choose customer roles of this user. - - - No customer roles available. Create at least one customer role before mapping. - - - Date of birth - - - The customer's date of birth. - - - Email - - - The customer's email. - - - Please provide an email. - - - Fax - - - The fax. - - - Fax is required - - - First name - - - The customer's first name. - - - Please provide a first name. - - - Name - - - Gender - - - Female - - - The customer's gender. - - - Male - - - IP Address - - - The last used IP address. - - - Is tax exempt - - - Determines whether the customer is tax exempt. - - - Last activity - - - The last activity date. - - - Last name - - - The customer's last name. - - - Please provide a last name. - - - Last visited page - - - The last visited date (public store). - - - Newsletter - - - Check to subscribe to newsletter. - - - Password - - - Change password - - - The customer's password. - - - Phone - - - The phone. - - - Phone is required - - - Registered in the store - - - Indicating in which store the customer is registered - - - State / province - - - The state / province. - - - Address - - - The address. - - - Street address is required - - - Address 2 - - - The address 2. - - - Street address 2 is required - - - System name - - - The system name of the customer role. - - - Time zone - - - The time zone. - - - Username - - - The customer's username. - - - Please provide a username. - - - VAT number - - - Enter company VAT number. NOTE: Enter VAT number with country code (e.g. GB 111 111 11). - - - Mark VAT number as invalid - - - Mark VAT number as valid - - - status: {0} - - - Manager of vendor - - - Choose a vendor associated to this customer account. When associated this customer will be able to login to the chosen vendor portal and manage his products and orders. - - - Not a vendor - - - Zip / postal code - - - The zip / postal code. - - - Zip / postal code is required - - - The customer cannot be in both 'Guests' and 'Registered' customer roles - - - Place order (impersonate) - - - Place order - - - During the order placement process, you will see almost exactly what the customer would see while browsing this site, with the exception of the header menu (you will see the following text 'Impersonated as customer@email.here - finish session'). Navigate to the products the customer wants and add them to the cart exactly as the customer would, then use the 'Checkout' button to proceed through the usual checkout process. - - - Note: Click 'finish session' link in order to finish this session - - - Customer info - - - Customer roles - - - Filter by customer role. - - - Company - - - Search by company. - - - Date of birth - - - Day - - - Filter by date of birth. Don't select any value to load all records. - - - Month - - - Email - - - Search by a specific email. - - - First name - - - Search by a first name. - - - Last name - - - Search by a last name. - - - Phone - - - Search by a phone number. - - - Username - - - Search by a specific username. - - - Zip code - - - Search by zip code. - - - IP address - - - Search by IP address. - - - A non-admin user cannot impersonate as an administrator - - - Orders - - - Created on - - - Order # - - - Order status - - - Order total - - - Payment status - - - Shipping status - - - Store - - - The password has been changed successfully. - - - Re-send activation message - - - Activation email has been successfully sent. - - - Reward points - - - The points will be activated on {0} - - - Add (reduce) reward points - - - Add (reduce) points - - - Message - - - Enter message (comment). - - - Store - - - Choose a store. It's useful only when you have "Points accumulated for all stores" setting disabled. - - - Value - - - Enter points to add. Negative values are also supported (reduce points). - - - Purchased value - - - Indicate if added reward points are purchased points. Negative values are also supported. - - - Date - - - Message - - - Points - - - Points balance - - - Points purchased - - - Points balance purchased - - - Store - - - Send email - - - The email body - - - Enter the message of email. - - - Planned date of sending - - - The specific send date and time. - - - The email has been queued successfully. - - - Send immediately - - - Send message immediately. - - - The subject - - - Enter the subject of email. - - - Send private message - - - The message - - - Enter the message of PM. - - - The PM has been sent successfully. - - - The subject - - - Enter the subject of PM. - - - Send welcome message - - - Welcome email has been successfully sent. - - - The customer has been updated successfully. - - - Valid Email is required for customer to be in 'Registered' role - - - Note: if you have a vendor associated with this customer, then also ensure it is in "Vendors" customer role. - - - Guest - - - Online customers - - - Customer info - - - IP Address - - - Last activity - - - Last visited page - - - "Store last visited page" setting is disabled - - - Location - - - Customer reports - - - Customers by number of orders - - - Customers by order total - - - End date - - - The end date for the search. - - - Customer - - - Number of orders - - - Order total - - - Order status - - - Search by a specific order status e.g. Complete. - - - Payment status - - - Search by a specific payment status e.g. Paid. - - - Shipping status - - - Search by a specific shipping status e.g. Not yet shipped. - - - Start date - - - The start date for the search. - - - Registered customers - - - Count - - - Period - - - In the last 14 days - - - In the last 7 days - - - In the last month - - - In the last year - - - Run report - - - New customers - - - Month - - - Week - - - Year - - - Dashboard - - - Common statistics - - - Orders - - - Registered customers - - - Pending return requests - - - Low stock products - - - More info - - - Incomplete orders - - - Latest Orders - - - View All Orders - - - Download uploaded file - - - Download URL - - - Download object is saved - - - Remove download - - - Save download - - - Upload file - - - Use download URL - - - Gift cards - - - The new gift card has been added successfully. - - - Add a new gift card - - - back to gift card list - - - The gift card has been deleted successfully. - - - Edit gift card details - - - Initial value - - - The initial value of this gift card. - - - Creation date - - - The date/time the gift card was created/purchased. - - - Order - - - The gift card was purchased with this order. - - - Coupon code - - - Generate code - - - The gift card coupon code (used during checkout). - - - Gift card type - - - The gift card type. - - - Is gift card activated - - - Determines whether gift this card is activated and can be used. - - - Is recipient notified - - - Is recipient notified. - - - Notify recipient - - - Message - - - Enter message. - - - Order - - - The gift card was purchased with this order. - - - Recipient's Email - - - Enter recipient's email. - - - Recipient's Name - - - Enter recipient's name. - - - Remaining amount - - - The remaining amount of this gift card. - - - Sender's Email - - - Enter sender's email. - - - Sender's Name - - - Sender's name is required. - - - Usage history - - - Used - - - Order # - - - Used amount - - - Gift card info - - - Activated - - - All - - - Activated - - - Deactivated - - - Search by a specific gift card status e.g. Activated. - - - Gift card coupon code - - - A gift card coupon code. Leave empty to load all records. - - - Recipient name - - - Search by recipient name. Leave empty to load all records. - - - The gift card has been updated successfully. - - - Clear cache - - - Logout - - - Public store - - - Restart application - - - Restarting the application... - - - Help - - - Community forums - - - Premium support services - - - Help topics - - - Home - - - Search - - - NopCommerce News - - - Hide advertisements - - - Show advertisements - - - Orders - - - Address 1 - - - Address 2 - - - back to order details - - - City - - - Company - - - Country - - - Edit address - - - Email - - - Fax - - - Full name - - - Phone - - - State / province - - - Zip / postal code - - - back to order list - - - Billing info - - - Edit order details - - - Affiliate - - - The affiliate who the order is linked to. - - - Authorization transaction ID - - - Authorization transaction identifier received from your payment gateway. - - - Billing address - - - Billing address info. - - - Capture - - - Capture transaction ID - - - Capture transaction identifier received from your payment gateway. - - - Card CVV2 - - - The card security code of the card used in the transaction. - - - Card expiry month - - - The expiry month of the card used in the transaction. - - - Card expiry year - - - The expiry year of the card used in the transaction. - - - Card name - - - The name on the card used in the transaction. - - - Card number - - - The number of the card used in the transaction. - - - Card type - - - The type of card used in the transaction. - - - Created on - - - The date/time the order was placed/created. - - - Customer - - - The customer who placed this order. - - - Email - - - Email. - - - Customer IP address - - - Customer IP address from where order has been placed. - - - Order # - - - The unique number of this order. - - - Custom values - - - Custom values from the payment method. - - - Reward points base - - - Earned reward points base amount - - - Earned reward points base amount incl. Tax - - - Earned reward points base amount excl. Tax - - - excl tax: - - - incl tax: - - - non taxable: - - - Order shipping - - - Edit the total shipping cost for this order. - - - Order subtotal - - - Edit the subtotal of this order. - - - Order subtotal discount - - - Edit the subtotal discount of this order. - - - Order total - - - Edit the total cost of this order. - - - Order discount - - - Edit the total discount applied to this order. - - - Payment method additional fee - - - Edit the payment method additional fee for this order. - - - Order tax - - - Edit the total tax applied to this order. - - - Order tax rates - - - Edit the tax rates applied to this order ([rate]:[taxtotal] - separated by semicolon). - - - Edit credit card - - - Edit order totals - - - Amount - - - Edit Amount - - - Amount incl. Tax - - - Edit Amount incl. Tax - - - Order discount incl. Tax - - - Edit Order discount incl. Tax - - - Earned reward points base amount incl. Tax - - - Edit earned reward points base amount incl. Tax - - - Earned reward points base amount excl. Tax - - - Edit earned reward points base amount excl. Tax - - - Edit Invoice data. - - - Save Invoice Data. - - - Gift card - - - Applied gift card. - - - Mark as paid - - - Order GUID - - - Internal reference for order. - - - Order shipping - - - The total shipping cost for this order. - - - excl tax - - - incl tax - - - non taxable - - - Order status - - - Cancel order - - - This order is cancelled - - - Change status - - - This option is only for advanced users (not recommended to change manually). All appropriate actions (such as inventory adjustment, sending notification emails, reward points, gift card activation/deactivation, etc) should be done manually in this case. - - - The status of this order. - - - Order subtotal - - - The subtotal of this order. - - - Order subtotal discount - - - The subtotal discount of this order. - - - excl tax - - - incl tax - - - excl tax - - - incl tax - - - Order total - - - The total cost of this order (includes discounts, shipping and tax). - - - Order discount - - - The total discount applied to this order. Manage your discounts from Promotions : Discounts. - - - Partial refund - - - Amount to refund - - - Enter amount to refund. - - - Max amount is {0} {1} - - - Partial refund for order #{0} - - - Partial refund (Offline) - - - Payment method - - - The payment method used for this transaction. You can manage Payment Methods from Configuration : Payment : Payment Methods. - - - Payment method additional fee - - - The payment method additional fee for this order. - - - excl tax - - - incl tax - - - non taxable - - - Payment status - - - The status of the payment. - - - Pickup point address - - - Pickup point address info. - - - View address on Google Maps - - - Profit - - - Profit of this order. - - - Recurring payment - - - This is a recurring order. See the appropriate recurring payment record. - - - Redeemed reward points - - - Redeemed reward points. - - - Redeemed reward points amount - - - Redeemed reward points amount. - - - Redeemed reward points purchased - - - Redeemed purchased reward points. - - - Redeemed reward points amount purchased - - - Redeemed purchased reward points amount. - - - points - - - Refund - - - Refunded amount - - - The total refunded amount. - - - Refund (Offline) - - - Save credit card - - - Save order totals - - - Shipping address - - - Shipping address info. - - - View address on Google Maps - - - Shipping method - - - The customers chosen shipping method for this order. You can manage shipping methods from Configuration : Shipping : Shipping Methods. - - - Shipping status - - - The status of the shipping. - - - Store - - - A store name in which this order was placed. - - - Subscription transaction ID - - - Subscription transaction identifier received from your payment gateway. - - - Order tax - - - Total tax applied to this order. Manage your tax settings from Configuration : Tax. - - - Used discounts - - - A list of used discounts. - - - VAT number - - - Used VAT number (the European Union Value Added Tax). - - - Void - - - Void (Offline) - - - Amount - - - Total order base amount. - - - Amount incl. Tax - - - Total order amount incl. tax - - - Order discount incl. Tax - - - Order discount incl. Tax - - - Invoice No. - - - Invoice No. - - - Invoice Date - - - Invoice Date - - - Info - - - Billing country - - - Filter by order billing country. - - - Billing email address - - - Filter by customer billing email address. - - - Billing last name - - - Filter by customer billing last name. - - - End date - - - The end date for the search. - - - Go directly to order # - - - Go directly to order #. - - - Order notes - - - Search in order notes. Leave empty to load all orders. - - - Order statuses - - - Search by a specific order statuses e.g. Complete. - - - Payment method - - - Search by a specific payment method. - - - Payment statuses - - - Search by a specific payment statuses e.g. Paid. - - - Product - - - Search by a specific product. - - - Shipping statuses - - - Search by a specific shipping statuses e.g. Not yet shipped. - - - Start date - - - The start date for the search. - - - Store - - - Search by a specific store. - - - Vendor - - - Search by a specific vendor. You'll see orders with products from a specified vendor. - - - Warehouse - - - Load orders with products from a specified warehouse. - - - This order item has an associated gift card record. Please delete it first - - - Order notes - - - Add order note - - - Add order note - - - Created on - - - Display to customer - - - A value indicating whether to display this order note to a customer. - - - Attached file - - - (check to upload file) - - - Upload a file attached to this order note. - - - Download - - - No file attached - - - Note - - - Enter this order note message. - - - Invoice (PDF) - - - No orders selected - - - Print PDF invoices - - - Print PDF invoices (all found) - - - Print PDF invoices (selected) - - - Products - - - Add product - - - back to product list - - - back to order details - - - Name - - - Quantity - - - Enter quantity. - - - SKU - - - Total (excl tax) - - - Enter total (excl tax). - - - Total (incl tax) - - - Enter total (incl tax). - - - Add a new product to order #{0} - - - Add product '{0}' to order #{1} - - - Price (excl tax) - - - Enter product price (excl tax). - - - Price (incl tax) - - - Enter product price (incl tax). - - - Do not to forget to update order totals after adding this product. - - - Discount - - - Downloadable product - - - Activate - - - Deactivate - - - Number of downloads: {0} - - - Reset - - - Click to reset download count - - - Excl tax: - - - Incl tax: - - - Gift card(s) - - - License file (optional): - - - Download license file - - - Upload license file - - - Picture - - - Price - - - Product name - - - Quantity - - - [Auto-ship, Every {0} {1}] - - - Return request(s) - - - SKU - - - Total - - - Vendor - - - Profit - - - Shipping - - - Summary - - - Tax - - - Total - - - Shipments - - - The new shipment has been added successfully. - - - Add shipment - - - Add a new shipment to order #{0} - - - Admin comment - - - Admin comment. For internal use. - - - Set admin comment - - - back to order details - - - Order # - - - The order associated to this shipment. - - - The shipment has been deleted successfully. - - - Date delivered - - - Set as delivered - - - Date and time should entered in Coordinated Universal Time (UTC) - - - The date this shipment was delivered. - - - Not yet - - - Set as delivered (selected) - - - Shipment # - - - Shipments - - - City - - - Search by a specific city. - - - Country - - - Search by a specific country. - - - End date - - - The end date (shipment creation date) for the search. - - - Load not shipped - - - Load only not shipped shipments. - - - Start date - - - The start date (shipment creation date) for the search. - - - State / province - - - Search by a specific state. - - - Tracking number - - - Search by a specific tracking number. - - - Warehouse - - - Load shipments with products from a specified warehouse. - - - No products selected - - - No shipments selected - - - Print packaging slip - - - Print packaging slips - - - Print packaging slips (all found) - - - Print packaging slips (selected) - - - Products shipped - - - Item dimensions - - - Item weight - - - Product - - - Qty ordered - - - Qty shipped - - - Qty to ship - - - this product should be shipped separately! - - - Warning: - - - SKU - - - Warehouse - - - {0} ({1} qty in stock, {2} qty reserved, {3} qty planned) - - - No warehouses available. You cannot ship this product. - - - [Reserved - Planned] quantity value of some products are less than specified quantity to be shipped. Are you sure? - - - Shipment status events - - - Country - - - Date - - - Event - - - Location - - - Date shipped - - - Set as shipped - - - Date and time should entered in Coordinated Universal Time (UTC) - - - The date this shipment was shipped. - - - Not yet - - - Set as shipped (selected) - - - Total weight - - - Shipment total weight. - - - Tracking number - - - Set tracking number - - - View tracking info - - - Set a tracking number of the current shipment. - - - View shipment details - - - Shipping info - - - Shipping not required - - - All - - - {0} - {1} of {2} items - - - No items to display - - - Go to the first page - - - items per page - - - Go to the last page - - - More pages - - - Go to the next page - - - of {0} - - - Page - - - Go to the previous page - - - Refresh - - - nopCommerce administration - - - Remove picture - - - Plugins - - - The plugin has been updated successfully. - - - Promotions - - - Campaigns - - - The new campaign has been added successfully. - - - Add a new campaign - - - back to campaign list - - - The campaign has been deleted successfully. - - - Edit campaign - - - Allowed message tokens - - - This is a list of the message tokens you can use in your campaign emails. - - - Body - - - The body of your campaign email. - - - Body is required - - - Created on - - - Limited to customer role - - - Choose a customer role which subscribers will get this email. - - - Planned date of sending - - - Enter a specific date and time to send the campaign. Leave empty to send it immediately. - - - Email account - - - The email account that will be used to send this campaign. - - - Name - - - The name of this campaign. - - - Campaign name is required - - - Limited to store - - - Choose a store which subscribers will get this email. - - - Subject - - - The subject of your campaign email. - - - Subject is required - - - Send test email to - - - The email address to which you want to send your test email. - - - Store - - - Search by a specific store. - - - {0} emails have been successfully queued. - - - Send mass email - - - Send test email - - - Email has been successfully sent. - - - The campaign has been updated successfully. - - - Make sure you've tested the campaign before sending it out to multiple customers. Save your campaign first by clicking "Save" button. - - - Discounts - - - The new discount has been added successfully. - - - Add a new discount - - - Applied to categories - - - You need to save the discount before you can add categories for this discount page. - - - Category - - - Add a new category - - - Applied to manufacturers - - - You need to save the discount before you can add manufacturers for this discount page. - - - Manufacturer - - - Add a new manufacturer - - - Applied to products - - - You need to save the discount before you can add products for this discount page. - - - Product - - - Add a new product - - - back to discount list - - - The discount has been deleted successfully. - - - Edit discount details - - - Assigned to categories - - - A list of categories to which the discount is to be applied. You can assign this discount on a category details page. - - - No categories selected - - - Apply to subcategories - - - Check to apply discount to subcategories of the selected parent. But please note that it can affect performance if you have a lot of nested subcategories. - - - Coupon code - - - The discount coupon code. - - - Discount - - - Discount amount - - - The discount amount to apply to the order/SKUs. - - - Discount limitation - - - Choose the limitation of discount. - - - Discount percentage - - - The percentage discount to apply to order/SKUs. - - - Discount type - - - The type of discount. - - - End date - - - The end of the discount period in Coordinated Universal Time (UTC). - - - Cumulative with other discounts - - - If checked, this discount can be used with other ones simultaneously. Please note that this feature works only for discounts with the same discount type. Right now discounts with distinct types are already cumulative. - - - N times - - - Enter the number of times that the discount is limited to. - - - times - - - Maximum discount amount - - - Maximum allowed discount amount. Leave empty to allow any discount amount. If you're using "Assigned to products" discount type, then it's applied to each product separately. - - - Maximum discounted quantity - - - Maximum product quantity which could be discounted. For example, you can have two products (the same) in the cart but only one of them will be discounted. It can be used for scenarios like "buy 2 get 1 free". Leave empty if any quantity could be discounted. - - - Name - - - The name of the discount. - - - Please provide a name. - - - Requires coupon code - - - If checked, a customer must supply a valid coupon code for the discount to be applied. - - - Start date - - - The start of the discount period in Coordinated Universal Time (UTC). - - - Times used - - - Use percentage - - - Determines whether to apply a percentage discount to the order/SKUs. If not enabled, a set value is discounted. - - - Usage history - - - Used - - - Order # - - - Order total - - - Discount info - - - Coupon code - - - Search by discount coupon code. - - - Discount name - - - Search by discount name. - - - Discount type - - - Search by discount type. - - - Requirements - - - Add a new discount requirement - - - Default requirement group - - - Requirement group is a useful feature for creating discount requirement templates. You can create a requirement group just once and then use it every time you want this limitation to be applied. You can include one requirement group into another one if needed.]]> - - - Discount requirement type - - - Add requirement group - - - You can choose one of the following requirement types, or add a requirement group to use several requirement types simultaneously. - - - Select requirement type - - - Failed to save requirement - - - The group is empty - - - Group name - - - Specify name of the requirement group (e.g. "Permitted customer roles"). - - - Interaction type in this group is - - - Add to group - - - Choose the group you want the requirement group you’re creating to be assigned to - - - Group - - - Requirement - - - Remove requirement - - - Remove group - - - You need to save the discount before you can add requirements for this discount page. - - - Discount requirement saved - - - The discount has been updated successfully. - - - In order to use this functionality you have to disable the following setting: Configuration > Catalog settings > Ignore discounts (sitewide). - - - Newsletter subscribers - - - Active - - - Subscribed on - - - Email - - - Email is required. - - - Store - - - {0} emails have been successfully imported - - - Customer roles - - - Search by a specific customer role. - - - Active - - - All - - - Active - - - Search by a specific status e.g. Active. - - - Not active - - - Email - - - Search by a specific email. - - - Store - - - Search by a specific store. - - - Start date - - - The start date for the search. - - - End date - - - The end date for the search. - - - Recurring payments - - - back to recurring payment list - - - The payment has been cancelled successfully. - - - The payment has been deleted successfully. - - - Recurring payments are used for automatic renewal of consumable merchandise or subscription services. - - - Edit recurring payment details - - - Customer - - - Customer. - - - Cycle length - - - Enter cycle length. - - - Cycle period - - - Select cycle period. - - - Cycles remaining - - - Number of cycles remaining. - - - ID - - - Initial order - - - Initial order of this recurring payment. - - - Is active - - - Determines whether this recurring payment is active. - - - Next payment date - - - Payment type - - - Indicates the payment type (Manual or Automatic). - - - Start date - - - Start date of this recurring payment. - - - Total cycles - - - Enter total cycles. - - - History - - - Cancel recurring payment - - - Created on - - - Created order - - - Last payment failed - - - Next payment date - - - Order status - - - Payment status - - - Process next payment (create a new order) - - - Shipping status - - - Recurring payment info - - - The payment has been processed successfully. - - - The payment has been updated successfully. - - - Return requests - - - back to return request list - - - The return request has been deleted successfully. - - - The Return Request feature (RMA) enables customers to send products back to you. Here you can find all submitted return requests. - - - Edit return request details - - - Date - - - Date when the return request has been submitted. - - - Customer - - - Customer information. - - - Customer comments - - - Entered customer comments. - - - ID - - - Return request identifier. - - - Order # - - - The unique number of the order. - - - Product - - - Product information. - - - Quantity - - - Entered quantity. - - - Reason for return - - - Chosen reason for return. - - - Reason for return is required - - - Requested action - - - Chosen requested action. - - - Requested action is required - - - Staff notes - - - Entered staff comments. - - - Return request status - - - Choose request status. - - - Uploaded file - - - Download - - - File uploaded by customer - - - The customer has been notified successfully. - - - Notify customer about status change - - - Order item is deleted - - - - ID - - - Search by a specific return request identifier. - - - End date - - - The end date for the search. - - - Return status - - - All - - - Search by a specific return request status e.g. Received. - - - Start date - - - The start date for the search. - - - The return request has been updated successfully. - - - Sales - - - Order totals - - - Order Status - - - All time - - - This Month - - - This Week - - - This Year - - - Today - - - Bestsellers - - - Billing country - - - Filter by order billing country. - - - Bestsellers by amount - - - Bestsellers by quantity - - - Category - - - Search in a specific category. - - - End date - - - The end date for the search. - - - Name - - - Total amount (excl tax) - - - Total quantity - - - Manufacturer - - - Search in a specific manufacturer. - - - Order status - - - Search by a specific order status e.g. Complete. - - - Payment status - - - Search by a specific payment status e.g. Paid. - - - Run report - - - Start date - - - The start date for the search. - - - Store - - - Filter report by orders placed in a specific store. - - - Vendor - - - Search by a specific vendor. - - - Country report - - - End date - - - The end date for the search. - - - Country - - - Order total - - - Number of orders - - - Order status - - - Search by a specific order status e.g. Complete. - - - Payment status - - - Search by a specific payment status e.g. Paid. - - - Run report - - - Start date - - - The start date for the search. - - - Incomplete orders - - - Count - - - Item - - - Total - - - Total incomplete orders (pending order status) - - - Total not yet shipped orders - - - Total unpaid orders (pending payment status) - - - view all - - - Products never purchased - - - End date - - - The end date for the search. - - - Name - - - Run report - - - Category - - - Load products only from a specific category. - - - Manufacturer - - - Load products only from a specific manufacturer. - - - Store - - - Load products only from a specific store (available in this store). - - - Vendor - - - Load products only by a specific vendor (owned by this vendor). - - - Start date - - - The start date for the search. - - - Orders - - - Month - - - Week - - - Year - - - Popular search keywords - - - Count - - - Keyword - - - The stock quantity has been increased by canceling the order #{0} - - - The stock quantity of combination has been edited - - - The stock quantity has been edited by copying the product #{0} - - - The stock quantity has been increased by deleting the order #{0} - - - The stock quantity has been increased by deleting an order item from the order #{0} - - - The stock quantity has been increased by deleting a shipment from the order #{0} - - - The stock quantity has been edited - - - Multiple warehouses. - - - The stock quantity has been changed by editing the order #{0} - - - The stock quantity has been changed by importing product - - - Products have been moved {0} {1} by importing product - - - Products have been moved {0} {1} - - - to the {0} - - - from the {0} - - - The stock quantity has been reduced by placing the order #{0} - - - The stock quantity has been reduced when an order item of the order #{0} was shipped - - - System - - - Log - - - back to system log - - - The log has been cleared successfully. - - - Clear log - - - The log entry has been deleted successfully. - - - Delete selected - - - Created on - - - Date/Time the log entry was created. - - - Customer - - - Name of the customer who caused the exception. - - - Full message - - - The details for the log entry. - - - IP address - - - IP address of the machine that caused the exception. - - - Log level - - - The level of log entry. - - - Page URL - - - Originating page of exception. - - - Referrer URL - - - The referrer URL. - - - Short message - - - The log entry message. - - - Created from - - - The creation from date for the search. - - - Created to - - - The creation to date for the search. - - - Log level - - - Select a log level. - - - Message - - - Message. - - - View log entry details - - - Maintenance - - - The backup created - - - Backup file "{0}" deleted - - - Backup now - - - Database backups - - - Database is restored - - - Delete - - - Download - - - File Name - - - File Size - - - Processing database backup... - - - Restore - - - Deleting abandoned shopping carts - - - Created before - - - Delete shopping cart items created before the specified date. - - - {0} items were deleted - - - Deleting old exported files - - - End date - - - The end date for the search. - - - Start date - - - The start date for the search. - - - {0} files were deleted - - - Deleting guest customers - - - End date - - - The end date for the search. - - - Only without shopping cart - - - A value indicating whether we need to find customers without shopping carts/wishlists. If unchecked, then all customers will be found. - - - Start date - - - The start date for the search. - - - {0} customers were deleted - - - Message queue - - - back to message queue - - - Delete all - - - The queued email has been deleted successfully. - - - All queued emails have been deleted successfully. - - - Delete selected - - - Edit message queue item - - - Attached static file - - - The attached static file that will be sent in this email. - - - Attached file path - - - The file path to the attachment. - - - Bcc - - - Bcc address. - - - Body - - - Message body. - - - Cc - - - Cc address. - - - Created on - - - Date/Time message added to queue. - - - Planned date of sending - - - The specific send date and time. - - - Email account - - - The email account that will be used to send this email. - - - From - - - From address. - - - From is required. - - - From name - - - From name. - - - ID - - - Message Priority - - - The priority of the message. - - - Enter priority. - - - ReplyTo - - - ReplyTo address (optional). - - - ReplyTo name - - - ReplyTo name (optional). - - - Send immediately - - - Send message immediately. - - - Sent on - - - The date/time message was sent. - - - Not sent yet - - - Sent attempts - - - The number of times to attempt to send this message. - - - The value must be from 0 to 999999. - - - Enter send tries. - - - Subject - - - Message subject. - - - To - - - To address. - - - To is required. - - - To name - - - To name. - - - End date - - - The end date for the search. - - - From address - - - From address. - - - Go directly to email # - - - Go directly to email #. - - - Load not sent emails only - - - Only load emails into queue that have not yet been sent. - - - Maximum send attempts - - - The maximum number of attempts to send a message. - - - Start date - - - The start date for the search. - - - To address - - - To address. - - - Requeue - - - The queued email has been requeued successfully. - - - The queued email has been updated successfully. - - - Schedule tasks - - - Task period should not exceed 24 days. - - - Enabled - - - Last end date - - - Last start date - - - Last success date - - - Name - - - Name is required - - - Do not forget to restart the application once a task has been modified. - - - Run now - - - Schedule task was run - - - Running the schedule task - - - Seconds (run period) - - - Seconds should be positive - - - Stop on error - - - Search engine friendly page names - - - Delete selected - - - Edit page - - - Entity ID - - - Entity name - - - ID - - - Is active - - - Language - - - Standard - - - Name - - - A name to find. - - - System information - - - ASP.NET info - - - ASP.NET info. - - - Current user time - - - Current user time (based on specified datetime and timezone settings). - - - HTTP_HOST - - - HTTP_HOST is used when you have run a multi-store solution to determine the current store. - - - Is full trust level - - - Is full trust level. - - - Loaded assemblies - - - A list of loaded assemblies. - - - nopCommerce version - - - nopCommerce version. - - - Operating system - - - Operating system. - - - Server local time - - - Server local time. - - - Server time zone - - - Server time zone. - - - Server variables - - - A list of server variables. - - - Coordinated Universal Time (UTC) - - - Coordinated Universal Time (UTC). - - - Templates - - - Category templates - - - Display order - - - Name - - - Name is required - - - View path - - - View path is required - - - Manufacturer templates - - - Display order - - - Name - - - Name is required - - - View path - - - View path is required - - - Product templates - - - Display order - - - Ignored product type IDs (advanced) - - - Name - - - Name is required - - - View path - - - View path is required - - - Topic templates - - - Display order - - - Name - - - Name is required - - - View path - - - View path is required - - - Warnings - - - Default dimension is not set - - - Default dimension. The ratio should be set to 1. - - - Default dimension is set - - - Default weight is not set - - - Default weight. The ratio should be set to 1. - - - Default weight is set - - - All directory permissions are OK - - - The '{0}' account is not granted with Modify permission on folder '{1}'. Please configure these permissions. - - - Primary exchange rate currency is not set - - - Primary exchange rate currency. The rate should be set to 1. - - - Primary exchange rate currency is set - - - All file permissions are OK - - - The '{0}' account is not granted with Modify permission on file '{1}'. Please configure these permissions. - - - '{0}' plugin is incompatible with your nopCommerce version. Delete it or update to the latest version. - - - A custom machine key is not specified (web.config file) - - - A custom machine key is specified (web.config file) - - - You don't have active payment methods - - - Payment methods are OK - - - Performance. You use only one store. Recommended to ignore store limitations (catalog settings) - - - In order to use this functionality you have to disable the following setting: Catalog settings > Ignore "limit per store" rules. - - - Performance. Recommended to ignore ACL rules if you don't use them (catalog settings) - - - In order to use this functionality you have to disable the following setting: Catalog settings > Ignore ACL rules. - - - Primary store currency is not set - - - Primary store currency is set - - - No shipping rate computation methods enabled - - - Only one offline shipping rate computation method is recommended to use - - - Specified store URL matches this store URL - - - Specified store URL ({0}) doesn't match this store URL ({1}) - - - Entered page name already exists, so it will be replaced by '{0}' - - - Vendors - - - The new vendor has been added successfully. - - - Add a new vendor - - - Address (optional) - - - back to vendor list - - - The vendor has been deleted successfully. - - - Edit vendor details - - - Active - - - A value indicating whether the vendor is active. - - - Admin comment - - - Admin comment. For internal use. - - - Allow customers to select page size - - - Whether customers are allowed to select the page size from a predefined list of options. - - - Customers - - - A list of customer accounts which could be used to manage products and orders of this vendor (have access to the vendor portal). You can associate customers to a vendor on a customer details page. If you don't want the vendor to have access to the vendor portal, then do not associate any customer account with it. - - - No customer account associated to this vendor. - - - Description - - - The description of the vendor. - - - Display order - - - Set the vendor's display order. 1 represents the top of the list. - - - Email - - - Enter email. - - - Email is required. - - - Meta description - - - Meta description to be added to vendor page header. - - - Meta keywords - - - Meta keywords to be added to vendor page header. - - - Meta title - - - Override the page title. The default is the name of the vendor. - - - Name - - - The name of the vendor. - - - Please provide a name. - - - Page size - - - Set the page size for products in this vendor e.g. '4' products per page. - - - Page size should be positive. - - - Page Size options (comma separated) - - - Comma separated list of page size options (e.g. 10, 5, 15, 20). First option is the default page size if none are selected. - - - Page Size options should have unique items. - - - Picture - - - The vendor picture. - - - Search engine friendly page name - - - Set a search engine friendly page name e.g. 'the-best-vendor' to make your page URL 'http://www.yourStore.com/the-best-vendor'. Leave empty to generate it automatically based on the name of the vendor. - - - Vendor Info - - - Vendor name - - - A vendor name. - - - The vendor has been updated successfully. - - - Vendor notes - - - Add vendor note - - - Add vendor note - - - Created on - - - Note - - - Enter this vendor note message. - - - You're already subscribed for this product back in stock notification - - - You cannot subscribe. Maximum number of allowed subscriptions is {0} - - - Subscriptions are not allowed for this product - - - Notify me - - - Notify me when available - - - Only registered customers can use this feature - - - Receive an email when this arrives in stock - - - You'll receive a onetime e-mail when this product is available for ordering again. We will not send you any other e-mails or add you to our newsletter; you will only be e-mailed about this product! - - - Unsubscribe - - - Bestsellers - - - Blog - - - Blog archive - - - Comments - - - Comment - - - Enter comment - - - Created on - - - Leave your comment - - - Only registered users can leave comments. - - - Blog comment is successfully added. You will see it after approving by a store administrator. - - - New comment - - - Blog comment is successfully added. - - - Comments ({0:d}) - - - Blog posts of '{0}' '{1}' - - - details - - - RSS - - - Click here to be informed automatically when we add new items to our site. - - - Popular blog tags - - - Blog posts tagged with '{0}' - - - Tags - - - Sort by - - - Display - - - per page - - - View as - - - Grid - - - List - - - Categories - - - Home - - - ({0}) - - - Checkout - - - Billing address - - - Bill to this address - - - Checkout - - - Confirm - - - Confirm order - - - Confirm your order - - - Enter billing address - - - Enter shipping address - - - Please wait several seconds before placing a new order (already placed another order several seconds ago). - - - Minimum order sub-total amount is {0} - - - Minimum order total amount is {0} - - - New Address - - - Next - - - No payment methods available - - - Order number - - - Order summary - - - Or enter new address - - - Payment error: {0} - - - Payment information - - - Payment method - - - Pickup - - - Pick up your items at the store - - - Pickup at {0} - - - Pickup points could not be loaded - - - Select pickup point - - - Click here for order details. - - - Address - - - Cart - - - Complete - - - Confirm - - - Payment - - - Shipping - - - Select billing address - - - Select a billing address from your address book or enter a new address. - - - Select payment method - - - {0} ({1}) - - - Select shipping address - - - Select a shipping address from your address book or enter a new address. - - - Select shipping method - - - {0} ({1}) - - - Shipping address - - - Shipping is not allowed - - - Shipping method - - - Please note that your order will be shipped from multiple locations - - - Shipping options could not be loaded - - - Ship to the same address - - - Ship to this address - - - Submitting order information... - - - Terms of service - - - I agree with the terms of service and I adhere to them unconditionally - - - Please accept the terms of service before the next step. - - - (read) - - - Thank you - - - Continue - - - Use my reward points, {0} reward points ({1}) available - - - Your order has been successfully processed! - - - Add new - - - All - - - Are you sure? - - - Back - - - Cancel - - - Close - - - Config - - - Confirm - - - Continue - - - Day - - - Delete - - - Are you sure you want to delete this item? - - - Are you sure you want to delete "{0}"? - - - Edit - - - Error - - - Cancel - - - Delete - - - Download - - - Drop files here to upload - - - Processing dropped files... - - - Remove - - - Retry - - - Upload a file - - - Hide - - - Home - - - Loading next step... - - - Manage this page - - - Month - - - No - - - No, cancel - - - Notification - - - OK - - - Remove - - - Save - - - Search - - - Show - - - Upload - - - View - - - Wait... - - - Warning - - - The characters didn't match the picture. Please try again. - - - The reCAPTCHA response is invalid or malformed. Please try again. - - - Wrong email - - - Year - - - Yes - - - Contact us - - - Submit - - - Your email - - - Enter your email address. - - - Enter email - - - Enquiry - - - Enter your enquiry. - - - Enter enquiry - - - Your name - - - Enter your name. - - - Enter your name - - - Subject - - - Enter subject. - - - Please enter subject - - - Your enquiry has been successfully sent to the store owner. - - - Contact vendor - - - Submit - - - Your email - - - Enter your email address. - - - Enter email - - - Enquiry - - - Enter your enquiry. - - - Enter enquiry - - - Your name - - - Enter your name. - - - Enter your name - - - Subject - - - Enter subject. - - - Please enter subject - - - Your enquiry has been successfully sent to the vendor. - - - Copyright &copy; {0} {1}. All rights reserved. - - - Currencies - - - Guest - - - Date - - - Download - - - n/a - - - Download license - - - Order # - - - Product - - - I agree - - - There are no downloadable products - - - You have reached maximum number of downloads {0} - - - User agreement - - - Checkboxes - - - Color squares - - - Date picker - - - Drop-down list - - - File upload - - - Multiline textbox - - - Radio button list - - - Read-only checkboxes - - - Textbox - - - Associated to product - - - Simple - - - Allow qty below 0 - - - Allow qty below 0 and notify customer - - - No backorders - - - Manually - - - When order is paid - - - Physical - - - Virtual - - - Disable buy button - - - Nothing - - - Unpublish - - - Don't track inventory - - - Track inventory - - - Track inventory by product attributes - - - Created on - - - Name: A to Z - - - Name: Z to A - - - Position - - - Price: Low to High - - - Price: High to Low - - - Grouped (product with variants) - - - Simple - - - Days - - - Months - - - Weeks - - - Years - - - Days - - - Weeks - - - Months - - - Years - - - Option - - - Custom text - - - Custom HTML text - - - Hyperlink - - - Using CONTAINS and AND with prefix_term - - - Exact match (using CONTAINS with prefix_term) - - - Using CONTAINS and OR with prefix_term - - - Show emails - - - Show first name - - - Show full names - - - Show usernames - - - Clear - - - Encrypted - - - Hashed - - - Days - - - Hours - - - Default rounding - - - Rounding up with 0.05 intervals (0.06 round to 0.10) - - - Rounding down with 0.05 intervals (0.06 round to 0.05) - - - Rounding up with 0.10 intervals (1.05 round to 1.10) - - - Rounding down with 0.10 intervals (1.05 round to 1.00) - - - Rounding with 0.50 intervals - - - Rounding with 1.00 intervals (1.01-1.49 round to 1.00, 1.50-1.99 round to 2.00) - - - Rounding up with 1.00 intervals (1.01–1.99 round to 2.00) - - - N times only - - - N times per customer - - - Unlimited - - - Assigned to categories - - - Assigned to manufacturers - - - Assigned to order subtotal - - - Assigned to order total - - - Assigned to shipping - - - Assigned to products - - - AND - - - OR - - - BBCode editor - - - Html editor - - - Simple textbox - - - Topic titles and post text - - - Post text only - - - Topic titles only - - - Announcement - - - Normal - - - Sticky - - - Debug - - - Error - - - Fatal - - - Information - - - Warning - - - Days - - - Hours - - - Low - - - High - - - Cancelled - - - Complete - - - Pending - - - Processing - - - Cancelled - - - Item(s) refunded - - - Item(s) repaired - - - Pending - - - Received - - - Request rejected - - - Return authorized - - - Authorized - - - Paid - - - Partially refunded - - - Pending - - - Refunded - - - Voided - - - Clear - - - Encrypted - - - Hashed - - - A customer should be approved by administrator - - - Registration is disabled - - - Email validation is required after registration - - - Standard account creation - - - Page name comes after store name - - - Store name comes after page name - - - Doesn't matter - - - Pages should not have WWW prefix - - - Pages should have WWW prefix - - - Delivered - - - Not yet shipped - - - Partially shipped - - - Shipped - - - Shipping not required - - - Billing address - - - Default address - - - Shipping address - - - Excluding tax - - - Including tax - - - Empty - - - Invalid - - - Unknown - - - All - - - Installed - - - Not installed - - - Valid - - - Automatic - - - Manual - - - Not supported - - - Version 1.0 - - - Version 2.0 - - - You cannot browse this site until you accept cookies - - - On 26 May 2011, the rules about cookies on websites changed. This site uses cookies. One or more of the cookies we use is essential for parts of this website to operate and has already been set. You may delete and block all cookies from this site, but parts of the site will not work. To find out more about cookies used on this website and how to delete cookies, see our privacy policy - - - Cookies help us deliver our services. By using our services, you agree to our use of cookies. - - - Learn more - - - Your cookie settings - - - Filter your results - - - Filter by price - - - Over {0} - - - Remove Filter - - - Under {0} - - - Filter by attributes - - - Currently shopping by: - - - Remove Filter - - - or - - - Customer service - - - Follow us - - - Facebook - - - Google+ - - - RSS - - - Twitter - - - YouTube - - - Information - - - My account - - - shipping]]> - - - shipping]]> - - - Active discussions - - - View all - - - Topics with active discussions. - - - {0} - Active Discussions - - - Click here to be informed automatically of active discussion topics - - - Advanced search - - - Announcement - - - Author - - - Forum Group - - - Forum Home Page - - - Forum - - - Home Page - - - Topic - - - By - - - Cancel - - - Delete Post - - - Delete Topic - - - Edit Post - - - Edit Topic - - - Forum - - - Forum's topics with newest posts. - - - {0} - Forum: {1} - - - Forum Name - - - Click here to be informed automatically of new posts and topics in the forum - - - Forums - - - Home - - - In - - - Joined - - - Latest Post - - - Location - - - Moderator - - - Move Topic - - - New Post - - - New Topic - - - No Posts - - - Normal - - - Notify me via email when someone posts in this topic - - - Active discussions - - - Forums - - - Move Topic - - - Edit Post - - - New Post - - - Search Forums - - - Edit Topic - - - New Topic - - - Posted - - - Link to this post - - - Posts - - - Priority - - - PM - - - Quote - - - Replies - - - {0} Replies - - - Reply - - - RSS - - - Search Forums - - - Advanced search - - - Limit results to previous: - - - 1 day - - - 1 month - - - 1 year - - - 2 weeks - - - 3 months - - - 6 months - - - 7 days - - - All results - - - Search in forum: - - - All forums - - - Search keyword: - - - Search within: - - - Topic titles and post text - - - Post text only - - - Topic titles only - - - Search - - - Search forums - - - No posts were found that matched your criteria. - - - Search term minimum length is {0} characters - - - Select the forum you want to move the post to - - - Status - - - Sticky - - - Submit - - - Text cannot be empty - - - Topics - - - {0} Topics - - - [Go to page: {0}] - - - Topic subject cannot be empty - - - Topic Title - - - Total Posts - - - Unwatch Forum - - - Unwatch Topic - - - Votes - - - You already voted for this post - - - You need to log in to vote for post - - - A maximum of {0} votes can be cast per user per day - - - You cannot vote for your own post - - - Views - - - Watch Forum - - - Watch Topic - - - For: {0} - - - ]]> - - - From: {0} - - - ]]> - - - Home page - - - Featured products - - - Languages - - - Manufacturers - - - Manufacturer List - - - View all - - - Picture for category {0} - - - Show products in category {0} - - - Loading... - - - Close (Esc) - - - Previous (Left arrow key) - - - Next (Right arrow key) - - - %curr% of %total% - - - Picture for manufacturer {0} - - - Show products manufactured by {0} - - - Picture of {0} - - - Picture of {0} - - - Show details for {0} - - - Picture of {0} - - - Picture for vendor {0} - - - Show products of vendor {0} - - - Menu - - - Gift card ({0}): - - - Order Total: - - - Payment method additional fee: - - - Product(s) - - - Download - - - Download license - - - Name - - - Price - - - Quantity - - - SKU: {0} - - - Total - - - {0} reward points: - - - {0} purchased reward points: - - - Shipping: - - - Sub-Total: - - - Discount: - - - Tax: - - - Tax {0}%: - - - Discount: - - - Included tax: - - - Amount: - - - Amount incl. tax: - - - News - - - News archive - - - Comments - - - Comment - - - Enter comment text - - - Title - - - Max length of comment title is {0} chars - - - Enter comment title - - - Created on - - - Leave your comment - - - Only registered users can leave comments. - - - News comment is successfully added. You will see it after approving by a store administrator. - - - New comment - - - News comment is successfully added. - - - details - - - RSS - - - Click here to be informed automatically when we add new items to our site. - - - View News Archive - - - Enter your email here... - - - Enter valid email - - - Send - - - Subscribe - - - Unsubscribe - - - Your subscription has been activated. - - - Your subscription already has been deactivated. - - - Your subscription has been deactivated. - - - Sign up for our newsletter - - - Thank you for signing up! A verification email has been sent. We appreciate your interest. - - - A verification email has been sent. Thank you! - - - Newsletter - - - Billing Address - - - Email - - - Fax - - - PDF Invoice - - - Gift card ({0}) - - - Note(s) - - - Created On - - - Download attached file - - - Note - - - Order # - - - Order Date - - - Order information - - - Order Status - - - Order Total - - - Payment - - - Payment Method - - - Payment Status - - - Payment method additional fee - - - Phone - - - Pickup point address - - - Print - - - Product(s) - - - Name - - - Price - - - Quantity - - - SKU - - - Total - - - Start date: {0}. End date: {1}. - - - Re-order - - - Retry payment - - - This order is not yet paid for. To pay now, click the "Retry payment" button. - - - Return Item(s) - - - {0} used reward points - - - {0} used purchased reward points - - - Shipments - - - Date delivered - - - Not yet - - - Email - - - Fax - - - Shipment # - - - Shipment #{0} - - - Order # - - - Phone - - - Pickup point address - - - Shipped product(s) - - - Name - - - Qty shipped - - - SKU - - - Date shipped - - - Not yet - - - Shipping Address - - - Shipping Method - - - Tracking number - - - View details - - - Shipment status events - - - Country - - - Date - - - Event - - - Location - - - Shipping - - - Shipping Address - - - Shipping Method - - - Shipping Status - - - Sub-Total - - - Discount - - - Tax - - - Tax {0}% - - - shipping]]> - - - shipping]]> - - - Discount - - - VAT number - - - Included Tax - - - Amount - - - Amount incl. Tax - - - Order discount incl. Tax - - - Earned reward points base amount incl. Tax - - - Earned reward points base amount excl. Tax - - - Invoice No. - - - Invoice Date - - - Tax % - - - Amount - - - Amount incl. Tax - - - Discount - - - Discount incl. tax - - - Base Amount - - - Tax Amount - - - Page {0} of {1} ({2} total) - - - First - - - First Page - - - Last - - - Last Page - - - Next - - - Next Page - - - Page {0} - - - Previous - - - Previous Page - - - Account - - - Account Activation - - - All product tags - - - Back in stock subscriptions - - - Blog - - - Checkout - - - Compare Products - - - Contact Us - - - Contact Vendor - {0} - - - My product reviews - - - Email validation - - - Forum Subscriptions - - - Login - - - Manufacturers - - - New Products - - - News Archive - - - Order Details - - - Page not found - - - Password Recovery - - - Private Messages - - - Email A Friend - - - Product Reviews - - - Products tagged with '{0}' - - - Profile - - - Recently Viewed Products - - - Register - - - Return Item(s) - - - Search - - - Post a private message - - - Shipment details - - - Shopping Cart - - - Sitemap - - - Store closed - - - Vendor info - - - Vendors - - - Apply for vendor account - - - View the private message - - - Wishlist - - - Email wishlist to a friend - - - Card code - - - Enter card code - - - Wrong card code - - - Cardholder name - - - Enter cardholder name - - - Card number - - - Enter card number - - - Wrong card number - - - Expiration date - - - Expire month is required - - - Expire year is required - - - Select credit card - - - Address: {0} - - - Address 2: {0} - - - Billing Information: - - - Company: {0} - - - Created on - - - Discount: - - - Fax: {0} - - - Gift card ({0}): - - - Name: {0} - - - Note - - - Order# {0} - - - ORDER - - - VatID: {0} - - - Date: {0} - - - Order notes: - - - Created on - - - Note - - - Order total: - - - Payment method: {0} - - - Payment Method Additional Fee: - - - Phone: {0} - - - Pickup point: - - - Product(s) - - - Name - - - Price - - - Qty - - - Total - - - {0} reward points: - - - {0} purchased reward points: - - - Shipping: - - - Shipping Information: - - - Shipping method: {0} - - - SKU - - - Sub-total: - - - Tax: - - - Tax {0}%: - - - VAT number: {0} - - - Tax % - - - Invoice - - - Invoice Date - - - Amount - - - Amount incl. tax - - - Discount - - - Discount incl. tax - - - VAT Amount - - - Amount to pay - - - Continued on next page ... - - - Additional items - - - Invoice discount - - - Currency - - - Page - - - Base Ammount - - - Total Base - - - Address: {0} - - - Address 2: {0} - - - Company: {0} - - - Name: {0} - - - Order #{0} - - - Phone: {0} - - - Product Name - - - Quantity - - - Shipment #{0} - - - Shipping method: {0} - - - SKU - - - Price - - - SKU - - - Stock quantity - - - Weight - - - Access admin area - - - Plugins. Access Web Service - - - Admin area. Allow Customer Impersonation - - - Public store. Display Prices - - - Public store. Enable shopping cart - - - Public store. Enable wishlist - - - Admin area. HTML Editor. Manage pictures - - - Admin area. Manage ACL - - - Admin area. Manage Activity Log - - - Admin area. Manage Affiliates - - - Admin area. Manage Attributes - - - Admin area. Manage Blog - - - Admin area. Manage Campaigns - - - Admin area. Manage Categories - - - Admin area. Manage Countries - - - Admin area. Manage Currencies - - - Admin area. Manage Current Carts - - - Admin area. Manage Customers - - - Admin area. Manage Discounts - - - Admin area. Manage Email Accounts - - - Admin area. Manage External Authentication Methods - - - Admin area. Manage Forums - - - Admin area. Manage Gift Cards - - - Admin area. Manage Languages - - - Admin area. Manage Maintenance - - - Admin area. Manage Manufacturers - - - Admin area. Manage Message Queue - - - Admin area. Manage Message Templates - - - Admin area. Manage News - - - Admin area. Manage Newsletter Subscribers - - - Admin area. Manage Orders - - - Admin area. Manage Payment Methods - - - Admin area. Manage Plugins - - - Admin area. Manage Polls - - - Admin area. Manage Product Reviews - - - Admin area. Manage Products - - - Admin area. Manage Product Tags - - - Admin area. Manage Recurring Payments - - - Admin area. Manage Return Requests - - - Admin area. Manage Schedule Tasks - - - Admin area. Manage Settings - - - Admin area. Manage Shipping Settings - - - Admin area. Manage Stores - - - Admin area. Manage System Log - - - Admin area. Manage Tax Settings - - - Admin area. Manage Topics - - - Admin area. Manage Vendors - - - Admin area. Manage Widgets - - - Admin area. Access order country report. - - - Public store. Allow navigation - - - Only registered users can vote. - - - Please select an answer - - - Community poll - - - {0} vote(s)... - - - Vote - - - {0} ({1} vote(s) - {2}%) - - - Inbox - - - Date - - - Delete selected - - - From - - - Mark selected as unread - - - Subject - - - To - - - Message cannot be empty - - - PM - - - Private Messages - - - Cancel - - - Message: - - - Post a Private Message - - - Send - - - Subject: - - - Subject is required. - - - To: - - - Date - - - Delete selected - - - Subject - - - To - - - Sent Items - - - Subject cannot be empty - - - ({0} Unread) - - - Back to messages - - - Delete - - - From: - - - Reply - - - Subject: - - - To: - - - View Message - - - You have {0} unread message(s) in your Inbox - - - - quantity {0} - - - Customers who bought this item also bought - - - Availability - - - Available in {0} - - - Out of Stock - on backorder and will be dispatched once in stock. - - - Out of stock - on backorder and will be dispatched once in stock ({0}). - - - In stock - - - {0} in stock - - - Out of stock - - - equates to {0} per {1} {2} - - - Home - - - Call for pricing - - - Compare products - - - Add to compare list - - - Clear list - - - Full description - - - Compare products list - - - Name - - - You have no items to compare. - - - Price - - - Short description - - - Compare products - - - Delivery date - - - Details - - - Sorry - this product is no longer available - - - Download sample - - - Email a friend - - - Send email - - - Friend's email - - - Enter friend's email. - - - Enter friend's email - - - Only registered customers can use email a friend feature - - - Personal message - - - Enter personal message (optional). - - - Your message has been sent. - - - Email a friend - - - Your email address - - - Enter your email address. - - - Enter your email - - - Please enter your price - - - The price must be from {0} to {1} - - - {0} excl tax - - - Featured products - - - Free shipping - - - Message - - - Recipient's Email - - - Recipient's Name - - - Your Email - - - Your Name - - - GTIN - - - {0} incl tax - - - Manufacturer - - - Manufacturer part number - - - Manufacturers - - - This product has a minimum quantity of {0} - - - New products - - - RSS - - - Click here to be informed automatically when we add new items to our site. - - - This product is sold out - - - Price - - - Old price - - - {0} per {1} day(s) - - - {0} per {1} week(s) - - - {0} per {1} month(s) - - - {0} per {1} year(s) - - - Rental price - - - shipping]]> - - - shipping]]> - - - Your price - - - From {0} - - - {0} [{1}{2}] - - - per item - - - Enter quantity: - - - The product has been added to your product comparison - - - product comparison]]> - - - The product has been added to your shopping cart - - - shopping cart]]> - - - The product has been added to your wishlist - - - wishlist]]> - - - Qty - - - Recently viewed products - - - Related products - - - Start date - - - End date - - - SKU - - - Products specifications - - - Product tags - - - All product tags - - - ({0}) - - - Popular tags - - - View all - - - Products tagged with '{0}' - - - PRICE BREAKS - The more you buy, the more you save - - - Price - - - Quantity - - - Vendor - - - Please note that this product is excluded from the reward points program. No points will be earned. - - - Add to wishlist - - - Update - - - Country - - - Date Of Birth - - - Full Name - - - Join Date - - - Latest Posts - - - No posts found - - - Profile Info - - - Posted - - - Profile: {0} - - - Statistics - - - Topic - - - Total Posts - - - Comments - - - Your return request has not been submitted because you haven't chosen any items. - - - Product - - - Unit price - - - Qty. to return - - - Return action - - - Return reason - - - Which items do you want to return? - - - Submit return request - - - Your return request has been submitted successfully. - - - order #{1}]]> - - - Upload (any additional document, scan, etc) - - - Why are you returning these items? - - - Reviews - - - Date - - - Existing reviews - - - Rating - - - Bad - - - Excellent - - - Review text - - - Review text is required. - - - Review title - - - Max length of product review title is {0} chars - - - Review title is required. - - - From - - - Only registered customers can set review helpfulness - - - Successfully voted - - - Was this review helpful? - - - You cannot vote for your own review - - - Only registered users can write reviews - - - Add your review - - - Be the first to review this product - - - review(s) - - - Product can be reviewed only after purchasing it - - - Product reviews for - - - A manager responded to this review - - - You will see the product review after approving by a store administrator. - - - Submit review - - - Product review is successfully added. - - - {0} review(s) - - - Write your own review - - - The points will be activated on {0} - - - Your current balance - - - Total - - - Amount - - - Date - - - Message - - - Points - - - Points Purchased - - - Points balance - - - Points balance purchased - - - History - - - Earned promotion for order #{0} - - - Registered as customer - - - Redeemed for order #{0} - - - Reduced promotion for order #{0} - - - Returned back for order #{0} - - - Minimum balance allowed to use is {0} reward points ({1}). - - - There is no balance history yet - - - Purchased with order #{0} - - - Purchased and earned with order #{0} - - - Search - - - Advanced search - - - Search - - - Category - - - Please enter some search keyword - - - Automatically search sub categories - - - Manufacturer - - - No products were found that matched your criteria. - - - Price range - - - From - - - to - - - Search store - - - Search In product descriptions - - - Search keyword - - - Search term minimum length is {0} characters - - - Vendor - - - Shopping cart - - - Add to cart - - - Update - - - Add to wishlist - - - Update - - - Add to compare list - - - Allowed quantities for this product: {0} - - - {0}. {1}. {2} - - - Available in {0} - - - Buying is disabled for this product - - - Product (Id={0}) cannot be loaded - - - Your cart has standard and auto-ship (recurring) items. Only one product type is allowed per order. - - - Your Shopping Cart is empty! - - - {0} [{1}] - - - Your cart has auto-ship (recurring) items with conflicting shipment schedules. Only one auto-ship schedule is allowed per order. - - - Continue shopping - - - Based on your selection, you may be interested in the following items - - - The price must be from {0} to {1} - - - Sorry, you've used this discount already - - - Sorry, this discount cannot be used with gift cards in the cart - - - Sorry, this discount cannot be used with reward points products in the cart - - - Sorry, this offer is expired - - - Sorry, this offer is not started yet - - - Discount Code - - - The coupon code was applied - - - Apply coupon - - - Entered coupon code - {0} - - - Enter your coupon here - - - The coupon code you entered couldn't be applied to your order - - - Estimate shipping - - - Estimate shipping - - - Country - - - {0} ({1}) - - - State / province - - - Enter your destination to get a shipping estimate - - - Zip / postal code - - - Your file successfully uploaded! - - - Gift Cards - - - The gift card code was applied - - - Add gift card - - - You cannot use gift cards with auto-ship (recurring) items. - - - Enter gift card code - - - The coupon code you entered couldn't be applied to your order - - - ({0}) - - - Image - - - Total - - - You save: {0} - - - Discounted qty: {0} - - - The maximum quantity allowed for purchase is {0}. - - - The maximum number of distinct products allowed in the cart is {0}. - - - Maximum file size is {0} KB - - - The maximum number of distinct products allowed in the wishlist is {0}. - - - {0} item(s) - - - There are {0} in your cart. - - - You have no items in your shopping cart. - - - Quantity - - - Unit price - - - Go to cart - - - The minimum quantity allowed for purchase is {0}. - - - Product is not available - - - Out of stock - - - Pre-order - - - Product(s) - - - Product is deleted - - - Product is not published - - - Qty. - - - Your quantity exceeds stock on hand. The maximum quantity that can be added is {0}. - - - Quantity should be positive - - - Enter valid recipient email - - - Enter valid recipient name - - - [Auto-ship, Every {0} {1}] - - - Remove - - - Rent - - - Enter rental end date - - - Enter rental start date - - - Start date: {0}. End date: {1}. - - - Rental start date should be less than end date - - - Rental start date should be the future date - - - This product requires the following product is added to the cart: {0} - - - Please select {0} - - - Enter valid sender email - - - Enter valid sender name - - - SKU - - - shipping]]> - - - shipping]]> - - - {0} : maximum length is {1} chars - - - {0} : minimum length is {1} chars - - - Calculated during checkout - - - Gift Card - - - ({0}) - - - {0} remaining - - - Total to pay - - - Invoice Discount - - - Payment method additional fee - - - {0} used reward points - - - {0} used purchased reward points - - - You will earn (based on {0}) - - - (*) when using purchased points - - - {0} point(s) - - - Shipping - - - ({0}) - - - Not required - - - Sub-Total - - - Discount - - - Tax - - - Included Tax - - - Total Amount - - - Total Amount incl. Tax - - - Tax % - - - Tax {0}% - - - Price - - - Update shopping cart - - - Update qty - - - Wishlist is disabled for this product - - - Sitemap - - - Categories - - - View the sitemap for this website below, with links to each of the pages and brief descriptions of what to find in each section - - - General - - - Manufacturers - - - Products - - - This store is currently closed - - - Please check back in a little while. - - - Select store theme - - - Show prices tax exclusive - - - Show prices tax inclusive - - - Tax display type - - - Enter - - - Please enter password to access this content: - - - Wrong password - - - Vendors - - - Apply for vendor account - - - You already applied for a vendor account. Please register as a new customer in order to apply for one more vendor account. - - - Submit - - - Description - - - Email - - - Enter your email address. - - - Email is required. - - - Vendor name - - - Enter vendor name. - - - Vendor name is required. - - - Picture - - - You can add only picture file - - - Your request has been submitted successfully. We'll contact you soon. - - - Vendor List - - - View all - - - Wishlist - - - Some product(s) from wishlist could not be moved to the cart for some reasons. - - - The wishlist is empty! - - - Email a friend - - - Send email - - - Friend's email - - - Enter friend's email. - - - Enter friend's email - - - Only registered customers can use email a friend feature - - - Personal message - - - Enter personal message (optional). - - - Your message has been sent. - - - Email your wishlist to a friend - - - Your email address - - - Enter your email address. - - - Enter your email - - - ({0}) - - - shipping]]> - - - shipping]]> - - - Update wishlist - - - Wishlist of {0} - - - Your wishlist URL for sharing - + + + + Account activation + + + Your account has been activated + + + Your account already has been activated + + + Administration + + + External authentication + + + You can associate your account with some external authentication systems on the following page (login once using them): + + + Authentication method + + + Email + + + External identifier + + + - or - + + + Remove + + + Account Association: Your new user account will be linked to + + + (remove) + + + Avatar + + + Maximum avatar size is {0} bytes + + + Remove avatar + + + Avatar must be in GIF or JPEG format with the maximum size of 20 KB + + + Back in stock subscriptions + + + Delete selected + + + You will receive an e-mail when a particular product is back in stock. + + + You are not currently subscribed to any Back In Stock notification lists + + + Product + + + Change password + + + Change password + + + Email is not entered + + + The specified email could not be found + + + Old password doesn't match + + + Password is not entered + + + You entered the password that is the same as one of the last passwords you used. Please create a new password. + + + Confirm password + + + Password is required. + + + New password + + + The new password and confirmation password do not match. + + + The password should have at least {0} characters. + + + New password is required. + + + Old password + + + Old password is required. + + + Your password has expired, please create a new one + + + Password was changed + + + Username available + + + Check Availability + + + Current username + + + Please enter username + + + Username not available + + + Company Details + + + Addresses + + + Add new address + + + Edit address + + + No addresses + + + Customer info + + + Orders + + + No orders + + + Order Date + + + Details + + + Order Number + + + Order status + + + Order Total + + + Recurring payments + + + Cancel payment + + + Cycle info + + + Cycles remaining + + + Initial order + + + Next payment + + + Retry last payment + + + Start date + + + Total cycles + + + View order (Order number - {0}) + + + Return Item(s) + + + My product reviews + + + Approved + + + Pending + + + You haven't written any reviews yet + + + Product review for + + + Return requests + + + Return Action: + + + Your Comments: + + + Date Requested: + + + Returned Item: + + + Return Reason: + + + Return #{0} - {1} + + + Uploaded file: + + + Download + + + Downloadable products + + + The e-mail address is already in use + + + E-mail address is too long + + + New email is not valid + + + The username is already in use + + + Username is too long + + + Email validation + + + Your email already has been validated + + + Your email has been validated + + + I accept privacy policy + + + (read) + + + Please accept privacy policy + + + City + + + City is required + + + Company name + + + Company name is required + + + Confirm email + + + Email is required. + + + Confirm password + + + Password is required. + + + Country + + + Country is required + + + Date of birth + + + You have to be {0} + + + Date of birth is required + + + Email + + + The email and confirmation email do not match. + + + Email is required. + + + New email + + + (not validated yet) + + + Fax + + + Fax is required + + + First name + + + First name is required. + + + Gender + + + Female + + + Male + + + Last name + + + Last name is required. + + + Newsletter + + + Password + + + The password and confirmation password do not match. + + + The password should have at least {0} characters. + + + Password is required. + + + Phone + + + Phone is required + + + Signature + + + State / province + + + State / province is required + + + Street address + + + Street address is required + + + Street address 2 + + + Street address 2 is required + + + Time zone + + + Username + + + Username is required. + + + VAT number + + + NOTE: Enter VAT number with country code (e.g. GB 111 111 11) + + + Status + + + status: {0} + + + Zip / postal code + + + Zip / postal code is required + + + Forum subscriptions + + + Delete Selected + + + You will receive an e-mail when a new forum topic/post is created. + + + Forum/Topic + + + You are not currently subscribed to any forums + + + Impersonated as {0} + + + finish impersonated session + + + Click here to finish impersonated session. + + + Log in + + + Checkout as Guest + + + Checkout as a guest or register + + + Email + + + Please enter your email + + + Password + + + Remember me? + + + Username + + + Forgot password? + + + Log in + + + New Customer + + + By creating an account on our website you will be able to shop faster, be up to date on an orders status, and keep track of the orders you have previously made. + + + Returning Customer + + + Login was unsuccessful. Please correct the errors and try again. + + + Welcome, Please Sign In! + + + The credentials provided are incorrect + + + No customer account found + + + Customer is deleted + + + Customer is locked out + + + Account is not active + + + Account is not registered + + + Log out + + + My account + + + My account + + + Options + + + Password recovery + + + Recover + + + Confirm password + + + Password is required. + + + Your email address + + + Enter your email + + + Email with instructions has been sent to you. + + + Email not found. + + + Your password recovery link is expired + + + New password + + + The new password and confirmation password do not match. + + + The password should have at least {0} characters. + + + New password is required. + + + Old password + + + Old password is required. + + + Your password already has been changed. For changing it once more, you need to again recover the password. + + + Your password has been changed + + + Recover + + + Please enter your email address below. You will receive a link to reset your password. + + + Wrong password recovery token + + + Preferences + + + Register + + + Register + + + The specified email already exists + + + Email is required. + + + Password is not provided + + + The specified username already exists + + + Username is required. + + + Your account will be activated after approving by administrator. + + + Continue + + + Registration not allowed. You can edit this in the admin area. + + + Your registration has been successfully completed. You have just been sent an email containing membership activation instructions. + + + Your registration completed + + + Registration was not complete. Please correct the errors and try again. + + + Reward points + + + Shopping Cart + + + Vendor info + + + Description + + + Email + + + Email is required. + + + Name + + + Vendor name is required. + + + Picture + + + You can add only picture file + + + Remove picture + + + Your Address + + + Your Contact Information + + + Your Password + + + Your Personal Details + + + Added a new address attribute (ID = {0}) + + + Added a new address attribute value (ID = {0}) + + + Added a new affiliate (ID = {0}) + + + Added a new blog post (ID = {0}) + + + Added a new campaign (ID = {0}) + + + Added a new category ('{0}') + + + Added a new checkout attribute ('{0}') + + + Added a new country (ID = {0}) + + + Added a new currency (ID = {0}) + + + Added a new customer (ID = {0}) + + + Added a new customer attribute (ID = {0}) + + + Added a new customer attribute value (ID = {0}) + + + Add customer reward points (points = {0}, purchased points = {1}) + + + Added a new customer role ('{0}') + + + Added a new discount ('{0}') + + + Added a new email account (ID = {0}) + + + Added a new gift card ('{0}') + + + Added a new language (ID = {0}) + + + Added a new manufacturer ('{0}') + + + Added a new measure dimension (ID = {0}) + + + Added a new measure weight (ID = {0}) + + + Added a new news (ID = {0}) + + + Added a new product ('{0}') + + + Added a new product attribute ('{0}') + + + Added a new setting ('{0}') + + + Added a new specification attribute ('{0}') + + + Added a new state or province (ID = {0}) + + + Added a new store (ID = {0}) + + + Added a new topic ('{0}') + + + Added a new vendor (ID = {0}) + + + Added a new warehouse (ID = {0}) + + + Added a new widget (ID = {0}) + + + Deleted activity log + + + Deleted an address attribute (ID = {0}) + + + Deleted an address attribute value (ID = {0}) + + + Deleted an affiliate (ID = {0}) + + + Deleted a blog post (ID = {0}) + + + Deleted a blog post comment (ID = {0}) + + + Deleted a campaign (ID = {0}) + + + Deleted a category ('{0}') + + + Deleted a checkout attribute ('{0}') + + + Deleted a country (ID = {0}) + + + Deleted a currency (ID = {0}) + + + Deleted a customer (ID = {0}) + + + Deleted a customer attribute (ID = {0}) + + + Deleted a customer attribute value (ID = {0}) + + + Deleted a customer role ('{0}') + + + Deleted a discount ('{0}') + + + Deleted an email account (ID = {0}) + + + Deleted a gift card ('{0}') + + + Deleted a language (ID = {0}) + + + Deleted a manufacturer ('{0}') + + + Deleted a measure dimension (ID = {0}) + + + Deleted a measure weight (ID = {0}) + + + Deleted a message template (ID = {0}) + + + Deleted a news (ID = {0}) + + + Deleted a news comment (ID = {0}) + + + Deleted an order (ID = {0}) + + + Deleted a product ('{0}') + + + Deleted a product attribute ('{0}') + + + Deleted a product review (ID = {0}) + + + Deleted a return request (ID = {0}) + + + Deleted a setting ('{0}') + + + Deleted a specification attribute ('{0}') + + + Deleted a state or province (ID = {0}) + + + Deleted a store (ID = {0}) + + + Deleted a topic ('{0}') + + + Deleted a vendor (ID = {0}) + + + Deleted a warehouse (ID = {0}) + + + Deleted a widget (ID = {0}) + + + Edited activity log types + + + Edited an address attribute (ID = {0}) + + + Edited an address attribute value (ID = {0}) + + + Edited an affiliate (ID = {0}) + + + Edited a blog comment (ID = {0}) + + + Edited a blog post (ID = {0}) + + + Edited a campaign (ID = {0}) + + + Edited a category ('{0}') + + + Edited a checkout attribute ('{0}') + + + Edited a country (ID = {0}) + + + Edited a currency (ID = {0}) + + + Edited a customer (ID = {0}) + + + Edited a customer attribute (ID = {0}) + + + Edited a customer attribute value (ID = {0}) + + + Edited a customer role ('{0}') + + + Edited customer reward points (ID = {0}) + + + Edited a discount ('{0}') + + + Edited an email account (ID = {0}) + + + Edited a gift card ('{0}') + + + Edited a language (ID = {0}) + + + Edited a manufacturer ('{0}') + + + Edited a measure dimension (ID = {0}) + + + Edited a measure weight (ID = {0}) + + + Edited a message template (ID = {0}) + + + Edited a news (ID = {0}) + + + Edited a news comment (ID = {0}) + + + Edited an order (Order number = {0}). See order notes for details + + + Edited a plugin (FriendlyName: '{0}') + + + Edited a product ('{0}') + + + Edited a product attribute ('{0}') + + + Edited a product review (ID = {0}) + + + Edited a return request (ID = {0}) + + + Edited settings + + + Edited a specification attribute ('{0}') + + + Edited a state or province (ID = {0}) + + + Edited a store (ID = {0}) + + + Edited a task (ID = {0}) + + + Edited a topic ('{0}') + + + Edited a vendor (ID = {0}) + + + Edited a warehouse (ID = {0}) + + + Edited a widget (ID = {0}) + + + Started customer impersonation (Email: {0}, ID = {1}) + + + Finished customer impersonation (Email: {0}, ID = {1}) + + + Impersonated by store owner (Email: {0}, ID = {1}) + + + Impersonation by store owner was finished (Email: {0}, ID = {1}) + + + {0} categories were imported + + + {0} manufacturers were imported + + + {0} products were imported + + + {0} states and provinces were imported + + + Installed a new plugin (FriendlyName: '{0}') + + + Uninstalled a plugin (FriendlyName: '{0}') + + + Added a blog comment + + + Added a news comment + + + Added a product review ('{0}') + + + Added a product to compare list ('{0}') + + + Added a product to shopping cart ('{0}') + + + Added a product to wishlist ('{0}') + + + Used contact us form + + + Login + + + Logout + + + Placed a new order (ID = {0}) + + + Sent PM to customer ('{0}') + + + Public store. Viewed a category details page ('{0}') + + + Public store. Viewed a manufacturer details page ('{0}') + + + Public store. Viewed a product details page ('{0}') + + + Address + + + Address 1 + + + Address 1 is required. + + + Address 2 + + + Address 2 is required. + + + City + + + City is required. + + + Company + + + Company is required. + + + Country + + + Country is required. + + + Email + + + Email is required. + + + Fax number + + + Fax number is required. + + + First name + + + First name is required. + + + Last name + + + Last name is required. + + + Phone number + + + Phone number is required. + + + State / province + + + State / province is required. + + + Zip / postal code + + + Zip / postal code is required. + + + Other (Non US) + + + Select country + + + Select state + + + Admin + + + You do not have permission to perform the selected operation. + + + Access denied. + + + Address + + + Custom address attributes + + + Add a new address attribute + + + back to address attribute list + + + If the default form fields are not enough for your needs, then you can manage additional address attributes below. + + + Edit address attribute details + + + Attribute info + + + The new attribute has been added successfully. + + + The attribute has been deleted successfully. + + + The attribute has been updated successfully. + + + Name + + + Please provide a name. + + + The name of the address attribute. + + + Required + + + When an attribute is required, the customer must choose an appropriate attribute value before they can continue. + + + Control type + + + Choose how to display your attribute values. + + + Display order + + + The address attribute display order. 1 represents the first item in the list. + + + Attribute values + + + Add a new address value + + + Edit address value details + + + You need to save the address attribute before you can add values for this address attribute page. + + + Name + + + Please provide a name. + + + The name of the address value. + + + Pre-selected + + + Determines whether this attribute value is pre selected for the address. + + + Display order + + + The display order of the attribute value. 1 represents the first item in attribute value list. + + + Address 1 + + + Enter address 1. + + + Address 1 is required. + + + Address 2 + + + Enter address 2. + + + Address 2 is required. + + + City + + + Enter city. + + + City is required. + + + Company + + + Enter company. + + + Company is required. + + + Country + + + Select country. + + + Country is required. + + + Email + + + Enter email. + + + Email is required. + + + Fax number + + + Enter fax number. + + + Fax number is required. + + + First name + + + Enter first name. + + + First name is required. + + + Last name + + + Enter last name. + + + Last name is required. + + + Phone number + + + Enter phone number. + + + Phone number is required. + + + State / province + + + Select state / province. + + + State / province is required. + + + Zip / postal code + + + Enter zip / postal code. + + + Zip / postal code is required. + + + Other (Non US) + + + Select country + + + Select state + + + Affiliates + + + The new affiliate has been added successfully. + + + Add a new affiliate + + + back to affiliate list + + + Affiliated customers + + + Name + + + The affiliate has been deleted successfully. + + + Affiliate is an Internet-based marketing practice in which a business rewards one or more affiliates for each visitor or customer. It is a web-based pay-for-performance program designed to compensate affiliate partner for driving qualified leads or sales to a merchant web site. + + + Edit affiliate details + + + Active + + + A value indicating whether the affiliate is active. + + + Admin comment + + + Admin comment. For internal use. + + + Friendly URL name + + + A friendly name for generated affiliate URL (by default affiliate ID is used). It's more friendly for marketing purposes. Leave empty to use affiliate identifier. + + + Affiliate URL + + + When this hyperlink is clicked from the affiliate site, this site looks for an Affiliate ID query string parameter. If one exists, the customer is tagged with that affiliate. + + + Affiliate info + + + Load only with orders + + + Check to load affiliates only with orders placed (by affiliated customers). + + + Orders start date + + + The start date for the order search. + + + Orders end date + + + The end date for the order search. + + + First name + + + Search by a first name. + + + Friendly URL name + + + Search by a friendly URL name. + + + Last name + + + Search by a last name. + + + Affiliated orders + + + Created on + + + Order # + + + End date + + + The end date for the search. + + + Order status + + + Search by a specific order status e.g. Complete. + + + Order total + + + Payment status + + + Search by a specific payment status e.g. Paid. + + + Shipping status + + + Search by a specific shipping status e.g. Not yet shipped. + + + Start date + + + The start date for the search. + + + The affiliate has been updated successfully. + + + Catalog + + + Attributes + + + Checkout attributes + + + The new attribute has been added successfully. + + + Add a new checkout attribute + + + back to checkout attribute list + + + Condition + + + Attribute + + + Choose an attribute. + + + Enable condition + + + Check to specify a condition (depending on other attribute) when this attribute should be enabled (visible). + + + No attribute exists that could be used as condition. + + + You need to save the checkout attribute before you can edit conditional attributes. + + + The attribute has been deleted successfully. + + + Checkout attributes are displayed on the shopping cart page and provide the opportunity to offer more services to customers, i.e. gift wrapping, before placing the order. + + + Edit checkout attribute details + + + Control type + + + Choose how to display your attribute values. + + + Default value + + + Enter default value for attribute. + + + Display order + + + The checkout attribute display order. 1 represents the first item in the list. + + + Allowed file extensions + + + Specify a comma-separated list of allowed file extensions. Leave empty to allow any file extension. + + + Maximum file size (KB) + + + Specify maximum file size in kilobytes. Leave empty to skip this validation. + + + Required + + + When an attribute is required, the customer must choose an appropriate attribute value before they can continue. + + + Tax exempt + + + Determines whether this option is tax exempt (tax will not be applied to this option at checkout). + + + Limited to stores + + + Option to limit this attribute to a certain store. If you have multiple stores, choose one or several from the list. If you don't use this option just leave this field empty. + + + Maximum length + + + Specify maximum length. Leave empty to skip this validation. + + + Minimum length + + + Specify minimum length. Leave empty to skip this validation. + + + Name + + + The name of the checkout attribute. + + + Please provide a name. + + + Shippable product required + + + An option indicating whether shippable products are required in order to display this attribute. + + + Tax category + + + The tax classification for this attribute (used to calculate tax). You can manage tax categories by selecting Configuration : Tax : Tax Categories. + + + Text prompt + + + Enter text prompt. + + + Attribute info + + + The attribute has been updated successfully. + + + Attribute values + + + Add a new checkout value + + + Edit checkout value details + + + RGB color + + + Choose color to be used with the color squares attribute control. + + + Display order + + + The display order of the attribute value. 1 represents the first item in attribute value list. + + + Pre-selected + + + Determines whether this attribute value is pre selected for the customer. + + + Name + + + The name of the checkout value. + + + Please provide a name. + + + Price adjustment + + + The price adjustment applied when choosing this attribute value e.g. '10' to add 10 dollars. + + + Weight adjustment + + + The weight adjustment applied when choosing this attribute value. + + + You need to save the checkout attribute before you can add values for this checkout attribute page. + + + Product attributes + + + The new attribute has been added successfully. + + + Add a new product attribute + + + back to product attribute list + + + The attribute has been deleted successfully. + + + Product attributes are quantifiable or descriptive aspects of a product (such as, color). For example, if you were to create an attribute for color, with the values of blue, green, yellow, and so on, you may want to apply this attribute to shirts, which you sell in various colors (you can adjust a price or weight for any of existing attribute values). You can add attributes to existing product on a product details page. + + + Edit product attribute details + + + Description + + + The description of the product attribute. + + + Name + + + The name of the product attribute. + + + Please provide a name. + + + Info + + + Predefined values + + + Add a new value + + + Edit value + + + Cost + + + The attribute value cost is the cost of all the different components which make up this value. This may be either the purchase price if the components are bought from outside suppliers, or the combined cost of materials and manufacturing processes if the component is made in-house. + + + Display order + + + The display order of the attribute value. 1 represents the first item in attribute value list. + + + Is pre-selected + + + Determines whether this attribute value is pre selected for the customer. + + + Name + + + The attribute value name e.g. 'Blue' for Color attributes. + + + Please provide a name. + + + Price adjustment + + + The price adjustment applied when choosing this attribute value e.g. '10' to add 10 dollars. + + + Weight adjustment + + + The weight adjustment applied when choosing this attribute value. + + + Predefined (default) values are helpful for a store owner when creating new products. Then when you add the attribute to a product, you don't have to create the values again. + + + You need to save the product attribute before you can add values for this page. + + + The attribute has been updated successfully. + + + Used by products + + + Here you can see a list of products which use this attribute. + + + Product + + + Published + + + Specification attributes + + + The new attribute has been added successfully. + + + Add a new specification attribute + + + back to specification attribute list + + + The attribute has been deleted successfully. + + + Specification attributes are product features i.e, screen size, number of USB-ports, visible on product details page. Specification attributes can be used for filtering products on the category details page. Unlike product attributes, specification attributes are used for information purposes only. You can add attributes to existing product on a product details page. + + + Edit specification attribute details + + + Display order + + + The display order of the specification attribute. + + + Name + + + The name of the specification attribute. + + + Please provide a name. + + + Attribute info + + + Options + + + Add a new option + + + Edit option details + + + RGB color + + + Choose color to be used instead of an option text name (it'll be displayed as "color square"). + + + Display order + + + The display order of the option. + + + Specify color + + + Check to choose color to be used instead of an option text name (it'll be displayed as "color square"). + + + Name + + + The name of the option. + + + Please provide a name. + + + Number of associated products + + + You need to save the specification attribute before you can add options for this specification attribute page. + + + The attribute has been updated successfully. + + + Bulk edit products + + + Manage inventory + + + Name + + + Old price + + + Price + + + Published + + + SKU + + + Stock qty + + + Category + + + Search by a specific category. + + + Manufacturer + + + Search by a specific manufacturer. + + + Product name + + + A product name. + + + Categories + + + The new category has been added successfully. + + + Add a new category + + + back to category list + + + Breadcrumb + + + The category has been deleted successfully. + + + Edit category details + + + Limited to customer roles + + + Select customer roles for which the category will be shown. Leave empty if you want this category to be visible to all users. + + + Allow customers to select page size + + + Whether customers are allowed to select the page size from a predefined list of options. + + + Category template + + + Choose a category template. This template defines how this category (and its products) will be displayed. + + + Created on + + + Deleted + + + Description + + + The description of the category. + + + Discounts + + + Select discounts to apply to this category. You can manage discounts by selecting Discounts from the Promotions menu. + + + No discounts available. Create at least one discount before mapping. + + + Display order + + + Set the category's display order. 1 represents the top of the list. + + + Include in top menu + + + Display in the top menu bar. If this category is a subcategory, then ensure that its parent category also has this property enabled. + + + Limited to stores + + + Option to limit this category to a certain store. If you have multiple stores, choose one or several from the list. If you don't use this option just leave this field empty. + + + Meta description + + + Meta description to be added to category page header. + + + Meta keywords + + + Meta keywords to be added to category page header. + + + Meta title + + + Override the page title. The default is the name of the category. + + + Name + + + The category's name. + + + Please provide a name. + + + Page size + + + Set the page size for products in this category e.g. '4' products per page. + + + Page size should be positive. + + + Page Size options (comma separated) + + + Comma separated list of page size options (e.g. 10, 5, 15, 20). First option is the default page size if none are selected. + + + Page Size options should not have duplicate items. + + + Parent category + + + Select a parent category for this category. Leave this field empty to make this the root level category. + + + [None] + + + Picture + + + The category picture. + + + Price ranges + + + Define the price ranges for the store price range filter. Separate ranges with a semicolon e.g. '0-199;200-300;301-;' (301- means 301 and over). + + + Published + + + Check to publish this category (visible in store). Uncheck to unpublish (category not available in store). + + + Search engine friendly page name + + + Set a search engine friendly page name e.g. 'the-best-category' to make your page URL 'http://www.yourStore.com/the-best-category'. Leave empty to generate it automatically based on the name of the category. + + + Show on home page + + + Check if you want to show a category on home page. + + + Categories have been imported successfully. + + + Category info + + + Imported categories are distinguished by ID. If the ID already exists, then its corresponding category will be updated. You should not specify ID (leave 0) for new categories. + + + Category name + + + A category name. + + + Store + + + Search by a specific store. + + + Manage Categories + + + Products + + + Add a new product + + + Display order + + + Is featured product? + + + Product + + + You need to save the category before you can add products for this category page. + + + switch to list view + + + The category has been updated successfully. + + + Low stock report + + + Manufacturers + + + The new manufacturer has been added successfully. + + + Add a new manufacturer + + + back to manufacturer list + + + The manufacturer has been deleted successfully. + + + Discounts + + + Select discounts to apply to this manufacturer. You can manage discounts by selecting Discounts from the Promotions menu. + + + No discounts available. Create at least one discount before mapping. + + + Edit manufacturer details + + + Limited to customer roles + + + Select customer roles for which the manufacturer will be shown. Leave empty if you want this manufacturer to be visible to all users. + + + Allow customers to select page size + + + Whether customers are allowed to select the page size from a predefined list of options. + + + Created on + + + Deleted + + + Description + + + The description of the manufacturer. + + + Display order + + + Set the manufacturer's display order. 1 represents the top of the list. + + + Limited to stores + + + Option to limit this manufacturer to a certain store. If you have multiple stores, choose one or several from the list. If you don't use this option just leave this field empty. + + + Manufacturer template + + + Choose a manufacturer template. This template defines how this manufacturer (and its products) will be displayed. + + + Meta description + + + Meta description to be added to manufacturer page header. + + + Meta keywords + + + Meta keywords to be added to manufacturer page header. + + + Meta title + + + Override the page title. The default is the name of the manufacturer. + + + Name + + + The manufacturer's name. + + + Please provide a name. + + + Page size + + + Set the page size for products in this manufacturer e.g. '4' products per page. + + + Page size should be positive. + + + Page Size options (comma separated) + + + Comma separated list of page size options (e.g. 10, 5, 15, 20). First option is the default page size if none are selected. + + + Page Size options should have unique items. + + + Picture + + + The manufacturer picture. + + + Price ranges + + + Define the price ranges for the store price range filter. Separate ranges with a semicolon e.g. '0-199;200-300;301-;' (301- means 301 and over). + + + Published + + + Check to publish this manufacturer (visible in store). Uncheck to unpublish (manufacturer not available in store). + + + Search engine friendly page name + + + Set a search engine friendly page name e.g. 'the-best-manufacturer' to make your page URL 'http://www.yourStore.com/the-best-manufacturer'. Leave empty to generate it automatically based on the name of the manufacturer. + + + Manufacturers have been imported successfully. + + + Manufacturer info + + + Imported manufacturers are distinguished by ID. If the ID already exists, then its corresponding manufacturer will be updated. You should not specify ID (leave 0) for new manufacturers. + + + Manufacturer name + + + A manufacturer name. + + + Store + + + Search by a specific store. + + + Products + + + Add a new product + + + Display order + + + Is featured product? + + + Product + + + You need to save the manufacturer before you can add products for this manufacturer page. + + + The manufacturer has been updated successfully. + + + Product reviews + + + Approve selected + + + back to product review list + + + The product review has been deleted successfully. + + + Delete selected + + + Disapprove selected + + + Edit product review details + + + Created on + + + The date/time that the review was created. + + + Customer + + + The customer who created the review. + + + Is approved + + + Is the review approved? Marking it as approved means that it is visible to all your site's visitors. + + + Product + + + The name of the product that the review is for. + + + Rating + + + The customer's product rating. + + + Reply text + + + The reply text (by a store owner). If specified, then it'll be visible to a customer. Leave empty to ignore this functionality. + + + Review text + + + The review text. + + + Review text is required. + + + Store + + + A store name in which this review was written. + + + Title + + + The title of the product review. + + + Title is required. + + + Created from + + + The creation from date for the search. + + + Created to + + + The creation to date for the search. + + + Approved + + + Search by a "Approved" property. + + + All + + + Approved only + + + Disapproved only + + + Store + + + Search by a specific store. + + + Message + + + Search in title and review text. + + + Product + + + Search by a specific product. + + + The product review has been updated successfully. + + + Products + + + The new product has been added successfully. + + + Add a new product + + + Associated products (variants) + + + Add new associated product + + + Display order + + + Product + + + Associated products are used only with "grouped" products. + + + A product could be associated to only one "grouped" product. + + + You need to save the product before you can add associated products for this product page. + + + back to product list + + + Categories + + + Choose categories. You can manage product categories by selecting Catalog > Categories. + + + No categories available. + + + General information + + + The product has been copied successfully + + + Copy product + + + Copy images + + + Check to copy the images. + + + New product name + + + {0} - copy + + + The name of the new product. + + + Published + + + Check to mark a product as published. + + + {0}-copy + + + Cross-sells + + + Add new cross-sell product + + + Product + + + You need to save the product before you can add cross-sell products for this product page. + + + The Cross-sell products option provides the opportunity to buy additional products that generally go with the selected product. They are displayed at the bottom of the checkout page. + + + The product has been deleted successfully. + + + Downloadable product + + + Edit product details + + + The maximum allowed number of products has been exceeded ({0}) + + + Customer roles + + + Choose one or several customer roles i.e. administrators, vendors, guests, who will be able to see this product in catalog. If you don't need this option just leave this field empty. In order to use this functionality you have to disable the following setting: Configuration > Catalog settings > Ignore ACL rules (sitewide). + + + Additional shipping charge + + + The additional shipping charge. + + + Admin comment + + + This comment is for internal use only, not visible for customers. + + + Allow only existing attribute combinations + + + Check to allow adding to the cart/wishlist only attribute combinations that exist and have stock greater than zero. In this case you have to create all existing product attribute combinations that you have in stock. + + + Allow back in stock subscriptions + + + Allow customers to subscribe to a notification list for a product that has gone out of stock. + + + Allow customer reviews + + + Check to allow customers to review this product. + + + Allowed quantities + + + 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. + + + Associated to product + + + A "grouped" parent product which this one is associated to. + + + Automatically add these products to the cart + + + Check to automatically add these products to the cart. + + + Available end date + + + The end of the product's availability in Coordinated Universal Time (UTC). + + + Available for pre-order + + + Check if this item is available for Pre-Order. It also displays "Pre-order" button instead of "Add to cart". + + + Available start date + + + The start of the product's availability in Coordinated Universal Time (UTC). + + + Backorders + + + Select backorder mode. + + + PAngV (base price) enabled + + + Check to display baseprice of a product. This is required according to the German law (PAngV). If you sell 500ml of beer for 1,50 euro, then you have to display baseprice: 3.00 euro per 1L. + + + Amount in product + + + Enter an amount in product. + + + Unit of product + + + Enter a unit of product. + + + Reference amount + + + Enter a reference amount. + + + Reference unit + + + Enter a reference unit. + + + Call for price + + + Check to show "Call for Pricing" or "Call for quote" instead of price. + + + Created on + + + Date and time when this product was created. + + + Customer enters price + + + An option indicating whether customer should enter price. + + + Delivery date + + + Choose a delivery date which will be displayed in the public store. You can manage delivery dates by selecting Configuration > Shipping > Dates and ranges. + + + None + + + Disable buy button + + + Check 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. + + + Discounts + + + Select discounts to apply to this product. You can manage discounts by selecting Discounts from the Promotions menu. + + + No discounts available. Create at least one discount before mapping. + + + Display order + + + Display order of the product. 1 represents the top of the list. + + + Display availability + + + Check 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. + + + Download file + + + You can download file using URL or uploading from the computer. If you want to download file using URL check the box Use download URL. + + + Download activation type + + + A value indicating when download links will be enabled. + + + Number of days + + + The number of days during customers keeps access to the file (e.g. 14). Leave this field blank to allow continuous downloads. + + + Full description + + + Full description is the text that is displayed in product page. + + + Gift card type + + + There are two gift card types: virtual and physical. WARNING: not recommended to change the gift card type from one to another in a "live" store. + + + GTIN (global trade item number) + + + Enter global trade item number (GTIN). These identifiers include UPC (in North America), EAN (in Europe), JAN (in Japan), and ISBN (for books). + + + Has sample download file + + + You can download file using URL or uploading from the computer. If you want to download file using URL check the box Use download URL. + + + Has user agreement + + + Select this checkbox if the customer has a user agreement (a customer must agree with this user agreement when trying to download the product). + + + Height + + + The product height. + + + ID + + + The product identifier. + + + Downloadable product + + + Check if the product is downloadable. When customers purchase a downloadable product, they can download it direct from your store. The link will be visible after checkout. + + + Free shipping + + + Check if this product comes with FREE shipping. + + + Is gift card + + + Check if it is a gift card. After adding gift card products to the shopping cart and completing the purchases, you can then search and view the list of all the purchased gift cards by selecting Gift Cards from the Sales menu. + + + Recurring product + + + Check if it is a recurring product. For any product, you can define a recurring cycle to enable the system to automatically create orders that repeat when a customer purchases such products. + + + Is rental + + + Check if this is a rental product (price is set for some period). Please note that inventory management is not fully supported for rental products yet. It's recommended to set 'Manage inventory method' to 'Don't track inventory' now. + + + Shipping enabled + + + Check if the product can be shipped. You can manage shipping settings by selecting Configuration > Shipping. + + + Tax exempt + + + Determines whether this product is tax exempt (tax will not be applied to this product at checkout). + + + Telecommunications, broadcasting and electronic services + + + Check if it's telecommunications, broadcasting and electronic services. It's used for tax calculation in Europe Union. + + + Length + + + The product length. + + + Low stock activity + + + Action to be taken when your current stock quantity falls below (reaches) the 'Minimum stock quantity'. + + + Inventory method + + + Select inventory method. There are three methods: Don’t track inventory, Track inventory and Track inventory by attributes. You should use Track inventory by attributes when the product has different combinations of these attributes and then manage inventory for this combinations. + + + Manufacturer part number + + + The manufacturer's part number for this product. + + + Manufacturers + + + Choose the manufacturer. You can manage manufacturers by selecting Catalog > Manufacturers. + + + No manufacturers available. + + + Mark as new + + + Check to mark the product as new. Use this option for promoting new products. + + + Mark as new. Start date + + + Set Product as New from Date in Coordinated Universal Time (UTC). + + + Mark as new. End date + + + Set Product as New to Date in Coordinated Universal Time (UTC). + + + Maximum amount + + + Enter a maximum amount. + + + Max. downloads + + + The maximum number of downloads. + + + Meta description + + + Meta description to be added to product page header. + + + Meta keywords + + + Meta keywords to be added to product page header. + + + Meta title + + + Override the page title. The default is the name of the product. + + + Minimum amount + + + Enter a minimum amount. + + + Minimum stock qty + + + If you have enabled 'Manage Stock' you can perform a number of different actions when the current stock quantity falls below (reaches) your minimum stock quantity. + + + Product availability range + + + Choose the product availability range that indicates when the product is expected to be available when out of stock (e.g. Available in 10-14 days). You can manage availability ranges by selecting Configuration > Shipping > Dates and ranges. + + + None + + + Multiple warehouses + + + Check if you want to support shipping and inventory management from multiple warehouses. + + + Product name + + + The name of the product. + + + Please provide a name. + + + Notify for qty below + + + When the current stock quantity falls below (reaches) this quantity, a store owner will receive a notification. + + + Not returnable + + + Check if this product is not returnable. In this case a customer won't be allowed to submit return request. + + + Old price + + + The old price of the product. If you set an old price, this will display alongside the current price on the product page to show the difference in price. + + + Maximum cart qty + + + Set the maximum quantity allowed in a customer's shopping cart e.g. set to 5 to only allow customers to purchase 5 of this product. + + + Minimum cart qty + + + Set the minimum quantity allowed in a customer's shopping cart e.g. set to 3 to only allow customers to purchase 3 or more of this product. + + + Overridden gift card amount + + + Enter gift card amount that can be used after purchase. If not specified, then product price will be used. + + + Picture + + + Pre-order availability start date + + + The availability start date of the product configured for pre-order in Coordinated Universal Time (UTC). 'Pre-order' button will automatically be changed to 'Add to cart' at this moment. + + + Price + + + The price of the product. You can manage currency by selecting Configuration > Currencies. + + + Product cost + + + Product cost is a prime product cost. This field is only for internal use, not visible for customers. + + + Product tags + + + Product tags are the keywords for product identification. The more products associated with a particular tag, the larger it will show on the tag cloud. + + + Enter tags ... + + + Product template + + + Choose a product template. This template defines how this product will be displayed in public store. + + + Product type + + + Product type can be simple or grouped. In most cases your product will have the Simple product type. You need to use Grouped product type when a new product consists of one or more existing products that will be displayed on one single product details page. + + + Published + + + Check to publish this product (visible in store). Uncheck to unpublish (product not available in store). + + + Cycle length + + + Specify the cycle length. It is a time period recurring order can be repeated. + + + Cycle period + + + Specify the cycle period. It defines units time period can be measured in. + + + Total cycles + + + Total cycles are number of times customer will receive the recurring product. + + + Rental period length + + + Specify period length for rental product. Price is specified for this period. + + + Rental period + + + Specify period for rental product. Price is specified for this period. + + + Required product IDs + + + Add required product + + + Choose + + + Specify comma separated list of required product IDs. NOTE: Ensure that there are no circular references (for example, A requires B, and B requires A). + + + Require other products + + + Check if the product requires adding other products to the cart. + + + Sample download file + + + The sample download file. + + + Search engine friendly page name + + + Set a search engine friendly page name e.g. 'the-best-product' to make your page URL 'http://www.yourStore.com/the-best-product'. Leave empty to generate it automatically based on the name of the product. + + + Limited to stores + + + Option to limit this product to a certain store. If you have multiple stores, choose one or several from the list. If you don't use this option just leave this field empty. In order to use this functionality you have to disable the following setting: Configuration > Catalog settings > Ignore "limit per store" rules (sitewide). + + + Ship separately + + + Check if the product should be shipped separately from other products (in single box). Notice that if the order includes several items of this product, all of them will be shipped separately. + + + Short description + + + Short description is the text that is displayed in product list i.e. сategory / manufacturer pages. + + + Show on home page + + + Check to display this product on your store's home page. Recommended for your most popular products. + + + SKU + + + Product stock keeping unit (SKU). Your internal unique identifier that can be used to track this product. + + + Stock quantity + + + The current stock quantity of this product. + + + Quantity has been changed while you were editing the product. Changes haven't been saved. Please ensure that everything is correct and click "Save" button one more time. + + + Tax category + + + The tax classification for the product. You can manage tax categories by selecting Configuration > Tax > Tax Categories. + + + Unlimited downloads + + + When a customer purchases a download product, they can download the item unlimited number of times. + + + Updated on + + + Date and time when this product was updated. + + + User agreement text + + + The text of the user agreement. + + + Vendor + + + Choose a vendor associated with this product. This can be useful when running a multi-vendor store to keep track of goods associated with vendor. + + + No vendor + + + Visible individually + + + Check it if you want the product to be on catalog or search results. You can uncheck this box to hide associated products from catalog and make them accessible only from grouped product details page. + + + Warehouse + + + Choose the warehouse which will be used when calculating shipping rates. You can manage warehouses by selecting Configuration > Shipping > Warehouses. + + + None + + + Weight + + + The product weight. + + + Width + + + The product width. + + + Is reward point(s) + + + If this field is checked, reward points can be pruchased. + + + Overridden reward points exchange rate + + + Use this field to override reward points exchange rate setting when converting product price to reward points (points = [overridden exchange rate] * price). If not specified, then reward points exchange rate will be used. + + + Exclude from reward points + + + If this field is checked, product will be excluded from reward points calculation. + + + Gift card + + + Reward points + + + Products have been imported successfully. + + + Product info + + + Inventory + + + Download catalog as PDF + + + Go directly to product SKU + + + Enter product SKU and click Go. + + + Imported products are distinguished by SKU. If the SKU already exists, then its corresponding product will be updated. + + + Category + + + Search by a specific category. + + + Search sub categories + + + Check to search in sub categories. + + + Manufacturer + + + Search by a specific manufacturer. + + + Product name + + + A product name. + + + Product type + + + Search by a product type. + + + Published + + + Search by a "Published" property. + + + All + + + Published only + + + Unpublished only + + + Store + + + Search by a specific store. + + + Vendor + + + Search by a specific vendor. + + + Warehouse + + + Search by a specific warehouse. + + + Mappings + + + Pictures + + + Add product picture + + + Add a new picture + + + Display order + + + Display order of the picture. 1 represents the top of the list. + + + Alt + + + Override "alt" attribute for "img" HTML element. If empty, then a default rule will be used (e.g. product name). + + + Title + + + Override "title" attribute for "img" HTML element. If empty, then a default rule will be used (e.g. product name). + + + Picture + + + Choose a picture to upload. If the picture size exceeds your stores max image size setting, it will be automatically resized. + + + You need to save the product before you can upload pictures for this product page. + + + Prices + + + Product attributes + + + Attribute combinations + + + Add combination + + + Select new combination and enter details below + + + Note that some attribute control types that support custom user input (e.g. file upload, textboxes, date picker) are useless with attribute combinations + + + Allow out of stock + + + A value indicating whether to allow orders when out of stock. + + + Attributes + + + GTIN + + + Enter global trade item number (GTIN). These identifiers include UPC (in North America), EAN (in Europe), JAN (in Japan), and ISBN (for books). + + + Manufacturer part number + + + The manufacturer's part number for this attribute combination. + + + Notify admin for quantity below + + + When the current stock quantity falls below (reaches) this quantity, a store owner will receive a notification. + + + Overridden price + + + Override price for this attribute combination. This way a store owner can override the default product price when this attribute combination is added to the cart. For example, you can give a discount this way. Leave empty to ignore field. All other applied discounts will be ignored when this field is specified. + + + Sku + + + Product stock keeping unit (SKU). Your internal unique identifier that can be used to track this attribute combination. + + + Stock quantity + + + The current stock quantity of this combination. + + + Generate all possible combinations + + + Attributes + + + This attribute is already added to this product + + + Condition + + + Attribute + + + Choose an attribute. + + + Conditional attributes appear if a previous attribute is selected, such as having an option for personalizing clothing with a name and only providing the text input box if the "Personalize" radio button is checked + + + Enable condition + + + Check to specify a condition (depending on other attribute) when this attribute should be enabled (visible). + + + Edit condition + + + Attribute + + + Control type + + + Display order + + + Is Required + + + Text prompt + + + + Product attributes are quantifiable or descriptive aspects of a product (such as, color). For example, if you were to create an attribute for color, with the values of blue, green, yellow, and so on, you may want to apply this attribute to shirts, which you sell in various colors (you can adjust a price or weight for any of existing attribute values). + You can add attribute for your product using existing list of attributes, or if you need to create a new one go to Catalog > Attributes > Product attributes. Please notice that if you want to manage inventory by product attributes (e.g. 5 green shirts and 3 blue ones), then ensure that "Inventory method" is set to "Track inventory by product attributes". + + + + Validation rules + + + Default value + + + Enter default value for attribute. + + + Allowed file extensions + + + Specify a comma-separated list of allowed file extensions. Leave empty to allow any file extension. + + + Maximum file size (KB) + + + Specify maximum file size in kilobytes. Leave empty to skip this validation. + + + Maximum length + + + Specify maximum length. Leave empty to skip this validation. + + + Minimum length + + + Specify minimum length. Leave empty to skip this validation. + + + Edit rules + + + Values + + + Add a new value + + + back to product details + + + Add/Edit values for [{0}] attribute. Product: {1} + + + Edit value + + + Associated product + + + Associate a product + + + Choose an associated product + + + The associated product is downloadable, keep in mind that won't be able to download it. + + + The associated product is a gift card, keep in mind that customers can not specify its details in the product details page. + + + The associated product has attributes, keep in mind that customers can not select them in the product details page. + + + The associated product has required product attributes, so customers won't be able to choose this product attribute value. + + + Associated product. + + + Attribute value type + + + Choose your attribute value type. + + + RGB color + + + Choose color to be used with the color squares attribute control. + + + Cost + + + The attribute value cost is the cost of all the different components which make up this value. This may be either the purchase price if the components are bought from outside suppliers, or the combined cost of materials and manufacturing processes if the component is made in-house. + + + Display order + + + The display order of the attribute value. 1 represents the first item in attribute value list. + + + Square picture + + + Upload a picture to be used with the image squares attribute control. + + + Is pre-selected + + + Determines whether this attribute value is pre selected for the customer. + + + Name + + + The attribute value name e.g. 'Blue' for Color attributes. + + + Please provide a name. + + + Picture + + + Choose a picture associated to this attribute value. This picture will replace the main product image when this product attribute value is clicked (selected). + + + No picture + + + Price adjustment + + + The price adjustment applied when choosing this attribute value e.g. '10' to add 10 dollars. + + + Product quantity + + + Quantity should be greater than or equal to 1 + + + Specify quantity of the associated product which will be added. Minimum allowed value is 1. + + + Customer enters quantity + + + Allow customers enter the quantity of associated product. + + + Weight adjustment + + + The weight adjustment applied when choosing this attribute value. + + + Total: + + + Edit values + + + No product attributes available. Create at least one product attribute before mapping. + + + You need to save the product before you can add attributes for this page. + + + Warehouses + + + "Stock quantity" is total quantity. It's reduced from when a shipment is shipped. + + + "Reserved qty" is product quantity that is ordered but not shipped yet. + + + "Planned qty" is product quantity that is ordered and already added to a shipment but not shipped yet. + + + Planned qty + + + Reserved qty + + + Stock qty + + + Warehouse + + + No warehouses defined + + + Use + + + Manage inventory per warehouse. + + + Purchased with orders + + + Here you can see a list of orders in which this product was purchased. + + + Recurring product + + + Related products + + + Add new related product + + + Display order + + + Product + + + You need to save the product before you can add related products for this product page. + + + The Related Products option provides the opportunity to advertise products that are not part of the selected category, to your visitors. These products are displayed on the product details pages. + + + Require other products + + + Rental + + + Access control list + + + Shipping + + + Specification attributes + + + Add attribute + + + Add a new product specification attribute + + + Allow filtering + + + Allow product filtering by this attribute. + + + Attribute type + + + Choose attribute type. + + + Value + + + Custom value (text, hyperlink, etc). + + + Display order + + + Display order of the specification attribute. 1 represents the top of the list. + + + Show on product page + + + The value of the specification attribute. Be visible on the product page. + + + Attribute + + + Choose a product specification attribute. You can manage specification attributes from Catalog : Attributes : Product Specifications. + + + Attribute option + + + The value of the specification attribute. + + + Value + + + No specification attributes defined. + + + You need to save the product before you can add product specification attributes for this product page. + + + Specification attributes are product features i.e, screen size, number of USB-ports, visible on product details page. Specification attributes can be used for filtering products on the category details page. Unlike product attributes, specification attributes are used for information purposes only. + + + Stock quantity history + + + Here you can see a history of the product stock quantity changes. + + + Attribute combination + + + Created On + + + Message + + + Stock quantity + + + Quantity adjustment + + + Warehouse + + + Tier prices + + + Add new tier price + + + Edit tier price details + + + Customer role + + + All customer roles + + + Select customer role for which the tier price will be available. + + + End date + + + The end date of the tier price in Coordinated Universal Time (UTC). + + + Price + + + Specify the price. + + + Quantity + + + Specify quantity for which this tier price will be available. + + + Start date + + + The start date of the tier price in Coordinated Universal Time (UTC). + + + Store + + + All stores + + + Option to limit this tier price to a certain store. If you have multiple stores, choose one from the list. + + + You need to save the product before you can add tier prices for this product page. + + + Tier pricing is a promotional tool that allows a store owner to price items differently for higher quantities. + + + The product has been updated successfully. + + + Product tags + + + Edit product tag details + + + Tag name + + + The name of the product tag. + + + Please provide a name. + + + Tagged products + + + Are you sure you want to perform this action? + + + Add new + + + Add new record + + + All + + + Are you sure? + + + Cancel + + + Change + + + Check + + + Clear + + + Configuration is not required + + + CSV file + + + Delete + + + Delete (selected) + + + Are you sure you want to delete this item? + + + Are you sure you want to delete "{0}"? + + + Edit + + + Excel file + + + Export + + + Export to CSV + + + Export to Excel + + + Export to Excel (all found) + + + Export to Excel (selected) + + + Export to XML + + + Export to XML (all found) + + + Export to XML (selected) + + + Go + + + Hide + + + Import + + + Import from CSV + + + Import from Excel + + + Import requires a lot of memory resources. That's why it's not recommended to import more than 500 - 1,000 records at once. If you have more records, it's better to split them to multiple Excel files and import separately. + + + List + + + You are going to lose any unsaved changes. Are you sure? + + + No + + + No, cancel + + + Preview + + + Save + + + Save and Continue Edit + + + Search + + + Select + + + SEO + + + Show + + + Standard + + + Update + + + Please upload a file + + + View + + + Wrong email + + + Yes + + + Configuration + + + Access control list + + + Access control list is a list of permissions attached to customer roles. This list specifies the access rights of users to objects. + + + Permission name + + + The ACL has been updated successfully. + + + Activity Log + + + Activity Log + + + Activity Log Type + + + The Activity Log Type. + + + Activity Log Type + + + Message + + + Created On + + + Created from + + + The creation from date for the search. + + + Created to + + + The creation to date for the search. + + + Customer + + + Customer Email + + + A customer Email. + + + Activity Types + + + Is Enabled + + + Name + + + The types have been updated successfully. + + + Countries + + + The new country has been added successfully. + + + Add a new country + + + back to country list + + + The country has been deleted successfully. + + + Edit country details + + + Export states to CSV + + + Allows billing + + + Allow billing to customers located in this country. + + + Allows shipping + + + Allow shipping to customers located in this country. + + + Display order + + + The display order for this country. 1 represents the top of the list. + + + Limited to stores + + + Option to limit this country to a certain store. If you have multiple stores, choose one or several from the list. If you don't use this option just leave this field empty. + + + Name + + + The name of the country. + + + Please provide a name. + + + Number of states + + + Numeric ISO code + + + The numeric ISO code for this country. For a complete list of ISO codes go to: http://en.wikipedia.org/wiki/ISO_3166-1_numeric. + + + Published + + + Determines whether this country is published (visible for creation of shipping/billing addresses). + + + Subject to VAT + + + Value indicating whether customers in this country must be charged EU VAT (the European Union Value Added Tax). + + + Three letter ISO code + + + The three letter ISO code for this country. For a complete list of ISO codes go to: http://en.wikipedia.org/wiki/ISO_3166-1_alpha-3. + + + Three letter ISO code should be 3 characters long. + + + Three letter ISO code is required. + + + Two letter ISO code + + + The two letter ISO code for this country. For a complete list of ISO codes go to: http://en.wikipedia.org/wiki/ISO_3166-1_alpha. + + + Two letter ISO code should be 2 characters long. + + + Two letter ISO code is required. + + + Import states from CSV + + + You can download a CSV file with a list of states for other countries on the following page: + + + {0} states have been successfully imported + + + Country info + + + Publish selected + + + States and provinces + + + Add a new state/province + + + The state can't be deleted. It has associated addresses. + + + Edit state/province + + + Abbreviation + + + An abbreviation for this state/province. + + + Display order + + + The display order for this state. 1 represents the top of the list. + + + Name + + + The name of the state. + + + Please provide a name. + + + Published + + + Determines whether this state is published. + + + You need to save the country before you can add states for this country page. + + + Unpublish selected + + + The country has been updated successfully. + + + Currencies + + + The new currency has been added successfully. + + + Add a new currency + + + Apply rate + + + 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. + + + Edit currency details + + + Created on + + + The date/time the currency was created. + + + Currency code + + + The currency code. For a list of currency codes, go to: http://www.iso.org/iso/support/currency_codes_list-1.htm. + + + Currency code must be less than or equal to 5 characters. + + + Please provide a currency code. + + + Auto update enabled + + + Custom formatting + + + Custom formatting to be applied to the currency values. + + + Custom formatting must be less than or equal to 50 characters. + + + Display locale + + + The currency specific culture code. + + + Select locale + + + Currency culture must be valid. + + + Display order + + + The display order of this currency. 1 represents the top of the list. + + + Current exchange rate provider + + + Is primary exchange rate currency + + + Is primary store currency + + + Limited to stores + + + Option to limit this currency to a certain store. If you have multiple stores, choose one or several from the list. If you don't use this option just leave this field empty. + + + Mark as primary exchange rate currency + + + Mark as primary store currency + + + Name + + + The name of the currency. + + + Name must be less than or equal to 50 characters. + + + Please provide a name. + + + Published + + + Determines whether the currency is published. + + + Rate + + + The exchange rate against the primary exchange rate currency. + + + Exchange rate must be greater than 0. + + + Rounding type + + + The rounding type. + + + Get live rates + + + Live currency rates + + + Localization + + + At least one published currency is required + + + Select a currency + + + The currency has been updated successfully. + + + Email accounts + + + The new email account has been added successfully. + + + Add a new email account + + + back to email account list + + + The email account has been deleted successfully. + + + Edit email account details + + + Email display name + + + This is the friendly display name for outgoing emails from your store e.g. 'Your Store Sales Department'. + + + Email address + + + This is the from address for all outgoing emails from your store e.g. 'sales@yourstore.com'. + + + SSL + + + Check to use Secure Sockets Layer (SSL) to encrypt the SMTP connection. + + + Host + + + This is the host name or IP address of your mail server. You can normally find this out from your ISP or web host. + + + Is default email account + + + Mark as default email account + + + Password + + + Change password + + + This is the password you use to authenticate to your mail server. + + + The password has been changed successfully. + + + Port + + + This is the SMTP port of your mail server. This is usually port 25. + + + Send email to + + + The email address to which you want to send your test email. + + + Send test email to ensure that everything is properly configured. + + + Use default credentials + + + Check to use default credentials for the connection. + + + User + + + This is the username you use to authenticate to your mail server. + + + Send Test Email (save settings first by clicking "Save" button) + + + Send test email + + + Email has been successfully sent. + + + The email account has been updated successfully. + + + External authentication + + + back to extenal authentication method list + + + Configure + + + Display order + + + Friendly name + + + Is active + + + System name + + + Languages + + + The new language has been added successfully. + + + Add a new language + + + back to language list + + + The language has been deleted successfully. + + + Edit language details + + + Export resources + + + Default currency + + + This property allows a store owner to specify a default currency for a language. If not specified, then the default currency display order will be used. + + + Display order + + + The display order of this language. 1 represents the top of the list. + + + Flag image + + + Flag image file name + + + The flag image file name. The image should be saved into \images\flags\ directory. + + + Language culture + + + The language specific culture code. + + + Language culture must be valid. + + + Limited to stores + + + Option to limit this language to a certain store. If you have multiple stores, choose one or several from the list. If you don't use this option just leave this field empty. + + + Name + + + The name of the language. + + + Please provide a name. + + + Flag + + + Published + + + Determines whether this language is published and can therefore be selected by visitors to your store. + + + 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. + + + Unique SEO code + + + The unique two letter SEO code. It's used to generate URLs like 'http://www.yourStore.com/en/' when you have more than one published language. 'SEO friendly URLs with multiple languages' option should also be enabled. + + + Two letter SEO code should be 2 characters long. + + + Please provide a unique SEO code. + + + Import resources + + + The resources have been imported. + + + Info + + + At least one published language is required + + + String resources + + + Resource name + + + Please provide a resource name. + + + Value + + + Please provide a resource value. + + + A resource already exists with the name: {0} + + + You need to save the language before you can make or change resources for this language. + + + Resource name + + + Search by a specific resource. + + + Value + + + Search by a specific resource value. + + + The language has been updated successfully. + + + Xml file + + + NOTE: It can take up to several minutes. + + + NOTE: DO NOT click twice. + + + Measures + + + Dimensions + + + The primary dimension can't be deleted. + + + NOTE: if you change your primary dimension, then do not forget to update the appropriate ratios of the units + + + Display order + + + Is primary dimension + + + Mark as primary dimension + + + Name + + + Please provide a name. + + + Ratio to primary dimension + + + System keyword + + + Please provide a system keyword. + + + Weights + + + The primary weight can't be deleted. + + + NOTE: if you change your primary weight, then do not forget to update the appropriate ratios of the units + + + Display order + + + Is primary weight + + + Mark as primary weight + + + Name + + + Please provide a name. + + + Ratio to primary weight + + + System keyword + + + Please provide a system keyword. + + + Payment + + + Payment restrictions + + + Country + + + Please mark the checkbox(es) for the country or countries in which you want the payment method(s) not available + + + Settings have been updated successfully + + + Payment methods + + + back to payment method list + + + Configure + + + Display order + + + Friendly name + + + Is active + + + Logo + + + Recurring support + + + Supports capture + + + Partial refund + + + Refund + + + Void + + + System name + + + Plugins + + + Additional info + + + Manual plugin installation + + + You can download more nopCommerce plugins in our marketplace

    ]]>
    +
    + + Upload the plugin to the /plugins folder in your nopCommerce directory. + + + Restart your application (or click 'Reload list of plugins' button). + + + Scroll down through the list of plugins to find the newly installed plugin. + + + Click on the 'Install' link to install the plugin. + + + Note: If you're running nopCommerce in medium trust, then it's recommended to clear your \Plugins\bin\ directory + + + Edit plugin details + + + Limited to customer roles + + + Choose one or several customer roles i.e. administrators, vendors, guests, who will be able to use this plugin. If you don't need this option just leave this field empty. + + + Author + + + Configure + + + Display order + + + The display order of the plugin. 1 represents the top of the list. + + + Friendly name + + + The friendly name of the plugin. + + + Friendly name is required. + + + Group + + + Install + + + Installing plugin... + + + Installation + + + Installed + + + Is enabled + + + Indicates whether the plugin is enabled/active. + + + Limited to stores + + + Option to limit this plugin to a certain store. If you have multiple stores, choose one or several from the list. If you don't use this option just leave this field empty. + + + Logo + + + System name + + + Uninstall + + + Uninstalling plugin... + + + Version + + + Group + + + Search by a group. + + + Plugin Info + + + The plugin has been installed. + + + Load mode + + + Local plugins + + + Search by a load mode. + + + back to plugin list + + + Configure + + + All plugins and themes + + + Category + + + Search by category. + + + Download + + + Here you can find third-party extensions and themes which are developed by our community and partners.They are also available in our marketplace

    ]]>
    +
    + + Name + + + Search by name. + + + Picture + + + Price + + + Free + + + Search by price. + + + Commercial + + + Supported versions + + + Version + + + Search by version. + + + Reload list of plugins + + + Reloading plugin list... + + + The plugin has been uninstalled. + + + Settings + + + All settings (advanced) + + + Setting name + + + Please provide a setting name. + + + Store + + + All stores + + + Value + + + Name + + + Search by a specific setting name. + + + Value + + + Search by a specific setting value. + + + Blog settings + + + Allow guests to leave comments + + + Check to allow guests to leave comments. + + + Blog comments + + + Common + + + Blog comments must be approved + + + Check if blog comments must be approved by administrator. + + + Blog enabled + + + Check to enable the blog in your store. + + + Notify about new blog comments + + + Check to notify store owner about new blog comments. + + + Number of tags (cloud) + + + The number of blog tags that appear in the tag cloud. + + + Posts page size + + + Set the page size for posts. + + + Display blog RSS feed link in the browser address bar + + + Check to enable the blog RSS feed link in customers browser address bar. + + + Blog comments per store + + + Check to display blog comments written in the current store only. + + + Catalog settings + + + Allow anonymous users to email a friend + + + Check 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. + + + Allow product sorting + + + Check 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. + + + Allow viewing of unpublished product details page + + + Check to allow viewing of unpublished product details page. This way SEO won't be affected by search crawlers when a product is temporary unpublished. Please note that a store owner always has access to unpublished products. + + + Additional sections + + + Catalog pages + + + Compare products + + + Export/Import + + + Performance + + + Product fields + + + Product page + + + Product sorting + + + Product reviews + + + Search + + + Share + + + Tax + + + Tags + + + Cache product prices + + + Check to cache product prices. It can significantly improve performance. But you not should enable it if you use some complex discounts, discount requirement rules, or coupon codes. + + + Category breadcrumb enabled + + + Check to show category breadcrumb. + + + 'Compare Products' enabled + + + Check to allow customers to use the 'Compare Products' option in your store. + + + Discontinued message for unpublished products + + + Check to display "a product has been discontinued" message when viewing details pages of unpublished products. + + + Display tax/shipping info (footer) + + + Check to display tax and shipping info in the footer. This option is used in Germany. + + + Display tax/shipping info (order details page) + + + Check to display tax and shipping info on the order details page. This option is used in Germany. + + + Display tax/shipping info (product boxes) + + + Check to display tax and shipping info in product boxes (catalog pages). This option is used in Germany. + + + Display tax/shipping info (product details page) + + + Check to display tax and shipping info on product details pages. This option is used in Germany. + + + Display tax/shipping info (shopping cart) + + + Check to display tax and shipping info on the shopping cart page. This option is used in Germany. + + + Display tax/shipping info (wishlist) + + + Check to display tax and shipping info on the wishlist page. This option is used in Germany. + + + 'Email a friend' enabled + + + Check to allow customers to use the 'Email a friend' option in your store. + + + Export/Import products with attributes + + + Check if products should be exported/imported with product attributes. + + + Ignore discounts (sitewide) + + + Check to ignore discounts (sitewide). It can significantly improve performance. + + + Ignore featured products (sitewide) + + + Check to ignore featured products (sitewide). It can significantly improve performance. + + + Ignore ACL rules (sitewide) + + + Check to ignore ACL rules configured for entities (sitewide). Recommended to enable this setting if you don't use it. It can significantly improve performance. + + + Ignore "limit per store" rules (sitewide) + + + Check to ignore "limit per store" rules configured for entities (sitewide). Recommended to enable this setting if you have only one store or don't use it. It can significantly improve performance. + + + Include full description in compare products + + + Check 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. + + + Number of manufacturers to display + + + Enter the number of manufacturers to display in manufacturer navigation block. + + + Notify about new product reviews + + + Check to notify store owner about new product reviews. + + + 'New products' page enabled + + + Check to enable the 'New products' page in your store. + + + Number of products on 'New products' page + + + The number of products to display when 'New products' page is enabled. + + + Number of best sellers on home page + + + The number of best sellers on home page to display when 'Show best sellers on home page' option is enabled. + + + Number of product tags (cloud) + + + The number of product tags that appear in the tag cloud. + + + Share button code + + + A page share button code. By default, we're using AddThis service. + + + Product review possible only after product purchasing + + + Check if product can be reviewed only by customer who have already ordered it. + + + Product reviews must be approved + + + Check 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. + + + Number of also purchased products to display + + + The number of products also purchased by other customers to display when 'Products also purchased' option is enabled. + + + Allow customers to select 'Products by tag' page size + + + Whether customers are allowed to select the 'Products by tag' page size from a predefined list of options. + + + 'Products by tag' page. Products per page + + + Set the page size for products on 'Products by tag' page. + + + 'Products by tag' Page Size options (comma separated) + + + Comma separated list of page size options (e.g. 10, 5, 15, 20). First option is the default page size if none are selected. + + + Search autocomplete enabled + + + Check to enabled autocomplete in the search box. + + + Number of 'autocomplete' products to display + + + Change number of visible results shown in autocomplete dropdown when searching. + + + Search term minimum length + + + Specify minimum length of search term. + + + 'Recently viewed products' enabled + + + Check to allow customers to use the 'Recently viewed products' feature in your store. + + + Number of 'Recently viewed products' + + + The number of 'Recently viewed products' to display when 'Recently viewed products' option is enabled. + + + Search page. Allow customers to select page size + + + Search page. Check to allow customers to select the page size from a predefined list of options. + + + Search page. Page size options (comma separated) + + + Search page. Comma separated list of page size options (e.g. 10, 5, 15, 20). First option is the default page size if none are selected. + + + Search page. Products per page + + + Set the page size for products on 'Search' page. + + + Show best sellers on home page + + + Check 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). + + + Include subcategories (number of distinct products) + + + Check to including when showing the number of products besides each category. + + + Show "free shipping" icon + + + Check to show "free shipping" notification for products with this option enabled. + + + Show GTIN + + + Check to show GTIN in public store. + + + Show manufacturer part number + + + Check to show manufacturer part numbers in public store. + + + Show product images in autocomplete box + + + Determines whether product images should be displayed in the autocomplete search box. + + + Include products from subcategories + + + Check if you want a category details page to include products from subcategories. + + + Show SKU on catalog pages + + + Check to show product SKU on catalog pages in public store. + + + Show SKU on product details page + + + Check to show product SKU on the product details page in public store. + + + Reviews per store + + + Check to display reviews written in the current store only (on a product details page). + + + Show product reviews tab on 'My account' page + + + Check to show product reviews tab on 'My account' page. + + + Product reviews page size + + + Set the page size for product reviews e.g. '10' reviews per page. + + + Show a share button + + + Check to show share button on product details page. + + + Display order + + + Is active + + + Name + + + Customer settings + + + 'Accept privacy policy' enabled + + + Ask customers to accept privacy policy during registration. + + + Address form fields + + + 'City' enabled + + + Set if 'City' is enabled. + + + 'City' required + + + Check if 'City' is required. + + + 'Company' enabled + + + Set if 'Company' is enabled. + + + 'Company' required + + + Check if 'Company' is required. + + + 'Country' enabled + + + Set if 'Country' is enabled. + + + You can create and manage the address form fields available during checkout. + + + 'Fax number' enabled + + + Set if 'Fax number' is enabled. + + + 'Fax number' required + + + Check if 'Fax number' is required. + + + 'Phone number' enabled + + + Set if 'Phone number' is enabled. + + + 'Phone number' required + + + Check if 'Phone number' is required. + + + 'State/province' enabled + + + Set if 'State/province' is enabled. + + + 'Street address 2' enabled + + + Set if 'Street address 2' is enabled. + + + 'Street address 2' required + + + Check if 'Street address 2' is required. + + + 'Street address' enabled + + + Set if 'Street address' is enabled. + + + 'Street address' required + + + Check if 'Street address' is required. + + + 'Zip / postal code' enabled + + + Set if 'Zip / postal code' is enabled. + + + 'Zip / postal code' required + + + Check 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. + + + Allow customers to upload avatars + + + A value indicating whether customers are allowed to upload avatars. + + + Allow customers to change their usernames + + + A value indicating whether customers are allowed to change their usernames. + + + Allow viewing of customer profiles + + + A value indicating whether the viewing of customer profiles is allowed. + + + Account + + + Common + + + Default fields + + + External authentication + + + Password and security + + + Profile + + + Time zone + + + Allow customers to check the availability of usernames + + + A value indicating whether customers are allowed to check the availability of usernames (when registering or changing in 'My Account'). + + + 'City' enabled + + + Set if 'City' is enabled. + + + 'City' required + + + Check if 'City' is required. + + + 'Company' enabled + + + Set if 'Company' is enabled. + + + 'Company' required + + + Check if 'Company' is required. + + + 'Country' enabled + + + Set if 'Country' is enabled. + + + 'Country' required + + + Check if 'Country' is required. + + + Customer form fields + + + You can create and manage the customer form fields available during registration below. + + + Customer name format + + + Customer name format. + + + Customer settings + + + 'Date of Birth' enabled + + + Set if 'Date of Birth' is enabled. + + + Customer minimum age + + + Enter minimum allowed age. Leave empty if customers of all ages are allowed. + + + 'Date of Birth' required + + + Check if 'Date of Birth' is required. + + + Default avatar enabled + + + A value indicating whether to display default user avatar. + + + Default password format + + + Choose default password format. Please keep in mind that this setting will be applied only to the newly registered customers. + + + Default store time zone + + + The default store time zone used to display dates. + + + Force entering email twice + + + Force entering email twice during registration. + + + External authentication. Auto register enabled + + + Check to enable auto registration when using external authentication (e.g. using Facebokk or Twitter). + + + 'Fax number' enabled + + + Set if 'Fax number' is enabled. + + + 'Fax number' required + + + Check if 'Fax number' is required. + + + Maximum login failures + + + Maximum login failures to lockout account. Set 0 to disable this feature. + + + Lockout time (login failures) + + + Enter number of minutes to lockout users (for login failures). + + + 'Gender' enabled + + + Set if 'Gender' is enabled. + + + Hide 'Back in stock subscriptions' tab + + + Check 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. + + + Hide newsletter box + + + Check if you want to hide the newsletter subscription box. + + + Newsletter box. Allow to unsubscribe + + + Check if you want to allow customers to display "unsubscribe" option in the newsletter block. + + + 'Newsletter' enabled + + + Set if 'Newsletter' is enabled. + + + Newsletter ticked by default + + + A value indicating whether 'Newsletter' checkbox is ticked by default on the registration page. + + + Notify about new customer registration + + + Notify the store owner when a new customer is registered. + + + Password lifetime + + + Specify number of days for password expiration. Don't forget to check "EnablePasswordLifetime" property on customer role edit page for those roles, who will have to change passwords. + + + Password minimum length + + + Specify password minimum length. + + + Password recovery link. Days valid + + + Enter number of days for password recovery link. Set to 0 if it doesn't expire. + + + 'Phone number' enabled + + + Set if 'Phone number' is enabled. + + + 'Phone number' required + + + Check if 'Phone number' is required. + + + Require registration for downloadable products + + + Require account creation to purchase downloadable products. + + + Show customers' join date + + + A value indicating whether to show customers' join date. + + + Show customers' location + + + A value indicating whether customers' location is shown. + + + 'State/province' enabled + + + Set if 'State/province' is enabled. + + + 'State/province' required + + + Check if 'State/province' is required. + + + Store last visited page + + + When enabled, the last visited page will be stored. When disabled, it can improved performance. + + + 'Street address 2' enabled + + + Set if 'Street address 2' is enabled. + + + 'Street address 2' required + + + Check if 'Street address 2' is required. + + + 'Street address' enabled + + + Set if 'Street address' is enabled. + + + 'Street address' required + + + Check if 'Street address' is required. + + + Unduplicated passwords number + + + Specify the number of customer passwords that mustn't be the same as the previous one, enter 0 if the customer can use the same password time after time. + + + 'Usernames' enabled + + + Check to use usernames for login/registration instead of emails. 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. + + + 'Zip / postal code' enabled + + + Set if 'Zip / postal code' is enabled. + + + 'Zip / postal code' required + + + Check if 'Zip / postal code' is required. + + + Forum settings + + + Active discussions feed count + + + The count of topics to include in the Active Discussion feed. + + + Active discussions feed enabled + + + Enables RSS feed for Active Discussions topics. + + + Active discussions page size + + + Set the page size for active discussions page e.g. '10' results per page. + + + Allow customers to delete posts + + + A value indicating whether customers are allowed to delete posts that they created. + + + Allow customers to edit posts + + + A value indicating whether customers are allowed to edit posts that they created. + + + Allow customers to manage forum subscriptions + + + Check if you want to allow customers to manage forum subscriptions. + + + Allow guests to create posts + + + Set if you want to allow guests to create posts. + + + Allow guests to create topics + + + Set if you want to allow guests to create topics. + + + Allow users to vote for posts + + + Set if you want to allow users to vote for posts. + + + Allow private messages + + + Determines whether to allow private messages. + + + Common + + + Feeds + + + Page sizes + + + Permissions + + + Forum editor + + + Forum editor type. WARNING: not recommended to change in production environment. + + + Forum feed count + + + The count of topics to include in the forum feed. + + + Forum feeds enabled + + + Enables RSS feed for each forum. + + + Forums enabled + + + Check to enable forums. + + + Maximum votes per day + + + Maximum number of votes for user per day. + + + Notify about private messages + + + Indicates whether a customer should be notified by email about new private messages. + + + Posts page size + + + Set the page size for posts in topics e.g. '10' posts per page. + + + Relative date and time formatting + + + Click to enable relative date and time formatting (e.g. 2 hours ago, a month ago). + + + Search results page size + + + Set the page size for search results e.g. '10' results per page. + + + Show alert for PM + + + Shows an alert for new Private Messages. + + + Show customers post count + + + A value indicating whether to show customers post count. + + + Signature enabled + + + Add an opportunity for customers to specify signature. Signature will be displayed below each forum post. + + + Topics page size + + + Set the page size for topics in forums e.g. '10' topics per page. + + + General settings + + + Admin area allowed IP + + + 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 customers to select a theme + + + Check to allow customers to select a store theme. + + + Automatically detect language + + + Check to automatically detect language based on a customer browser settings. + + + Full-Text + + + CAPTCHA + + + Common + + + Localization + + + Pdf + + + Security + + + SEO + + + Sitemap + + + Social media + + + Top menu items + + + Enable canonical URLs + + + The goal of the canonicalization process is to transform a URL into a canonical URL so it is possible to determine if two syntactically different URLs may be equivalent. + + + A CAPTCHA is a program that can tell whether its user is a human or a computer.You've probably seen them — colorful images with distorted text at the bottom ofWeb registration forms. CAPTCHAs are used by many websites to prevent abuse from"bots," or automated programs usually written to generate spam. No computer programcan read distorted text as well as humans can, so bots cannot navigate sites protectedby CAPTCHAs. nopCommerce uses reCAPTCHA.

    ]]>
    +
    + + Captcha is enabled but the appropriate keys are not entered + + + CAPTCHA enabled + + + Check to enable CAPTCHA. + + + Show on apply for vendor account page + + + Check to show CAPTCHA on apply for vendor account page. + + + Show on blog page (comments) + + + Check to show CAPTCHA on blog page when writing a comment. + + + Show on contact us page + + + Check 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. + + + Show on 'email wishlist to a friend' page + + + Check to show CAPTCHA on 'email wishlist to a friend' page. + + + Show on login page + + + Check to show CAPTCHA on login page. + + + Show on news page (comments) + + + Check 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. + + + Show on registration page + + + Check to show CAPTCHA on registration page. + + + Convert non-western chars + + + Check to take out the accent marks in the letters of SEO names while keeping the letter. + + + tag]]> + + + tag(s) here. For example, some custom tag. Or leave empty if ignore this setting.]]> + + + Default meta description + + + The default meta description for pages in your store. You can override this for individual categories / products / manufacturer pages. + + + Default meta keywords + + + The default meta keywords for pages in your store. You can override these for individual categories / products / manufacturer pages. + + + Default store theme + + + You can get more themes on + + + The public store theme. You can download themes from the extensions page at www.nopcommerce.com. + + + Default page title + + + The default title for pages in your store. + + + Disable PDF invoices for pending orders + + + If checked, customers won't be allowed to print PDF invoices for pending orders. + + + Display "Blog" + + + Check if "Blog" menu item should be displayed in the top menu. + + + Display "Forums" + + + Check if "Forums" menu item should be displayed in the top menu. + + + Display "Contact us" + + + Check if "Contact us" menu item should be displayed in the top menu. + + + Display "My account" + + + Check if "My account" menu item should be displayed in the top menu. + + + Display "Home page" + + + Check if "Home page" menu item should be displayed in the top menu. + + + Display "New products" + + + Check if "New products" menu item should be displayed in the top menu. + + + Display "Search" + + + Check if "Search" menu item should be displayed in the top menu. + + + Display EU cookie law warning + + + Make the site EU cookie law compliant. When enabled, new customers will see an appropriate warning box. + + + CSS bundling and minification + + + Enable to combine (bundle) multiple CSS files into a single file. Do not enable if you're running nopCommerce in IIS virtual directory. Note that this functionality requires significant server resources (not recommended to use with cheap shared hosting plans). + + + JavaScript bundling and minification + + + Enable to combine (bundle) multiple JavaScript files into a single file. Note that this functionality requires significant server resources (not recommended to use with cheap shared hosting plans). + + + Enable XSRF protection for admin area + + + Check to enable XSRF protection for admin area. + + + Enable XSRF protection for public store + + + Check to enable XSRF protection for public store. + + + Encryption private key + + + Encryption key changed + + + The encryption private key used for storing sensitive data. + + + The new encryption key is the same as the old one + + + Encryption private key must be 16 characters long + + + Facebook page URL + + + Specify your Facebook page URL. Leave empty if you have no such page. + + + Force SSL for all site pages + + + By default not all site pages are SSL protected. Check to force SSL for the entire site. This setting is useful only when you have SSL enabled on your store details pages. + + + Full-Text is disabled + + + Full-Text is enabled + + + Disable Full-Text + + + Full-Text support has been disabled successfully. + + + Enable Full-Text + + + Full-Text support has been enabled successfully. + + + To prevent a full-text index from becoming bloated, Microsoft SQL Server has a mechanism that discards commonly occurring words that do not help the search. These words are called noise words, or stop words. Noise words are listed in the locale specific noise word files. For example, in the English locale, words such as "a," "and," "is," and "the" are in the English noise word file and are left out of the full-text index since they are empirically known to be useless to a search. Please contact your SQL Server administrator to get more information about it. + + + Full-Text is not supported by your database + + + Search mode + + + Choose Full-Text search mode. + + + Full-Text is supported by your database + + + Generate product META description + + + When enabled, product META descriptions will be automatically generated (if not specified on the product details page) based on product short description. + + + Google+ page URL + + + Specify your Google+ page URL. Leave empty if you have no such page. + + + Enable honeypot + + + Check to enable honeypot technique for registration page. + + + Invoice footer text (left column) + + + Enter the text that will appear at the bottom of generated invoices (left column). + + + Invoice footer text (right column) + + + Enter the text that will appear at the bottom of generated invoices (right column). + + + Load all locale resources on startup + + + When enabled, all locale resources will be loaded on application startup. The application start will be slower, but then all pages could be opened much faster. + + + It seems that you use Redis server for caching, keep in mind that enabling this setting create a lot of traffic between the Redis server and the application because of the large number of locales. + + + Load all localized properties on startup + + + When enabled, all localized properties (such as localized product properties) will be loaded on application startup. The application start will be slower, but then all pages could be opened much faster. It's used only when you have two or more languages enabled. Not recommended to enable when you have a large catalog (several thousand localized entities). + + + Load all search engine friendly names on startup + + + When enabled, all slugs (search engine friendly names) will be loaded on application startup. The application start will be slower, but then all pages could be opened much faster. Not recommended to enable when you have a large catalog (several thousand entities). + + + Logo + + + Upload your store logo. If not uploaded, then the default one will be used. + + + Open Graph META tags + + + Check to generate Open Graph META tags on the product details page. + + + Page title SEO adjustment + + + Select a page title SEO adjustment. For example, generated page title could be (PAGE NAME | YOURSTORE.COM) instead of (YOURSTORE.COM | PAGE NAME). + + + Page title separator + + + Specify page title separator. + + + Use Letter page size + + + If checked, uses Letter page size for PDF documents. Uses A4 page size if unchecked. + + + PDF logo + + + Image file that will be displayed in PDF order invoices. A small image is recommended. + + + reCAPTCHA private key + + + Enter reCAPTCHA private key (if enabled). + + + reCAPTCHA public key + + + Enter reCAPTCHA public key (if enabled). + + + reCAPTCHA version + + + Select version of the reCAPTCHA. + + + SEO friendly URLs with multiple languages enabled + + + When enabled, your URLs will be http://www.yourStore.com/en/ or http://www.yourStore.com/ru/ (SEO friendly). + + + Sitemap enabled + + + Check to enable sitemap. + + + Sitemap includes categories + + + Check if you want to include categories in sitemap. + + + Sitemap includes manufacturers + + + Check if you want to include manufacturers in sitemap. + + + Sitemap includes products + + + Check if you want to include products in sitemap. + + + Store closed + + + Check to close the store. Uncheck to re-open. + + + Contact us page. 'Subject' field + + + Check to allow a customer to type a subject on the contact us page. + + + Twitter page URL + + + Specify your Twitter page URL. Leave empty if you have no such page. + + + Twitter META tags + + + Check to generate Twitter META tags on the product details page. + + + Use images for language selection + + + Check if you want to use images for language selection. + + + Contact us page. Use system email + + + Check to use your system email as "From" field when sending emails from contact us page. Otherwise, customer email will be used (please note that some email services do not allow it). + + + WWW prefix requirement + + + Choose your store WWW prefix requirement. For example, http://yourStore.com/ could be automatically redirected to http://www.yourStore.com/. + + + YouTube channel URL + + + Specify your YouTube channel URL. Leave empty if you have no such page. + + + Media settings + + + Associated product image size + + + The default size (pixels) for associated product images (part of 'grouped' products). + + + Avatar image size + + + The default size (pixels) for avatar images. + + + Common + + + Other pages + + + Product + + + Cart/Wishlist thumbnail image size + + + The default size (pixels) for product thumbnail images on the shopping cart and wishlist. + + + Category thumbnail image size + + + The default size (pixels) for product thumbnail images on category pages. + + + Default image quality (0 - 100) + + + The image quality to be used for uploaded images. Once changed you have to manually delete already generated thumbs. + + + Picture zoom + + + Check to enable picture zoom on product details page. + + + Import product images using hash + + + Check to use fast HASHBYTES (hash sum) database function to compare pictures when importing products. Please note that this functionality is not supported by some database. + + + Manufacturer thumbnail image size + + + The default size (pixels) for product thumbnail images on manufacturer pages. + + + Maximum image size + + + The maximum image size (longest side) allowed for image uploads. + + + Mini-shopping cart thumbnail image size + + + The default size (pixels) for product thumbnail images in the mini-shopping cart block. + + + Multiple thumb directories + + + Check to enable multiple thumb directories. It can be helpful if your hosting company has some limitations to the number of allowed files per directory. + + + Pictures are stored into... + + + Change + + + database + + + file system + + + A value indicating whether pictures are stored in database or file system. + + + NOTE: Do not forget to backup your database before changing this option + + + Product detail image size + + + The default size (pixels) for product detail images. + + + Product thumbnail image size (catalog) + + + The default size (pixels) for product thumbnail images displayed on category or manufacturer pages. + + + Product thumbnail image size (product page) + + + The default size (pixels) for product thumbnail images displayed on product details page when you have more than one product image. + + + Vendor thumbnail image size + + + The default size (pixels) for vendor thumbnail images. + + + Basic + + + Advanced + + + News settings + + + Allow guests to leave comments + + + Check to allow guests to leave comments. + + + News comments + + + Common + + + News enabled + + + Check to enable the news in your store. + + + Number of items to display + + + The number of news items to display on your home page. + + + News archive page size + + + A number of news displayed on one page. + + + News comments must be approved + + + Check if news comments must be approved by administrator. + + + Notify about new news comments + + + Check 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. + + + News comments per store + + + Check to display news comments written in the current store only. + + + Show on home page + + + Check to display your news items on your store home page. + + + Order settings + + + Activate gift cards after completing of an order + + + Check to activate related gift cards when an order is completed. + + + Anonymous checkout allowed + + + Check to enable anonymous checkout (customers are not required to login/register when purchasing products). + + + Attach PDF invoice ("order completed" email) + + + Check to attach PDF invoice to the "order completed" email sent to a customer. + + + Attach PDF invoice ("order paid" email) + + + Check to attach PDF invoice to the "order paid" email sent to a customer. + + + Attach PDF invoice ("order placed" email) + + + Check to attach PDF invoice to the "order placed" email sent to a customer. + + + Assign Invoice ID from Task + + + Assign invoice id using background task. This assures that the same invoice id isn't used more than ones and that there are no wholes in the number series. + + + Auto update order totals + + + Check to automatically update order totals on editing an order in admin area. IMPORANT: currently this functionality is in BETA testing status. + + + Checkout + + + Common + + + Gift cards + + + Order totals + + + Pdf invoice + + + Complete order when delivered + + + Check if an order status should be set to "Complete" only when its shipping status is "Delivered". Otherwise, "Shipped" status will be enough. + + + Order number mask + + + {DD} - day of order creation date + + + {ID} -Order identifier + + + {MM} - month of order creation date + + + {YY} - last two digits of year of order creation date + + + {YYYY} - year of order creation date + + + Order number mask, for creating custom order number. For example, RE-{YYYY}-{MM}. Leave this field empty if you don't want to use custom order numbers. + + + Deactivate gift cards after cancelling of an order + + + Check to deactivate related gift cards when an order is cancelled. + + + Deactivate gift cards after deleting of an order + + + Check to deactivate related gift cards when an order is deleted. + + + Disable "Billing address" step + + + Check to disable "Billing address" step during checkout. Billing address will be pre-filled and saved using the default registration data (this option cannot be used with guest checkout enabled). Also ensure that appropriate address fields that cannot be pre-filled are not required (or disabled). + + + Disable "Order completed" page + + + When disabled, customers will be automatically redirected to the order details page. + + + Export orders with products + + + Check if orders should be exported with products. + + + Is re-order allowed + + + Check if you want to allow customers to make re-orders. + + + Min order sub-total amount + + + Min order total amount. + + + Calculate 'Min order sub-total amount' including tax + + + Check to calculate 'Min order sub-total amount' value including tax; otherwise excluding tax. + + + Min order total amount + + + Enter minimum order total amount. + + + Number of days that the return request is available + + + Set a certain number of days that the Return Request Link will be available in the customer area. For example, if the store owner allows returns within 30 days of purchase, they would set this to 30. Logged in customers, viewing orders in My Account, would then not see Return Request buttons for orders completed more than thirty days ago. + + + Order totals on payment info tab + + + Check to display a product list and order totals on the payment info tab (one-page checkout). + + + Use one page checkout + + + One page checkout is a single web page your customers can use to buy a products/service from you. + + + Order ID + + + 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. + + + Order settings + + + Return request actions + + + The new return request action has been added successfully. + + + Add new return request action + + + back to return request action list + + + The return request action has been deleted successfully. + + + Display order + + + The return request action display order. 1 represents the first item in the list. + + + Edit return request action details + + + List of actions a customer will be able to choose when submitting a return request. + + + Please provide a name. + + + Name + + + The return request action name. + + + The return request action has been updated successfully. + + + Return request number mask + + + Return request number mask. For example, RMA-{ID}-{YYYY}-{MM}-{DD}. + + + {DD} - day of return request creation date + + + {ID} - Return request identifier + + + {MM} - month of return request creation date + + + {YYYY} - year of return request creation date + + + {YY} - last two digits of year of return request creation date + + + Return request reasons + + + The new return request reason has been added successfully. + + + Add new return request reason + + + back to return request reason list + + + The return request reason has been deleted successfully. + + + Display order + + + The return request reason display order. 1 represents the first item in the list. + + + Edit return request reason details + + + List of reasons a customer will be able to choose when submitting a return request. + + + Please provide a name. + + + Name + + + The return request reason name. + + + The return request reason has been updated successfully. + + + Allow file uploads + + + Check if you want to allow customers to upload files when submitting return requests. + + + The returns system will allow your customers to request a return on items they've purchased. These are also known as RMA requests. + + + NOTE: This option is available for completed orders. + + + Enable Returns System + + + Check if you want to allow customers to submit return requests for items they've previously purchased. + + + Return request settings + + + Terms of service (confirm order page) + + + Require customers to accept or decline terms of service before processing the order (on the confirm order page). + + + Terms of service (shopping cart page) + + + Require customers to accept or decline terms of service before processing the order (on the shopping cart page). + + + Last used Invoice No. + + + Invoice ID counter. This is useful if you want your invoices to start from certain number. This only affects invoices henceforward. The value must be greater than the current maximum invoice ID. + + + Year of last invoice + + + Year of last invoice. If actual year is different from this setting, then InvoiceIdent starts from one. + + + Customer roles + + + Additional shipping charge + + + Admin comment + + + Allow back in stock subscriptions + + + Allow customer reviews + + + Allowed quantities + + + Allow only existing attribute combinations + + + Available end date + + + Available for pre-order + + + Available start date + + + Backorders + + + General information + + + Advanced product types + + + Advanced product types + + + Price + + + Tabs and display options + + + Shipping + + + Access control list + + + Inventory + + + Mappings + + + Linked products + + + Settings + + + Call for price + + + Created on + + + Cross-sells products + + + Customer enters price + + + Delivery date + + + Dimensions + + + Disable buy button + + + Disable wishlist button + + + Discounts + + + Display order + + + Display availability + + + Display stock qty + + + Downloadable product + + + Free shipping + + + GTIN (global trade item number) + + + ID + + + Is gift card + + + Is rental + + + Low stock activity + + + Manufacturer part number + + + Manufacturers + + + Mark as new + + + Mark as new. End date + + + Mark as new. Start date + + + Maximum cart qty + + + Minimum cart qty + + + Minimum stock qty + + + Check fields you want to see on the product details page in the "basic" mode. + + + Settings + + + Notify for qty below + + + Not returnable + + + Old price + + + One column product page + + + PAngV (base price) enabled + + + Product attributes + + + Product availability range + + + Product cost + + + Product tags + + + Product template + + + Product type + + + Published + + + Purchased with orders + + + Recurring product + + + Related products + + + Require other products + + + SEO + + + Ship separately + + + Show on home page + + + Specification attributes + + + Stock quantity history + + + Stores + + + Telecommunications, broadcasting and electronic services + + + Tier prices + + + Vendor + + + Visible individually + + + Updated on + + + Multiple warehouses + + + Warehouses + + + Weight + + + Reward point(s) + + + Reward points + + + Common + + + The Reward Points Program allows customers to earn points for certain actions they take on the site. Points are awarded based on making purchases and customer actions such as registration. + + + Display how much will be earned + + + Check to display how much point will be earned before checkout. + + + Earning Reward Points + + + Each + + + spent will earn + + + reward points + + + Enabled + + + Check if you want to enable the Reward Points Program. + + + Exchange rate + + + Specify reward points exchange rate. + + + 1 reward point = + + + Minimum reward points to use + + + Customers won't be able to use reward points before they have X amount of points. Set to 0 if you do not want to use this setting. + + + Page size + + + Set the page size for history of reward points on 'My account' page. + + + Points accumulated for all stores + + + Check to accumulate all reward points in one balance for all stores so they can be used in any store. Otherwise, each store has its own rewards points and they can only be used in that store. WARNING: not recommended to change in production environment with several stores already created. + + + Points for purchases + + + Specify number of points awarded for purchases. + + + Activate points immediately + + + Activates bonus points immediately after their calculation + + + Reward points activation + + + Specify how many days (hours) must elapse before earned points become active. Points earned by purchase cannot be redeemed until activated. For example, you may set the days before the points become available to 7. In this case, the points earned will be available for spending 7 days after the order gets chosen awarded status. + + + Points for registration + + + Specify number of points awarded for registration. + + + Specify if earned reward points are taxable + + + If Earned reward points are taxable, then OrderAmount and tax will be reduced (like discounts do). Otherwise payment amount is reduced like with gift cards. + + + Include shipping + + + Specify if shipping amount should be included in reward points calculation. + + + Include payment method fee + + + Specify if payment method additional fee should be included in reward points calculation. + + + Exclude gift card(s) + + + Specify if gift cards should be excluded in reward points calculation. This setting allows to earn reward points, although order was paid or partially paid with giftcards. + + + Exclude purchased reward points + + + Specify if purchased points should be excluded in reward points calculation. This setting allows to earn reward points, although order total is reduced or zero. + + + Earn points only when using purchased points + + + Specify if reward points can only be earned when using purchased reward points for payment. With this earning of reward points is connected to the use of purchased points. + + + Shipping settings + + + "Pick Up in Store" enabled + + + A value indicating whether "Pick Up in Store" option is enabled during checkout. Please ensure that you have at least one active pickup point provider. + + + Checkout + + + Common + + + Notifications + + + Bypass shipping method page if there's only one + + + Check to bypass a shipping method page during checkout if there's only one shipping method available. + + + Consider associated products dimensions and weight + + + Check to consider associated products dimensions and weight on shipping, uncheck for example if the main product already includes them. + + + Display pickup points on the map + + + Check to display pickup points on the map. + + + Display shipment events (customers) + + + Check if you want your customers to see shipment events on their shipment details pages (if supported by your shipping rate computation method). + + + Display shipment events (store owner) + + + Check if you want a store owner to see shipment events on the shipment details pages of admin area (if supported by your shipping rate computation method). + + + Estimate shipping enabled + + + Check 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. + + + Calculate 'X' including tax + + + Check to calculate 'X' value including tax; otherwise excluding tax. + + + Value of 'X' + + + All orders with a total greater than the value of 'X' will qualify for free shipping. + + + Google maps API key + + + Specify Google maps API key. + + + Hide shipping total if shipping not required + + + Check if you want Hide 'Shipping total' label if shipping not required. + + + Notify customer about shipping from multiple locations + + + Check if you want customers to be notified when shipping from multiple locations. + + + Ship to the same address + + + Check to display "ship to the same address" option during checkout ("billing address" step). In this case "shipping address" with appropriate options (e.g. pick up in store) will be skipped. Also note that all billing countries should support shipping ("Allow shipping" checkbox ticked). + + + Shipping origin + + + Use warehouse location + + + Check to use warehouse location when requesting shipping rates. This is useful when you ship from multiple warehouses. + + + Shopping cart settings + + + Allow guests to email their wishlists + + + Check to allow guests to email their wishlists to friends. + + + Allow cart item editing + + + Check to allow customers to edit items already placed in the cart or wishlist. It could be useful when your products have attributes or any other fields entered by a customer. + + + Allow 'out of stock' items to be added to wishlist + + + Check to allow 'out of stock' products to be added to wishlist. + + + Common + + + Mini shopping cart + + + Wishlist + + + Carts shared between storest + + + Determines whether shopping carts (and wishlist) are shared between stores (in multi-store environment). + + + Number of 'Cross-Sells' + + + The number of 'Cross-Sells' to display on shopping cart page; 0 if you don't want to load cross-sells. + + + Display cart after adding product + + + If checked, a customer will be taken to the Shopping Cart page immediately after adding a product to their cart. If unchecked, a customer will stay on the same page that they are adding the product to the cart from. + + + Display wishlist after adding product + + + If checked, a customer will be taken to the Wishlist page immediately after adding a product to their wishlist. If unchecked, a customer will stay on the same page that they are adding the product to the wishlist from. + + + Allow customers to email their wishlists + + + Check to allow customers to email their wishlists to friends. + + + Maximum shopping cart items + + + Maximum number of distinct products allowed in a shopping cart. + + + Maximum wishlist items + + + Maximum number of distinct products allowed in a wishlist. + + + Show mini-shopping cart + + + Check to enable mini-shopping cart. + + + Number of products in mini-shopping cart + + + Specify the maximum number of products which can be displayed in the mini-shopping cart block. + + + 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. + + + Show discount box + + + Check if you want the discount coupon box to be displayed on shopping cart page. + + + Show gift card box + + + Check if you want the gift card coupon box to be displayed on shopping cart page. + + + Show product images in mini-shopping cart + + + Determines whether product images should be displayed in the mini-shopping cart block. + + + Show product images on cart + + + Determines whether product images should be displayed in your store shopping cart. + + + Show product images on wishlist + + + Determines whether product images should be displayed on customer wishlists. + + + Multi-store configuration for + + + All stores + + + Check/uncheck all + + + (check boxes if you want to set a custom value for this shop). + + + Tax settings + + + Allow customers to select tax display type + + + A value indicating whether customers are allowed to select tax display type. + + + Common + + + Payment + + + Shipping + + + Tax dispaying + + + VAT + + + Default tax address (used for tax calculation) + + + Default tax category + + + Select default tax category for products. + + + Display all applied tax rates + + + A value indicating whether each tax rate should be displayed on a separate line (shopping cart page). + + + Display tax suffix + + + A value indicating whether to display tax suffix (incl tax/excl tax). + + + Allow VAT exemption + + + Check if this store will exempt eligible VAT-registered customers from VAT. + + + Assume VAT always valid + + + Check to skip VAT validation. Entered VAT numbers will always be valid. It will be a client's responsibility to provide the correct VAT number. + + + 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. + + + EU VAT enabled + + + Check to enable EU VAT (the European Union Value Added Tax). + + + Your shop country + + + Select your shop country for VAT calculation. + + + 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). + + + Force tax exclusion from order subtotal + + + Check to always exclude tax from order subtotal (no matter of selected tax dispay type). This setting effects only pages where order totals are displayed. + + + Hide tax in order summary + + + A value indicating whether to hide tax in order summary when prices are shown tax inclusive. + + + Hide zero tax + + + A value indicating whether to hide zero tax in order summary. + + + Payment method additional fee includes tax + + + A value indicating whether payment method additional fee includes tax. + + + Payment method additional fee is taxable + + + A value indicating whether payment method additional fee is taxable. + + + Payment method additional fee tax category + + + Select tax category used for payment method additional fee tax calculation. + + + Prices include tax + + + A value indicating whether entered prices include tax. + + + Shipping is taxable + + + A value indicating whether shipping is taxable. + + + Shipping price includes tax + + + A value indicating whether shipping price includes tax. + + + Shipping tax category + + + Select tax category used for shipping tax calculation. + + + Tax based on + + + Tax based on. + + + Tax based on pickup point address + + + A value indicating whether to use pickup point address (when pickup point is chosen) for tax calculation. + + + [None] + + + Tax display type + + + Tax display type. + + + Vendor settings + + + Allow customers to apply for vendor account + + + Check to allow customers users to fill a form to become a new vendor. Then a store owner will have to manually approve it. + + + Allow customers to contact vendors + + + Check to allow customers to contact vendors. + + + Allow search by vendor + + + Check to allow customers to search by vendor on advanced search page. + + + Allow vendors to edit info + + + Check to allow vendors to edit information about themselves (in public store). Please note that localizable properties (name, description) are not supported in case if you have multiple languages (only standard values can be edited in this case). + + + Allow vendors to import products + + + Check if vendors are allowed to import products. + + + Catalog + + + Common + + + Maximum number of products + + + Sets a maximum number of products per vendor. + + + Notify about vendor information changes + + + Check to notify a store owner about vendor information changes. + + + Show vendor on product details page + + + Check to display a vendor name on the product details page (if associated). + + + Number of vendors to display + + + Enter the number of vendors to display in vendor navigation block. + + + Shipping + + + Dates and ranges + + + Delivery dates + + + The new delivery date has been added successfully. + + + Add a new delivery date + + + back to delivery date list + + + The delivery date has been deleted successfully. + + + Edit delivery date details + + + Display order + + + The display order of this delivery date. 1 represents the top of the list. + + + Name + + + Enter delivery date name. + + + Please provide a name. + + + List of delivery dates which will be available for choice in product details. + + + The delivery date has been updated successfully. + + + Shipping methods + + + The new shipping method has been added successfully. + + + Add a new shipping method + + + back to shipping method list + + + The shipping method has been deleted successfully. + + + Shipping methods used by offline shipping providers. For example, "Manual (Fixed or By Weight)". + + + Edit shipping method details + + + Description + + + Enter shipping method description. + + + Display order + + + The display order of this shipping method. 1 represents the top of the list. + + + Name + + + Enter shipping method name. + + + Please provide a name. + + + Manage shipping methods + + + The shipping method has been updated successfully. + + + Pickup point providers + + + back to pickup point provider list + + + Configure + + + Display order + + + Friendly name + + + Is active + + + Logo + + + System name + + + Pickup points + + + Product availability ranges + + + The new product availability range has been added successfully. + + + Add a new product availability range + + + back to product availability range list + + + The product availability range has been deleted successfully. + + + Edit product availability range details + + + Display order + + + The display order of this product availability range. 1 represents the top of the list. + + + Name + + + Enter product availability range name. + + + Please provide a name. + + + List of availability ranges which will be available for choice in product details. + + + The product availability range has been updated successfully. + + + Shipping providers + + + back to shipping rate computation method list + + + Configure + + + Display order + + + Friendly name + + + Is active + + + Logo + + + System name + + + Shipping rate computation methods (providers) + + + Shipping method restrictions + + + Country + + + Please mark the checkbox(es) for the country or countries in which you want the shipping method(s) not available + + + Shipping method restrictions + + + The settings have been updated successfully. + + + Warehouses + + + The new warehouse has been added successfully. + + + Add a new warehouse + + + back to warehouse list + + + The warehouse has been deleted successfully. + + + Edit warehouse details + + + Address + + + Name + + + Enter a warehouse name. + + + Please provide a name. + + + Admin comment + + + Admin comment. For internal use. + + + The warehouse has been updated successfully. + + + SMS providers + + + back to SMS provider list + + + Configure + + + Friendly name + + + Is active + + + System name + + + Stores + + + The new store has been added successfully. + + + Add a new store + + + back to store list + + + The store has been deleted successfully. + + + Edit store details + + + Company address + + + Enter your company address. + + + Company name + + + Enter your company name. + + + Company phone number + + + Enter your company phone number. + + + Company VAT + + + Enter your company VAT (the European Union Value Added Tax). + + + Default language + + + This property allows a store owner to specify a default language for a store. If not specified, then the default language display order will be used. + + + Display order + + + The display order for this store. 1 represents the top of the list. + + + HOST values + + + The comma separated list of possible HTTP_HOST values (for example, "yourstore.com,www.yourstore.com"). This property is required only when you run a multi-store solution to determine the current store. + + + Store name + + + Enter the name of your store e.g. Your Store. + + + Please provide a name. + + + Secure URL + + + The secure URL of your store e.g. https://www.yourstore.com/ or http://sharedssl.yourstore.com/. Leave it empty if you want nopCommerce to detect secure URL automatically. + + + SSL enabled + + + Check if your store will be SSL secured. + + + WARNING: Do not enable it until you have SSL certificate installed on the server. + + + Store URL + + + The URL of your store e.g. http://www.yourstore.com/. + + + Please provide a store URL. + + + The store has been updated successfully. + + + Tax + + + Tax categories + + + Display order + + + Name + + + Please provide a name. + + + Tax providers + + + back to tax provider list + + + Configure + + + Friendly name + + + Is primary provider + + + Mark as primary provider + + + System name + + + The settings have been updated successfully. + + + Content management + + + Blog + + + Blog posts + + + The new blog post has been added successfully. + + + Add a new blog post + + + back to blog post list + + + The blog post has been deleted successfully. + + + Edit blog post details + + + Allow comments + + + When checked, customers can leave comments about your blog entry. + + + Body + + + The body of this blog entry. + + + Body is required. + + + Body overview + + + Brief overview of blog post. If specified, then it will be used instead of full body on the main blog page. HTML is supported. + + + View comments + + + Created on + + + End date + + + Set the blog post end date in Coordinated Universal Time (UTC). You can also leave it empty. + + + Language + + + The language of this blog entry. A customer will only see the blog entries for their selected language. + + + Limited to stores + + + Option to limit this blog post to a certain store. If you have multiple stores, choose one or several from the list. If you don't use this option just leave this field empty. + + + Meta description + + + Meta description to be added to blog post page header. + + + Meta keywords + + + Meta keywords to be added to blog post page header. + + + Meta title + + + Override the page title. The default is the title of the blog post. + + + Search engine friendly page name + + + Set a search engine friendly page name e.g. 'the-best-blog-post' to make your page URL 'http://www.yourStore.com/the-best-blog-post'. Leave empty to generate it automatically based on the title of the blog post. + + + Start date + + + Set the blog post start date in Coordinated Universal Time (UTC). You can also leave it empty. + + + Tags + + + Tags are keywords that this blog post can also be identified by. Enter a comma separated list of the tags to be associated with this blog post. + + + Dots are not supported by tags. + + + Enter tags ... + + + Title + + + The title of this blog entry. + + + Title is required. + + + Info + + + Store + + + Search by a specific store. + + + The blog post has been updated successfully. + + + Blog comments + + + Approve selected + + + Delete selected + + + Disapprove selected + + + Blog post + + + Comment + + + Created on + + + Customer + + + Is approved + + + Store name + + + Created from + + + The creation from date for the search. + + + Created to + + + The creation to date for the search. + + + Approved + + + Search by a "Approved" property. + + + All + + + Approved only + + + Disapproved only + + + Message + + + Search in comment text. + + + Forums + + + Forum + + + The new forum has been added successfully. + + + back to forum list + + + Add New Forum + + + The forum has been deleted successfully. + + + Edit forum details + + + Created on + + + Description + + + The description of the forum. This is the description that the customer will see. + + + Display Order + + + The display order of the forum. 1 represents the top of the list. + + + Forum Group + + + Choose a forum group. + + + Forum group is required. + + + Name + + + The name of this forum. This is the name that the customer will see. + + + Forum group name is required. + + + The forum has been updated successfully. + + + Forum Group + + + The new forum group has been added successfully. + + + back to forum group list + + + Add New Forum Group + + + The forum group has been deleted successfully. + + + Edit Forum Group Details + + + Created on + + + Display Order + + + The display order of the forum group. 1 represents the top of the list. + + + Name + + + The name of this forum group. This is the name that the customer will see. + + + Forum Group Name is required. + + + The forum group has been updated successfully. + + + Manage Forums + + + Message templates + + + back to return message template list + + + The message template has been copied successfully + + + Copy template + + + The message template has been deleted successfully. + + + Notify about new blog comments in Configuration - Settings - Blog settings.]]> + + + Allow back in stock subscriptions in Product info tab - Inventory section.]]> + + + This message template is used when a customer changes an email address in his account. The customer receives a message to confirm an email address used when changing email address. + + + This message template is used the option Email notification from Registration method dropdown list in Configuration - Settings - Customer settings is selected. The customer receives a message to confirm an email address used when registering. + + + This message template is used when the customer gets a notification about a new order being placed from this account. + + + Show alert for PM in Configuration - Settings - Forum settings.]]> + + + This message template is used when a customer forgets a password needed to log in the account. The customer receives a message with a link for entering a new password. + + + This message template is used to welcome a new customer after registration. + + + This message template is used when a new forum post in certain forum topic is created. The message is received by a store owner. + + + This message template is used when a new forum topic is created. The message is received by a store owner. + + + This message template is used to send a notification to a customer about getting a gift card. + + + Notify about new customer registration in Configuration - Settings - Customer settings.]]> + + + This message template is used to notify a customer about a new return request submitted from his/her account. + + + This message template is used when a new return request is created. The message is received by a store owner. + + + Notify about new news comments in Configuration - Settings - News settings.]]> + + + This message template is used when a customer subscribes to the newsletter to confirm the subscription. + + + Newsletter box. Allow to unsubscribe in Configuration - Settings - Customer settings.]]> + + + Notify admin when a new VAT number is submitted in Configuration - Settings - Tax settings.]]> + + + This message template is used to notify a customer that the certain order was canceled. The order can ba canceled by a customer on the account page or by store owner in Customers - Customers in Orders tab or in Sales - Orders. + + + This message template is used to notify a customer that the certain order was completed. The order gets the order status Complete when it''s paid and delivered, or it can be changed manually to Complete in Sales - Orders. + + + This message template is used to notify a customer that the certain order was paid. The order gets the payment status Paid when the amount was charged, or it can be changed manually Sales - Orders by clicking Mark as paid button in Payment status. + + + This message template is used to notify a store owner that the certain order was paid. The order gets the status Paid when the amount was charged, or it can be changed manually Sales - Orders by clicking Mark as paid button in Payment status. + + + This message template is used to notify a vendor that the certain order was paid. The order gets the status Paid when the amount was charged. + + + This message template is used to notify a customer that the certain order was placed. Orders can be viewed by a customer on the account page. + + + This message template is used to notify a store owner that the certain order was placed. Orders can be viewed by a store owner in Sales - Orders or in Customers menu. + + + This message template is used to notify a vendor that the certain order was placed. Orders can be viewed by a vendor in Sales - Orders. You can allow them to access this part of Admin Area in Configuration - Access control list. + + + This message template is used to notify a customer that the certain order was refunded. The customer can submit to refund the order when the payment status is paid on the account page, or it can be done by a store owner in Sales - Orders by clicking "Refund" button. + + + This message template is used to notify a store owner that the certain order was refunded. The customer can submit to refund the order when the payment status is paid on the account page, or it can be done by a store owner in Sales - Orders by clicking "Refund" button. + + + Notify about new product reviews in Configuration - Settings - Catalog settings.]]> + + + This message template is used to notify a store owner that the certain product attribute combination is getting low stock. You can set up the combination minimum quantity when creating or editing the product in Product attribute tab - Attributes combinations tab in Notify admin for quantity below field. + + + Minimum stock qty field.]]> + + + This message template is used to notify a customer that the certain recurring payment is canceled. Payment can be canceled by a customer in the account page or by a store owner in Sales - Recurring payments in History tab by clicking "Cancel recurring payment" button. + + + This message template is used to notify a store owner that the certain recurring payment is canceled. Payment can be canceled by a customer in the account page or by a store owner in Sales - Recurring payments in History tab by clicking "Cancel recurring payment" button. + + + This message template is used to notify a customer that the certain recurring payment is failed. For example, the amount can''t be charged from the provided credit card. + + + This message template is used to notify a customer that the request status to the certain order is changed. You can set up this option in Sales - Return requests by clicking "Notify customer about status change" button. + + + This message template is used to notify a store owner about a message sent through the contact form. + + + This message template is used to notify a vendor about a message sent through the contact form. + + + This message template is used to send "email a friend" message. + + + Display shipment events (customers).]]> + + + Display shipment events (customers).]]> + + + Allow customers to apply for vendor account.]]> + + + Allow vendors to edit info.]]> + + + This message template is used when a customer wants to share some product from the wishlist with a friend by sending an email. You can set up this option by ticking the checkbox Allow customers to email their wishlists in Configuration - Settings - Shopping cart settings. + + + Edit message template details + + + Allowed message tokens + + + This is a list of the message tokens you can use in your emails. + + + Attached static file + + + Upload a static file you want to attach to each sent email. + + + Has attached file + + + BCC + + + The blind carbon copy (BCC) recipients for this e-mail message. + + + Body + + + The body of your message. + + + Body is required. + + + Delay send + + + The delay before sending message. + + + Email account + + + Standard + + + The email account that will be used to send this message template. + + + Is active + + + Indicating whether the message template is active. + + + Limited to stores + + + Option to limit this template to a certain store. If you have multiple stores, choose one or several from the list. If you don't use this option just leave this field empty. + + + Name + + + The name of this template (read only). + + + Send immediately + + + Send message immediately. + + + Subject + + + The subject of the message (email). TIP - You can include tokens in your subject. + + + Subject is required. + + + Store + + + Search by a specific store. + + + Test template + + + back to template + + + Send + + + Send email to + + + Send test email to ensure that everything is properly configured. + + + Email has been successfully queued. + + + Tokens + + + Please enter tokens you want to be replaced below + + + Enter tokens. + + + Send test email + + + For conditional expressions use the token %if (your conditions ) ... endif% + + + The message template has been updated successfully. + + + News + + + News comments + + + Approve selected + + + Delete selected + + + Disapprove selected + + + Comment text + + + Comment title + + + Created on + + + Customer + + + Is approved + + + News item + + + Store name + + + Created from + + + The creation from date for the search. + + + Created to + + + The creation to date for the search. + + + Approved + + + Search by a "Approved" property. + + + All + + + Approved only + + + Disapproved only + + + Message + + + Search in title and comment text. + + + News items + + + The new news item has been added successfully. + + + Add a new news item + + + back to news item list + + + The news item has been deleted successfully. + + + Edit news item details + + + Allow comments + + + When checked, customers can leave comments about your news entry. + + + View comments + + + Created on + + + End date + + + Set the news item end date in Coordinated Universal Time (UTC). You can also leave it empty. + + + Full description + + + The full description of this news entry. + + + Full description is required. + + + Language + + + The language of this news entry. A customer will only see the news entries for their selected language. + + + Limited to stores + + + Option to limit this news item to a certain store. If you have multiple stores, choose one or several from the list. If you don't use this option just leave this field empty. + + + Meta description + + + Meta description to be added to news page header. + + + Meta keywords + + + Meta keywords to be added to news page header. + + + Meta title + + + Override the page title. The default is the title of the news. + + + Published + + + Determines whether the news item is published (visible) in your store. + + + Search engine friendly page name + + + Set a search engine friendly page name e.g. 'the-best-news' to make your page URL 'http://www.yourStore.com/the-best-news'. Leave empty to generate it automatically based on the title of the news. + + + Short description + + + The short description of this news entry. + + + Short description is required. + + + Start date + + + Set the news item start date in Coordinated Universal Time (UTC). You can also leave it empty. + + + Title + + + The title of this news entry. + + + Title is required. + + + Info + + + Store + + + Search by a specific store. + + + The news item has been updated successfully. + + + Polls + + + The new poll has been added successfully. + + + Add a new poll + + + Poll answers + + + Display order + + + Name + + + Name is required. + + + Number of votes + + + You need to save the poll before you can add answers for this poll page. + + + back to poll list + + + The poll has been deleted successfully. + + + Edit poll details + + + Allow guests to vote + + + Check to allow guests to vote. + + + Display order + + + The display order of this poll. 1 represents the top of the list. + + + End date + + + Set the poll end date in Coordinated Universal Time (UTC). You can also leave it empty. + + + Language + + + The language of this poll. A customer will only see the polls for their selected language. + + + Name + + + The name of this poll. + + + Name is required. + + + Published + + + Determines whether this poll is published (visible) in your store. + + + Show on home page + + + Check if you want to show poll on home page. + + + Start date + + + Set the poll start date in Coordinated Universal Time (UTC). You can also leave it empty. + + + System keyword + + + The system keyword for this poll. For example, you can enter 'LeftColumnPoll' in order to display it in the left column. + + + Poll Info + + + The poll has been updated successfully. + + + Topics (pages) + + + The new topic has been added successfully. + + + Add a new topic + + + back to topic list + + + The topic has been deleted successfully. + + + Edit topic details + + + Accessible when store closed + + + Check to allow customer to view this topic details page when the store is closed. + + + Customer roles + + + Select customer roles for which the topic will be shown. Leave empty if you want this topic to be visible to all users. + + + Body + + + The body of your topic. + + + Display order + + + The topic display order. 1 represents the first item in the list. It's used with properties such as "Include in top menu" or "Include in footer". + + + Include in footer (column 1) + + + Check to include this topic in the footer (column 1). Ensure that your theme supports it. + + + Include in footer (column 2) + + + Check to include this topic in the footer (column 2). Ensure that your theme supports it. + + + Include in footer (column 3) + + + Check to include this topic in the footer (column 3). Ensure that your theme supports it. + + + Include in sitemap + + + Check to include this topic in the sitemap. + + + Include in top menu + + + Check to include this topic in the top menu. + + + Password protected + + + Check if this topic is password protected. + + + Limited to stores + + + Option to limit this topic to a certain store. If you have multiple stores, choose one or several from the list. If you don't use this option just leave this field empty. + + + Meta description + + + Meta description to be added to topic page header. + + + Meta keywords + + + Meta keywords to be added to topic page header. + + + Meta title + + + Override the page title. The default is the name of the topic. + + + Password + + + The password to access the content of this topic. + + + Published + + + Determines whether this topic is published (visible) in your store. + + + Search engine friendly page name + + + Set a search engine friendly page name e.g. 'some-topic-name' to make your page URL 'http://www.yourStore.com/some-topic-name'. Leave empty to generate it automatically based on the title of the topic. + + + Store + + + All stores + + + Choose a store this topic is assigned to. + + + System name + + + System name of this topic. + + + Title + + + The title of your topic. + + + Topic template + + + Choose a topic template. This template defines how this topic will be displayed. + + + URL + + + The URL of this topic. + + + Info + + + Store + + + Search by a specific store. + + + The topic has been updated successfully. + + + Widgets + + + back to widget list + + + Widget zone + + + Choose widget zone. + + + Configure + + + Display order + + + Friendly name + + + Is active + + + System name + + + Current shopping carts + + + Customer + + + Product + + + Quantity + + + Store + + + Total + + + Total items + + + Unit price + + + Updated on + + + Current wishlists + + + Customers + + + Custom customer attributes + + + The new attribute has been added successfully. + + + Add a new customer attribute + + + back to customer attribute list + + + The attribute has been deleted successfully. + + + If the default form fields are not enough for your needs, then you can manage additional customer attributes below. + + + Edit customer attribute details + + + Control type + + + Choose how to display your attribute values. + + + Display order + + + The customer attribute display order. 1 represents the first item in the list. + + + Required + + + When an attribute is required, the customer must choose an appropriate attribute value before they can continue. + + + Name + + + The name of the customer attribute. + + + Please provide a name. + + + Attribute info + + + The attribute has been updated successfully. + + + Attribute values + + + Add a new customer value + + + Edit customer value details + + + Display order + + + The display order of the attribute value. 1 represents the first item in attribute value list. + + + Pre-selected + + + Determines whether this attribute value is pre selected for the customer. + + + Name + + + The name of the customer value. + + + Please provide a name. + + + You need to save the customer attribute before you can add values for this customer attribute page. + + + Customer roles + + + The new customer role has been added successfully. + + + Add a new customer role + + + back to customer role list + + + The customer role has been deleted successfully. + + + Edit customer role details + + + Active + + + System customer roles can't be disabled. + + + Check to make this role active. + + + Enable password lifetime + + + Check to force customers to change their passwords after a specified time. + + + Free shipping + + + Check to allow customers in this role to get free shipping on their orders. + + + Is system role + + + A value indicating whether it's a system role. + + + Name + + + The name of the customer role. + + + Please provide a name. + + + Purchased with product + + + Choose product + + + A customer is added to this customer role once a specified product is purchased (paid). Please note that in case of refund or order cancellation you have to manually remove a customer from this role. + + + You cannot specify "Purchased with product" value for "Registered" customer role + + + Remove + + + System name + + + The system name of system customer roles can't be edited. + + + The system name of the customer role. + + + Tax exempt + + + Check to allow customers in this role to make tax free purchases. + + + The customer role has been updated successfully. + + + Customers + + + Activity log + + + Activity Log Type + + + Comment + + + Created on + + + IP address + + + The IP address for the search. + + + Add the customer to 'Guests' or 'Registered' customer role + + + The new customer has been added successfully. + + + Add a new customer + + + Addresses + + + Add new address + + + The new address has been added successfully. + + + Add a new address + + + back to customer details + + + Edit address + + + The address has been updated successfully. + + + You can't deactivate the last administrator. At least one administrator account should exists. + + + You can't remove the Administrator role. At least one administrator account should exists. + + + You can't delete the last administrator. At least one administrator account should exists. + + + A customer with a vendor associated could not be in "Administrators" role. + + + External authentication + + + Authentication method + + + Email + + + External identifier + + + A list of external authentication identifiers. + + + Back in stock subscriptions + + + Store + + + Product + + + Subscribed on + + + back to customer list + + + A customer in the Vendors role should have a vendor account associated. + + + You're not allowed to change passwords of administrators. Only administrators can do it. + + + You're not allowed to delete administrators. Only administrators can do it. + + + Current shopping cart + + + Current wishlist + + + The customer has been deleted successfully. + + + Edit customer details + + + Active + + + Is this customer's account active. To disable/suspend a customer's account, uncheck this. + + + Admin comment + + + Admin comment. For internal use. + + + Affiliate + + + The affiliate who the customer is linked to. + + + Remove affiliate + + + City + + + The city. + + + City is required + + + Company name + + + The company name. + + + Company is required. + + + Country + + + The country. + + + Created on + + + The date the customer record is created. + + + Customer roles + + + Choose customer roles of this user. + + + No customer roles available. Create at least one customer role before mapping. + + + Date of birth + + + The customer's date of birth. + + + Email + + + The customer's email. + + + Please provide an email. + + + Fax + + + The fax. + + + Fax is required + + + First name + + + The customer's first name. + + + Please provide a first name. + + + Name + + + Gender + + + Female + + + The customer's gender. + + + Male + + + IP Address + + + The last used IP address. + + + Is tax exempt + + + Determines whether the customer is tax exempt. + + + Last activity + + + The last activity date. + + + Last name + + + The customer's last name. + + + Please provide a last name. + + + Last visited page + + + The last visited date (public store). + + + Newsletter + + + Check to subscribe to newsletter. + + + Password + + + Change password + + + The customer's password. + + + Phone + + + The phone. + + + Phone is required + + + Registered in the store + + + Indicating in which store the customer is registered + + + State / province + + + The state / province. + + + Address + + + The address. + + + Street address is required + + + Address 2 + + + The address 2. + + + Street address 2 is required + + + System name + + + The system name of the customer role. + + + Time zone + + + The time zone. + + + Username + + + The customer's username. + + + Please provide a username. + + + VAT number + + + Enter company VAT number. NOTE: Enter VAT number with country code (e.g. GB 111 111 11). + + + Mark VAT number as invalid + + + Mark VAT number as valid + + + status: {0} + + + Manager of vendor + + + Choose a vendor associated to this customer account. When associated this customer will be able to login to the chosen vendor portal and manage his products and orders. + + + Not a vendor + + + Zip / postal code + + + The zip / postal code. + + + Zip / postal code is required + + + The customer cannot be in both 'Guests' and 'Registered' customer roles + + + Place order (impersonate) + + + Place order + + + During the order placement process, you will see almost exactly what the customer would see while browsing this site, with the exception of the header menu (you will see the following text 'Impersonated as customer@email.here - finish session'). Navigate to the products the customer wants and add them to the cart exactly as the customer would, then use the 'Checkout' button to proceed through the usual checkout process. + + + Note: Click 'finish session' link in order to finish this session + + + Customer info + + + Customer roles + + + Filter by customer role. + + + Company + + + Search by company. + + + Date of birth + + + Day + + + Filter by date of birth. Don't select any value to load all records. + + + Month + + + Email + + + Search by a specific email. + + + First name + + + Search by a first name. + + + Last name + + + Search by a last name. + + + Phone + + + Search by a phone number. + + + Username + + + Search by a specific username. + + + Zip code + + + Search by zip code. + + + IP address + + + Search by IP address. + + + A non-admin user cannot impersonate as an administrator + + + Orders + + + Created on + + + Order # + + + Order status + + + Order total + + + Payment status + + + Shipping status + + + Store + + + The password has been changed successfully. + + + Re-send activation message + + + Activation email has been successfully sent. + + + Reward points + + + The points will be activated on {0} + + + Add (reduce) reward points + + + Add (reduce) points + + + Message + + + Enter message (comment). + + + Store + + + Choose a store. It's useful only when you have "Points accumulated for all stores" setting disabled. + + + Value + + + Enter points to add. Negative values are also supported (reduce points). + + + Purchased value + + + Indicate if added reward points are purchased points. Negative values are also supported. + + + Date + + + Message + + + Points + + + Points balance + + + Points purchased + + + Points balance purchased + + + Store + + + Send email + + + The email body + + + Enter the message of email. + + + Planned date of sending + + + The specific send date and time. + + + The email has been queued successfully. + + + Send immediately + + + Send message immediately. + + + The subject + + + Enter the subject of email. + + + Send private message + + + The message + + + Enter the message of PM. + + + The PM has been sent successfully. + + + The subject + + + Enter the subject of PM. + + + Send welcome message + + + Welcome email has been successfully sent. + + + The customer has been updated successfully. + + + Valid Email is required for customer to be in 'Registered' role + + + Note: if you have a vendor associated with this customer, then also ensure it is in "Vendors" customer role. + + + Guest + + + Online customers + + + Customer info + + + IP Address + + + Last activity + + + Last visited page + + + "Store last visited page" setting is disabled + + + Location + + + Customer reports + + + Customers by number of orders + + + Customers by order total + + + End date + + + The end date for the search. + + + Customer + + + Number of orders + + + Order total + + + Order status + + + Search by a specific order status e.g. Complete. + + + Payment status + + + Search by a specific payment status e.g. Paid. + + + Shipping status + + + Search by a specific shipping status e.g. Not yet shipped. + + + Start date + + + The start date for the search. + + + Registered customers + + + Count + + + Period + + + In the last 14 days + + + In the last 7 days + + + In the last month + + + In the last year + + + Run report + + + New customers + + + Month + + + Week + + + Year + + + Dashboard + + + Common statistics + + + Orders + + + Registered customers + + + Pending return requests + + + Low stock products + + + More info + + + Incomplete orders + + + Latest Orders + + + View All Orders + + + Download uploaded file + + + Download URL + + + Download object is saved + + + Remove download + + + Save download + + + Upload file + + + Use download URL + + + Gift cards + + + The new gift card has been added successfully. + + + Add a new gift card + + + back to gift card list + + + The gift card has been deleted successfully. + + + Edit gift card details + + + Initial value + + + The initial value of this gift card. + + + Creation date + + + The date/time the gift card was created/purchased. + + + Order + + + The gift card was purchased with this order. + + + Coupon code + + + Generate code + + + The gift card coupon code (used during checkout). + + + Gift card type + + + The gift card type. + + + Is gift card activated + + + Determines whether gift this card is activated and can be used. + + + Is recipient notified + + + Is recipient notified. + + + Notify recipient + + + Message + + + Enter message. + + + Order + + + The gift card was purchased with this order. + + + Recipient's Email + + + Enter recipient's email. + + + Recipient's Name + + + Enter recipient's name. + + + Remaining amount + + + The remaining amount of this gift card. + + + Sender's Email + + + Enter sender's email. + + + Sender's Name + + + Sender's name is required. + + + Usage history + + + Used + + + Order # + + + Used amount + + + Gift card info + + + Activated + + + All + + + Activated + + + Deactivated + + + Search by a specific gift card status e.g. Activated. + + + Gift card coupon code + + + A gift card coupon code. Leave empty to load all records. + + + Recipient name + + + Search by recipient name. Leave empty to load all records. + + + The gift card has been updated successfully. + + + Clear cache + + + Logout + + + Public store + + + Restart application + + + Restarting the application... + + + Help + + + Community forums + + + Premium support services + + + Help topics + + + Home + + + Search + + + NopCommerce News + + + Hide advertisements + + + Show advertisements + + + Orders + + + Address 1 + + + Address 2 + + + back to order details + + + City + + + Company + + + Country + + + Edit address + + + Email + + + Fax + + + Full name + + + Phone + + + State / province + + + Zip / postal code + + + back to order list + + + Billing info + + + Edit order details + + + Affiliate + + + The affiliate who the order is linked to. + + + Authorization transaction ID + + + Authorization transaction identifier received from your payment gateway. + + + Billing address + + + Billing address info. + + + Capture + + + Capture transaction ID + + + Capture transaction identifier received from your payment gateway. + + + Card CVV2 + + + The card security code of the card used in the transaction. + + + Card expiry month + + + The expiry month of the card used in the transaction. + + + Card expiry year + + + The expiry year of the card used in the transaction. + + + Card name + + + The name on the card used in the transaction. + + + Card number + + + The number of the card used in the transaction. + + + Card type + + + The type of card used in the transaction. + + + Created on + + + The date/time the order was placed/created. + + + Customer + + + The customer who placed this order. + + + Email + + + Email. + + + Customer IP address + + + Customer IP address from where order has been placed. + + + Order # + + + The unique number of this order. + + + Custom values + + + Custom values from the payment method. + + + Reward points base + + + Earned reward points base amount + + + Earned reward points base amount incl. Tax + + + Earned reward points base amount excl. Tax + + + excl tax: + + + incl tax: + + + non taxable: + + + Order shipping + + + Edit the total shipping cost for this order. + + + Order subtotal + + + Edit the subtotal of this order. + + + Order subtotal discount + + + Edit the subtotal discount of this order. + + + Order total + + + Edit the total cost of this order. + + + Order discount + + + Edit the total discount applied to this order. + + + Payment method additional fee + + + Edit the payment method additional fee for this order. + + + Order tax + + + Edit the total tax applied to this order. + + + Order tax rates + + + Edit the tax rates applied to this order ([rate]:[taxtotal] - separated by semicolon). + + + Edit credit card + + + Edit order totals + + + Amount + + + Edit Amount + + + Amount incl. Tax + + + Edit Amount incl. Tax + + + Order discount incl. Tax + + + Edit Order discount incl. Tax + + + Earned reward points base amount incl. Tax + + + Edit earned reward points base amount incl. Tax + + + Earned reward points base amount excl. Tax + + + Edit earned reward points base amount excl. Tax + + + Edit Invoice data. + + + Save Invoice Data. + + + Gift card + + + Applied gift card. + + + Mark as paid + + + Order GUID + + + Internal reference for order. + + + Order shipping + + + The total shipping cost for this order. + + + excl tax + + + incl tax + + + non taxable + + + Order status + + + Cancel order + + + This order is cancelled + + + Change status + + + This option is only for advanced users (not recommended to change manually). All appropriate actions (such as inventory adjustment, sending notification emails, reward points, gift card activation/deactivation, etc) should be done manually in this case. + + + The status of this order. + + + Order subtotal + + + The subtotal of this order. + + + Order subtotal discount + + + The subtotal discount of this order. + + + excl tax + + + incl tax + + + excl tax + + + incl tax + + + Order total + + + The total cost of this order (includes discounts, shipping and tax). + + + Order discount + + + The total discount applied to this order. Manage your discounts from Promotions : Discounts. + + + Partial refund + + + Amount to refund + + + Enter amount to refund. + + + Max amount is {0} {1} + + + Partial refund for order #{0} + + + Partial refund (Offline) + + + Payment method + + + The payment method used for this transaction. You can manage Payment Methods from Configuration : Payment : Payment Methods. + + + Payment method additional fee + + + The payment method additional fee for this order. + + + excl tax + + + incl tax + + + non taxable + + + Payment status + + + The status of the payment. + + + Pickup point address + + + Pickup point address info. + + + View address on Google Maps + + + Profit + + + Profit of this order. + + + Recurring payment + + + This is a recurring order. See the appropriate recurring payment record. + + + Redeemed reward points + + + Redeemed reward points. + + + Redeemed reward points amount + + + Redeemed reward points amount. + + + Redeemed reward points purchased + + + Redeemed purchased reward points. + + + Redeemed reward points amount purchased + + + Redeemed purchased reward points amount. + + + points + + + Refund + + + Refunded amount + + + The total refunded amount. + + + Refund (Offline) + + + Save credit card + + + Save order totals + + + Shipping address + + + Shipping address info. + + + View address on Google Maps + + + Shipping method + + + The customers chosen shipping method for this order. You can manage shipping methods from Configuration : Shipping : Shipping Methods. + + + Shipping status + + + The status of the shipping. + + + Store + + + A store name in which this order was placed. + + + Subscription transaction ID + + + Subscription transaction identifier received from your payment gateway. + + + Order tax + + + Total tax applied to this order. Manage your tax settings from Configuration : Tax. + + + Used discounts + + + A list of used discounts. + + + VAT number + + + Used VAT number (the European Union Value Added Tax). + + + Void + + + Void (Offline) + + + Amount + + + Total order base amount. + + + Amount incl. Tax + + + Total order amount incl. tax + + + Order discount incl. Tax + + + Order discount incl. Tax + + + Invoice No. + + + Invoice No. + + + Invoice Date + + + Invoice Date + + + Info + + + Billing country + + + Filter by order billing country. + + + Billing email address + + + Filter by customer billing email address. + + + Billing last name + + + Filter by customer billing last name. + + + End date + + + The end date for the search. + + + Go directly to order # + + + Go directly to order #. + + + Order notes + + + Search in order notes. Leave empty to load all orders. + + + Order statuses + + + Search by a specific order statuses e.g. Complete. + + + Payment method + + + Search by a specific payment method. + + + Payment statuses + + + Search by a specific payment statuses e.g. Paid. + + + Product + + + Search by a specific product. + + + Shipping statuses + + + Search by a specific shipping statuses e.g. Not yet shipped. + + + Start date + + + The start date for the search. + + + Store + + + Search by a specific store. + + + Vendor + + + Search by a specific vendor. You'll see orders with products from a specified vendor. + + + Warehouse + + + Load orders with products from a specified warehouse. + + + This order item has an associated gift card record. Please delete it first + + + Order notes + + + Add order note + + + Add order note + + + Created on + + + Display to customer + + + A value indicating whether to display this order note to a customer. + + + Attached file + + + (check to upload file) + + + Upload a file attached to this order note. + + + Download + + + No file attached + + + Note + + + Enter this order note message. + + + Invoice (PDF) + + + No orders selected + + + Print PDF invoices + + + Print PDF invoices (all found) + + + Print PDF invoices (selected) + + + Products + + + Add product + + + back to product list + + + back to order details + + + Name + + + Quantity + + + Enter quantity. + + + SKU + + + Total (excl tax) + + + Enter total (excl tax). + + + Total (incl tax) + + + Enter total (incl tax). + + + Add a new product to order #{0} + + + Add product '{0}' to order #{1} + + + Price (excl tax) + + + Enter product price (excl tax). + + + Price (incl tax) + + + Enter product price (incl tax). + + + Do not to forget to update order totals after adding this product. + + + Discount + + + Downloadable product + + + Activate + + + Deactivate + + + Number of downloads: {0} + + + Reset + + + Click to reset download count + + + Excl tax: + + + Incl tax: + + + Gift card(s) + + + License file (optional): + + + Download license file + + + Upload license file + + + Picture + + + Price + + + Product name + + + Quantity + + + [Auto-ship, Every {0} {1}] + + + Return request(s) + + + SKU + + + Total + + + Vendor + + + Profit + + + Shipping + + + Summary + + + Tax + + + Total + + + Shipments + + + The new shipment has been added successfully. + + + Add shipment + + + Add a new shipment to order #{0} + + + Admin comment + + + Admin comment. For internal use. + + + Set admin comment + + + back to order details + + + Order # + + + The order associated to this shipment. + + + The shipment has been deleted successfully. + + + Date delivered + + + Set as delivered + + + Date and time should entered in Coordinated Universal Time (UTC) + + + The date this shipment was delivered. + + + Not yet + + + Set as delivered (selected) + + + Shipment # + + + Shipments + + + City + + + Search by a specific city. + + + Country + + + Search by a specific country. + + + End date + + + The end date (shipment creation date) for the search. + + + Load not shipped + + + Load only not shipped shipments. + + + Start date + + + The start date (shipment creation date) for the search. + + + State / province + + + Search by a specific state. + + + Tracking number + + + Search by a specific tracking number. + + + Warehouse + + + Load shipments with products from a specified warehouse. + + + No products selected + + + No shipments selected + + + Print packaging slip + + + Print packaging slips + + + Print packaging slips (all found) + + + Print packaging slips (selected) + + + Products shipped + + + Item dimensions + + + Item weight + + + Product + + + Qty ordered + + + Qty shipped + + + Qty to ship + + + this product should be shipped separately! + + + Warning: + + + SKU + + + Warehouse + + + {0} ({1} qty in stock, {2} qty reserved, {3} qty planned) + + + No warehouses available. You cannot ship this product. + + + [Reserved - Planned] quantity value of some products are less than specified quantity to be shipped. Are you sure? + + + Shipment status events + + + Country + + + Date + + + Event + + + Location + + + Date shipped + + + Set as shipped + + + Date and time should entered in Coordinated Universal Time (UTC) + + + The date this shipment was shipped. + + + Not yet + + + Set as shipped (selected) + + + Total weight + + + Shipment total weight. + + + Tracking number + + + Set tracking number + + + View tracking info + + + Set a tracking number of the current shipment. + + + View shipment details + + + Shipping info + + + Shipping not required + + + All + + + {0} - {1} of {2} items + + + No items to display + + + Go to the first page + + + items per page + + + Go to the last page + + + More pages + + + Go to the next page + + + of {0} + + + Page + + + Go to the previous page + + + Refresh + + + nopCommerce administration + + + Remove picture + + + Plugins + + + The plugin has been updated successfully. + + + Promotions + + + Campaigns + + + The new campaign has been added successfully. + + + Add a new campaign + + + back to campaign list + + + The campaign has been deleted successfully. + + + Edit campaign + + + Allowed message tokens + + + This is a list of the message tokens you can use in your campaign emails. + + + Body + + + The body of your campaign email. + + + Body is required + + + Created on + + + Limited to customer role + + + Choose a customer role which subscribers will get this email. + + + Planned date of sending + + + Enter a specific date and time to send the campaign. Leave empty to send it immediately. + + + Email account + + + The email account that will be used to send this campaign. + + + Name + + + The name of this campaign. + + + Campaign name is required + + + Limited to store + + + Choose a store which subscribers will get this email. + + + Subject + + + The subject of your campaign email. + + + Subject is required + + + Send test email to + + + The email address to which you want to send your test email. + + + Store + + + Search by a specific store. + + + {0} emails have been successfully queued. + + + Send mass email + + + Send test email + + + Email has been successfully sent. + + + The campaign has been updated successfully. + + + Make sure you've tested the campaign before sending it out to multiple customers. Save your campaign first by clicking "Save" button. + + + Discounts + + + The new discount has been added successfully. + + + Add a new discount + + + Applied to categories + + + You need to save the discount before you can add categories for this discount page. + + + Category + + + Add a new category + + + Applied to manufacturers + + + You need to save the discount before you can add manufacturers for this discount page. + + + Manufacturer + + + Add a new manufacturer + + + Applied to products + + + You need to save the discount before you can add products for this discount page. + + + Product + + + Add a new product + + + back to discount list + + + The discount has been deleted successfully. + + + Edit discount details + + + Assigned to categories + + + A list of categories to which the discount is to be applied. You can assign this discount on a category details page. + + + No categories selected + + + Apply to subcategories + + + Check to apply discount to subcategories of the selected parent. But please note that it can affect performance if you have a lot of nested subcategories. + + + Coupon code + + + The discount coupon code. + + + Discount + + + Discount amount + + + The discount amount to apply to the order/SKUs. + + + Discount limitation + + + Choose the limitation of discount. + + + Discount percentage + + + The percentage discount to apply to order/SKUs. + + + Discount type + + + The type of discount. + + + End date + + + The end of the discount period in Coordinated Universal Time (UTC). + + + Cumulative with other discounts + + + If checked, this discount can be used with other ones simultaneously. Please note that this feature works only for discounts with the same discount type. Right now discounts with distinct types are already cumulative. + + + N times + + + Enter the number of times that the discount is limited to. + + + times + + + Maximum discount amount + + + Maximum allowed discount amount. Leave empty to allow any discount amount. If you're using "Assigned to products" discount type, then it's applied to each product separately. + + + Maximum discounted quantity + + + Maximum product quantity which could be discounted. For example, you can have two products (the same) in the cart but only one of them will be discounted. It can be used for scenarios like "buy 2 get 1 free". Leave empty if any quantity could be discounted. + + + Name + + + The name of the discount. + + + Please provide a name. + + + Requires coupon code + + + If checked, a customer must supply a valid coupon code for the discount to be applied. + + + Start date + + + The start of the discount period in Coordinated Universal Time (UTC). + + + Times used + + + Use percentage + + + Determines whether to apply a percentage discount to the order/SKUs. If not enabled, a set value is discounted. + + + Usage history + + + Used + + + Order # + + + Order total + + + Discount info + + + Coupon code + + + Search by discount coupon code. + + + Discount name + + + Search by discount name. + + + Discount type + + + Search by discount type. + + + Requirements + + + Add a new discount requirement + + + Default requirement group + + + Requirement group is a useful feature for creating discount requirement templates. You can create a requirement group just once and then use it every time you want this limitation to be applied. You can include one requirement group into another one if needed.]]> + + + Discount requirement type + + + Add requirement group + + + You can choose one of the following requirement types, or add a requirement group to use several requirement types simultaneously. + + + Select requirement type + + + Failed to save requirement + + + The group is empty + + + Group name + + + Specify name of the requirement group (e.g. "Permitted customer roles"). + + + Interaction type in this group is + + + Add to group + + + Choose the group you want the requirement group you’re creating to be assigned to + + + Group + + + Requirement + + + Remove requirement + + + Remove group + + + You need to save the discount before you can add requirements for this discount page. + + + Discount requirement saved + + + The discount has been updated successfully. + + + In order to use this functionality you have to disable the following setting: Configuration > Catalog settings > Ignore discounts (sitewide). + + + Newsletter subscribers + + + Active + + + Subscribed on + + + Email + + + Email is required. + + + Store + + + {0} emails have been successfully imported + + + Customer roles + + + Search by a specific customer role. + + + Active + + + All + + + Active + + + Search by a specific status e.g. Active. + + + Not active + + + Email + + + Search by a specific email. + + + Store + + + Search by a specific store. + + + Start date + + + The start date for the search. + + + End date + + + The end date for the search. + + + Recurring payments + + + back to recurring payment list + + + The payment has been cancelled successfully. + + + The payment has been deleted successfully. + + + Recurring payments are used for automatic renewal of consumable merchandise or subscription services. + + + Edit recurring payment details + + + Customer + + + Customer. + + + Cycle length + + + Enter cycle length. + + + Cycle period + + + Select cycle period. + + + Cycles remaining + + + Number of cycles remaining. + + + ID + + + Initial order + + + Initial order of this recurring payment. + + + Is active + + + Determines whether this recurring payment is active. + + + Next payment date + + + Payment type + + + Indicates the payment type (Manual or Automatic). + + + Start date + + + Start date of this recurring payment. + + + Total cycles + + + Enter total cycles. + + + History + + + Cancel recurring payment + + + Created on + + + Created order + + + Last payment failed + + + Next payment date + + + Order status + + + Payment status + + + Process next payment (create a new order) + + + Shipping status + + + Recurring payment info + + + The payment has been processed successfully. + + + The payment has been updated successfully. + + + Return requests + + + back to return request list + + + The return request has been deleted successfully. + + + The Return Request feature (RMA) enables customers to send products back to you. Here you can find all submitted return requests. + + + Edit return request details + + + Date + + + Date when the return request has been submitted. + + + Customer + + + Customer information. + + + Customer comments + + + Entered customer comments. + + + ID + + + Return request identifier. + + + Order # + + + The unique number of the order. + + + Product + + + Product information. + + + Quantity + + + Entered quantity. + + + Reason for return + + + Chosen reason for return. + + + Reason for return is required + + + Requested action + + + Chosen requested action. + + + Requested action is required + + + Staff notes + + + Entered staff comments. + + + Return request status + + + Choose request status. + + + Uploaded file + + + Download + + + File uploaded by customer + + + The customer has been notified successfully. + + + Notify customer about status change + + + Order item is deleted + + + + ID + + + Search by a specific return request identifier. + + + End date + + + The end date for the search. + + + Return status + + + All + + + Search by a specific return request status e.g. Received. + + + Start date + + + The start date for the search. + + + The return request has been updated successfully. + + + Sales + + + Order totals + + + Order Status + + + All time + + + This Month + + + This Week + + + This Year + + + Today + + + Bestsellers + + + Billing country + + + Filter by order billing country. + + + Bestsellers by amount + + + Bestsellers by quantity + + + Category + + + Search in a specific category. + + + End date + + + The end date for the search. + + + Name + + + Total amount (excl tax) + + + Total quantity + + + Manufacturer + + + Search in a specific manufacturer. + + + Order status + + + Search by a specific order status e.g. Complete. + + + Payment status + + + Search by a specific payment status e.g. Paid. + + + Run report + + + Start date + + + The start date for the search. + + + Store + + + Filter report by orders placed in a specific store. + + + Vendor + + + Search by a specific vendor. + + + Country report + + + End date + + + The end date for the search. + + + Country + + + Order total + + + Number of orders + + + Order status + + + Search by a specific order status e.g. Complete. + + + Payment status + + + Search by a specific payment status e.g. Paid. + + + Run report + + + Start date + + + The start date for the search. + + + Incomplete orders + + + Count + + + Item + + + Total + + + Total incomplete orders (pending order status) + + + Total not yet shipped orders + + + Total unpaid orders (pending payment status) + + + view all + + + Products never purchased + + + End date + + + The end date for the search. + + + Name + + + Run report + + + Category + + + Load products only from a specific category. + + + Manufacturer + + + Load products only from a specific manufacturer. + + + Store + + + Load products only from a specific store (available in this store). + + + Vendor + + + Load products only by a specific vendor (owned by this vendor). + + + Start date + + + The start date for the search. + + + Orders + + + Month + + + Week + + + Year + + + Popular search keywords + + + Count + + + Keyword + + + The stock quantity has been increased by canceling the order #{0} + + + The stock quantity of combination has been edited + + + The stock quantity has been edited by copying the product #{0} + + + The stock quantity has been increased by deleting the order #{0} + + + The stock quantity has been increased by deleting an order item from the order #{0} + + + The stock quantity has been increased by deleting a shipment from the order #{0} + + + The stock quantity has been edited + + + Multiple warehouses. + + + The stock quantity has been changed by editing the order #{0} + + + The stock quantity has been changed by importing product + + + Products have been moved {0} {1} by importing product + + + Products have been moved {0} {1} + + + to the {0} + + + from the {0} + + + The stock quantity has been reduced by placing the order #{0} + + + The stock quantity has been reduced when an order item of the order #{0} was shipped + + + System + + + Log + + + back to system log + + + The log has been cleared successfully. + + + Clear log + + + The log entry has been deleted successfully. + + + Delete selected + + + Created on + + + Date/Time the log entry was created. + + + Customer + + + Name of the customer who caused the exception. + + + Full message + + + The details for the log entry. + + + IP address + + + IP address of the machine that caused the exception. + + + Log level + + + The level of log entry. + + + Page URL + + + Originating page of exception. + + + Referrer URL + + + The referrer URL. + + + Short message + + + The log entry message. + + + Created from + + + The creation from date for the search. + + + Created to + + + The creation to date for the search. + + + Log level + + + Select a log level. + + + Message + + + Message. + + + View log entry details + + + Maintenance + + + The backup created + + + Backup file "{0}" deleted + + + Backup now + + + Database backups + + + Database is restored + + + Delete + + + Download + + + File Name + + + File Size + + + Processing database backup... + + + Restore + + + Deleting abandoned shopping carts + + + Created before + + + Delete shopping cart items created before the specified date. + + + {0} items were deleted + + + Deleting old exported files + + + End date + + + The end date for the search. + + + Start date + + + The start date for the search. + + + {0} files were deleted + + + Deleting guest customers + + + End date + + + The end date for the search. + + + Only without shopping cart + + + A value indicating whether we need to find customers without shopping carts/wishlists. If unchecked, then all customers will be found. + + + Start date + + + The start date for the search. + + + {0} customers were deleted + + + Message queue + + + back to message queue + + + Delete all + + + The queued email has been deleted successfully. + + + All queued emails have been deleted successfully. + + + Delete selected + + + Edit message queue item + + + Attached static file + + + The attached static file that will be sent in this email. + + + Attached file path + + + The file path to the attachment. + + + Bcc + + + Bcc address. + + + Body + + + Message body. + + + Cc + + + Cc address. + + + Created on + + + Date/Time message added to queue. + + + Planned date of sending + + + The specific send date and time. + + + Email account + + + The email account that will be used to send this email. + + + From + + + From address. + + + From is required. + + + From name + + + From name. + + + ID + + + Message Priority + + + The priority of the message. + + + Enter priority. + + + ReplyTo + + + ReplyTo address (optional). + + + ReplyTo name + + + ReplyTo name (optional). + + + Send immediately + + + Send message immediately. + + + Sent on + + + The date/time message was sent. + + + Not sent yet + + + Sent attempts + + + The number of times to attempt to send this message. + + + The value must be from 0 to 999999. + + + Enter send tries. + + + Subject + + + Message subject. + + + To + + + To address. + + + To is required. + + + To name + + + To name. + + + End date + + + The end date for the search. + + + From address + + + From address. + + + Go directly to email # + + + Go directly to email #. + + + Load not sent emails only + + + Only load emails into queue that have not yet been sent. + + + Maximum send attempts + + + The maximum number of attempts to send a message. + + + Start date + + + The start date for the search. + + + To address + + + To address. + + + Requeue + + + The queued email has been requeued successfully. + + + The queued email has been updated successfully. + + + Schedule tasks + + + Task period should not exceed 24 days. + + + Enabled + + + Last end date + + + Last start date + + + Last success date + + + Name + + + Name is required + + + Do not forget to restart the application once a task has been modified. + + + Run now + + + Schedule task was run + + + Running the schedule task + + + Seconds (run period) + + + Seconds should be positive + + + Stop on error + + + Search engine friendly page names + + + Delete selected + + + Edit page + + + Entity ID + + + Entity name + + + ID + + + Is active + + + Language + + + Standard + + + Name + + + A name to find. + + + System information + + + ASP.NET info + + + ASP.NET info. + + + Current user time + + + Current user time (based on specified datetime and timezone settings). + + + HTTP_HOST + + + HTTP_HOST is used when you have run a multi-store solution to determine the current store. + + + Is full trust level + + + Is full trust level. + + + Loaded assemblies + + + A list of loaded assemblies. + + + nopCommerce version + + + nopCommerce version. + + + Operating system + + + Operating system. + + + Server local time + + + Server local time. + + + Server time zone + + + Server time zone. + + + Server variables + + + A list of server variables. + + + Coordinated Universal Time (UTC) + + + Coordinated Universal Time (UTC). + + + Templates + + + Category templates + + + Display order + + + Name + + + Name is required + + + View path + + + View path is required + + + Manufacturer templates + + + Display order + + + Name + + + Name is required + + + View path + + + View path is required + + + Product templates + + + Display order + + + Ignored product type IDs (advanced) + + + Name + + + Name is required + + + View path + + + View path is required + + + Topic templates + + + Display order + + + Name + + + Name is required + + + View path + + + View path is required + + + Warnings + + + Default dimension is not set + + + Default dimension. The ratio should be set to 1. + + + Default dimension is set + + + Default weight is not set + + + Default weight. The ratio should be set to 1. + + + Default weight is set + + + All directory permissions are OK + + + The '{0}' account is not granted with Modify permission on folder '{1}'. Please configure these permissions. + + + Primary exchange rate currency is not set + + + Primary exchange rate currency. The rate should be set to 1. + + + Primary exchange rate currency is set + + + All file permissions are OK + + + The '{0}' account is not granted with Modify permission on file '{1}'. Please configure these permissions. + + + '{0}' plugin is incompatible with your nopCommerce version. Delete it or update to the latest version. + + + A custom machine key is not specified (web.config file) + + + A custom machine key is specified (web.config file) + + + You don't have active payment methods + + + Payment methods are OK + + + Performance. You use only one store. Recommended to ignore store limitations (catalog settings) + + + In order to use this functionality you have to disable the following setting: Catalog settings > Ignore "limit per store" rules. + + + Performance. Recommended to ignore ACL rules if you don't use them (catalog settings) + + + In order to use this functionality you have to disable the following setting: Catalog settings > Ignore ACL rules. + + + Primary store currency is not set + + + Primary store currency is set + + + No shipping rate computation methods enabled + + + Only one offline shipping rate computation method is recommended to use + + + Specified store URL matches this store URL + + + Specified store URL ({0}) doesn't match this store URL ({1}) + + + Entered page name already exists, so it will be replaced by '{0}' + + + Vendors + + + The new vendor has been added successfully. + + + Add a new vendor + + + Address (optional) + + + back to vendor list + + + The vendor has been deleted successfully. + + + Edit vendor details + + + Active + + + A value indicating whether the vendor is active. + + + Admin comment + + + Admin comment. For internal use. + + + Allow customers to select page size + + + Whether customers are allowed to select the page size from a predefined list of options. + + + Customers + + + A list of customer accounts which could be used to manage products and orders of this vendor (have access to the vendor portal). You can associate customers to a vendor on a customer details page. If you don't want the vendor to have access to the vendor portal, then do not associate any customer account with it. + + + No customer account associated to this vendor. + + + Description + + + The description of the vendor. + + + Display order + + + Set the vendor's display order. 1 represents the top of the list. + + + Email + + + Enter email. + + + Email is required. + + + Meta description + + + Meta description to be added to vendor page header. + + + Meta keywords + + + Meta keywords to be added to vendor page header. + + + Meta title + + + Override the page title. The default is the name of the vendor. + + + Name + + + The name of the vendor. + + + Please provide a name. + + + Page size + + + Set the page size for products in this vendor e.g. '4' products per page. + + + Page size should be positive. + + + Page Size options (comma separated) + + + Comma separated list of page size options (e.g. 10, 5, 15, 20). First option is the default page size if none are selected. + + + Page Size options should have unique items. + + + Picture + + + The vendor picture. + + + Search engine friendly page name + + + Set a search engine friendly page name e.g. 'the-best-vendor' to make your page URL 'http://www.yourStore.com/the-best-vendor'. Leave empty to generate it automatically based on the name of the vendor. + + + Vendor Info + + + Vendor name + + + A vendor name. + + + The vendor has been updated successfully. + + + Vendor notes + + + Add vendor note + + + Add vendor note + + + Created on + + + Note + + + Enter this vendor note message. + + + You're already subscribed for this product back in stock notification + + + You cannot subscribe. Maximum number of allowed subscriptions is {0} + + + Subscriptions are not allowed for this product + + + Notify me + + + Notify me when available + + + Only registered customers can use this feature + + + Receive an email when this arrives in stock + + + You'll receive a onetime e-mail when this product is available for ordering again. We will not send you any other e-mails or add you to our newsletter; you will only be e-mailed about this product! + + + Unsubscribe + + + Bestsellers + + + Blog + + + Blog archive + + + Comments + + + Comment + + + Enter comment + + + Created on + + + Leave your comment + + + Only registered users can leave comments. + + + Blog comment is successfully added. You will see it after approving by a store administrator. + + + New comment + + + Blog comment is successfully added. + + + Comments ({0:d}) + + + Blog posts of '{0}' '{1}' + + + details + + + RSS + + + Click here to be informed automatically when we add new items to our site. + + + Popular blog tags + + + Blog posts tagged with '{0}' + + + Tags + + + Sort by + + + Display + + + per page + + + View as + + + Grid + + + List + + + Categories + + + Home + + + ({0}) + + + Checkout + + + Billing address + + + Bill to this address + + + Checkout + + + Confirm + + + Confirm order + + + Confirm your order + + + Enter billing address + + + Enter shipping address + + + Please wait several seconds before placing a new order (already placed another order several seconds ago). + + + Minimum order sub-total amount is {0} + + + Minimum order total amount is {0} + + + New Address + + + Next + + + No payment methods available + + + Order number + + + Order summary + + + Or enter new address + + + Payment error: {0} + + + Payment information + + + Payment method + + + Pickup + + + Pick up your items at the store + + + Pickup at {0} + + + Pickup points could not be loaded + + + Select pickup point + + + Click here for order details. + + + Address + + + Cart + + + Complete + + + Confirm + + + Payment + + + Shipping + + + Select billing address + + + Select a billing address from your address book or enter a new address. + + + Select payment method + + + {0} ({1}) + + + Select shipping address + + + Select a shipping address from your address book or enter a new address. + + + Select shipping method + + + {0} ({1}) + + + Shipping address + + + Shipping is not allowed + + + Shipping method + + + Please note that your order will be shipped from multiple locations + + + Shipping options could not be loaded + + + Ship to the same address + + + Ship to this address + + + Submitting order information... + + + Terms of service + + + I agree with the terms of service and I adhere to them unconditionally + + + Please accept the terms of service before the next step. + + + (read) + + + Thank you + + + Continue + + + Use my reward points, {0} reward points ({1}) available + + + Your order has been successfully processed! + + + Add new + + + All + + + Are you sure? + + + Back + + + Cancel + + + Close + + + Config + + + Confirm + + + Continue + + + Day + + + Delete + + + Are you sure you want to delete this item? + + + Are you sure you want to delete "{0}"? + + + Edit + + + Error + + + Cancel + + + Delete + + + Download + + + Drop files here to upload + + + Processing dropped files... + + + Remove + + + Retry + + + Upload a file + + + Hide + + + Home + + + Loading next step... + + + Manage this page + + + Month + + + No + + + No, cancel + + + Notification + + + OK + + + Remove + + + Save + + + Search + + + Show + + + Upload + + + View + + + Wait... + + + Warning + + + The characters didn't match the picture. Please try again. + + + The reCAPTCHA response is invalid or malformed. Please try again. + + + Wrong email + + + Year + + + Yes + + + Contact us + + + Submit + + + Your email + + + Enter your email address. + + + Enter email + + + Enquiry + + + Enter your enquiry. + + + Enter enquiry + + + Your name + + + Enter your name. + + + Enter your name + + + Subject + + + Enter subject. + + + Please enter subject + + + Your enquiry has been successfully sent to the store owner. + + + Contact vendor + + + Submit + + + Your email + + + Enter your email address. + + + Enter email + + + Enquiry + + + Enter your enquiry. + + + Enter enquiry + + + Your name + + + Enter your name. + + + Enter your name + + + Subject + + + Enter subject. + + + Please enter subject + + + Your enquiry has been successfully sent to the vendor. + + + Copyright &copy; {0} {1}. All rights reserved. + + + Currencies + + + Guest + + + Date + + + Download + + + n/a + + + Download license + + + Order # + + + Product + + + I agree + + + There are no downloadable products + + + You have reached maximum number of downloads {0} + + + User agreement + + + Checkboxes + + + Color squares + + + Date picker + + + Drop-down list + + + File upload + + + Multiline textbox + + + Radio button list + + + Read-only checkboxes + + + Textbox + + + Associated to product + + + Simple + + + Allow qty below 0 + + + Allow qty below 0 and notify customer + + + No backorders + + + Manually + + + When order is paid + + + Physical + + + Virtual + + + Disable buy button + + + Nothing + + + Unpublish + + + Don't track inventory + + + Track inventory + + + Track inventory by product attributes + + + Created on + + + Name: A to Z + + + Name: Z to A + + + Position + + + Price: Low to High + + + Price: High to Low + + + Grouped (product with variants) + + + Simple + + + Days + + + Months + + + Weeks + + + Years + + + Days + + + Weeks + + + Months + + + Years + + + Option + + + Custom text + + + Custom HTML text + + + Hyperlink + + + Using CONTAINS and AND with prefix_term + + + Exact match (using CONTAINS with prefix_term) + + + Using CONTAINS and OR with prefix_term + + + Show emails + + + Show first name + + + Show full names + + + Show usernames + + + Clear + + + Encrypted + + + Hashed + + + Days + + + Hours + + + Default rounding + + + Rounding up with 0.05 intervals (0.06 round to 0.10) + + + Rounding down with 0.05 intervals (0.06 round to 0.05) + + + Rounding up with 0.10 intervals (1.05 round to 1.10) + + + Rounding down with 0.10 intervals (1.05 round to 1.00) + + + Rounding with 0.50 intervals + + + Rounding with 1.00 intervals (1.01-1.49 round to 1.00, 1.50-1.99 round to 2.00) + + + Rounding up with 1.00 intervals (1.01–1.99 round to 2.00) + + + N times only + + + N times per customer + + + Unlimited + + + Assigned to categories + + + Assigned to manufacturers + + + Assigned to order subtotal + + + Assigned to order total + + + Assigned to shipping + + + Assigned to products + + + AND + + + OR + + + BBCode editor + + + Html editor + + + Simple textbox + + + Topic titles and post text + + + Post text only + + + Topic titles only + + + Announcement + + + Normal + + + Sticky + + + Debug + + + Error + + + Fatal + + + Information + + + Warning + + + Days + + + Hours + + + Low + + + High + + + Cancelled + + + Complete + + + Pending + + + Processing + + + Cancelled + + + Item(s) refunded + + + Item(s) repaired + + + Pending + + + Received + + + Request rejected + + + Return authorized + + + Authorized + + + Paid + + + Partially refunded + + + Pending + + + Refunded + + + Voided + + + Clear + + + Encrypted + + + Hashed + + + A customer should be approved by administrator + + + Registration is disabled + + + Email validation is required after registration + + + Standard account creation + + + Page name comes after store name + + + Store name comes after page name + + + Doesn't matter + + + Pages should not have WWW prefix + + + Pages should have WWW prefix + + + Delivered + + + Not yet shipped + + + Partially shipped + + + Shipped + + + Shipping not required + + + Billing address + + + Default address + + + Shipping address + + + Excluding tax + + + Including tax + + + Empty + + + Invalid + + + Unknown + + + All + + + Installed + + + Not installed + + + Valid + + + Automatic + + + Manual + + + Not supported + + + Version 1.0 + + + Version 2.0 + + + You cannot browse this site until you accept cookies + + + On 26 May 2011, the rules about cookies on websites changed. This site uses cookies. One or more of the cookies we use is essential for parts of this website to operate and has already been set. You may delete and block all cookies from this site, but parts of the site will not work. To find out more about cookies used on this website and how to delete cookies, see our privacy policy + + + Cookies help us deliver our services. By using our services, you agree to our use of cookies. + + + Learn more + + + Your cookie settings + + + Filter your results + + + Filter by price + + + Over {0} + + + Remove Filter + + + Under {0} + + + Filter by attributes + + + Currently shopping by: + + + Remove Filter + + + or + + + Customer service + + + Follow us + + + Facebook + + + Google+ + + + RSS + + + Twitter + + + YouTube + + + Information + + + My account + + + shipping]]> + + + shipping]]> + + + Active discussions + + + View all + + + Topics with active discussions. + + + {0} - Active Discussions + + + Click here to be informed automatically of active discussion topics + + + Advanced search + + + Announcement + + + Author + + + Forum Group + + + Forum Home Page + + + Forum + + + Home Page + + + Topic + + + By + + + Cancel + + + Delete Post + + + Delete Topic + + + Edit Post + + + Edit Topic + + + Forum + + + Forum's topics with newest posts. + + + {0} - Forum: {1} + + + Forum Name + + + Click here to be informed automatically of new posts and topics in the forum + + + Forums + + + Home + + + In + + + Joined + + + Latest Post + + + Location + + + Moderator + + + Move Topic + + + New Post + + + New Topic + + + No Posts + + + Normal + + + Notify me via email when someone posts in this topic + + + Active discussions + + + Forums + + + Move Topic + + + Edit Post + + + New Post + + + Search Forums + + + Edit Topic + + + New Topic + + + Posted + + + Link to this post + + + Posts + + + Priority + + + PM + + + Quote + + + Replies + + + {0} Replies + + + Reply + + + RSS + + + Search Forums + + + Advanced search + + + Limit results to previous: + + + 1 day + + + 1 month + + + 1 year + + + 2 weeks + + + 3 months + + + 6 months + + + 7 days + + + All results + + + Search in forum: + + + All forums + + + Search keyword: + + + Search within: + + + Topic titles and post text + + + Post text only + + + Topic titles only + + + Search + + + Search forums + + + No posts were found that matched your criteria. + + + Search term minimum length is {0} characters + + + Select the forum you want to move the post to + + + Status + + + Sticky + + + Submit + + + Text cannot be empty + + + Topics + + + {0} Topics + + + [Go to page: {0}] + + + Topic subject cannot be empty + + + Topic Title + + + Total Posts + + + Unwatch Forum + + + Unwatch Topic + + + Votes + + + You already voted for this post + + + You need to log in to vote for post + + + A maximum of {0} votes can be cast per user per day + + + You cannot vote for your own post + + + Views + + + Watch Forum + + + Watch Topic + + + For: {0} + + + ]]> + + + From: {0} + + + ]]> + + + Home page + + + Featured products + + + Languages + + + Manufacturers + + + Manufacturer List + + + View all + + + Picture for category {0} + + + Show products in category {0} + + + Loading... + + + Close (Esc) + + + Previous (Left arrow key) + + + Next (Right arrow key) + + + %curr% of %total% + + + Picture for manufacturer {0} + + + Show products manufactured by {0} + + + Picture of {0} + + + Picture of {0} + + + Show details for {0} + + + Picture of {0} + + + Picture for vendor {0} + + + Show products of vendor {0} + + + Menu + + + Gift card ({0}): + + + Order Total: + + + Payment method additional fee: + + + Product(s) + + + Download + + + Download license + + + Name + + + Price + + + Quantity + + + SKU: {0} + + + Total + + + {0} reward points: + + + {0} purchased reward points: + + + Shipping: + + + Sub-Total: + + + Discount: + + + Tax: + + + Tax {0}%: + + + Discount: + + + Included tax: + + + Amount: + + + Amount incl. tax: + + + News + + + News archive + + + Comments + + + Comment + + + Enter comment text + + + Title + + + Max length of comment title is {0} chars + + + Enter comment title + + + Created on + + + Leave your comment + + + Only registered users can leave comments. + + + News comment is successfully added. You will see it after approving by a store administrator. + + + New comment + + + News comment is successfully added. + + + details + + + RSS + + + Click here to be informed automatically when we add new items to our site. + + + View News Archive + + + Enter your email here... + + + Enter valid email + + + Send + + + Subscribe + + + Unsubscribe + + + Your subscription has been activated. + + + Your subscription already has been deactivated. + + + Your subscription has been deactivated. + + + Sign up for our newsletter + + + Thank you for signing up! A verification email has been sent. We appreciate your interest. + + + A verification email has been sent. Thank you! + + + Newsletter + + + Billing Address + + + Email + + + Fax + + + PDF Invoice + + + Gift card ({0}) + + + Note(s) + + + Created On + + + Download attached file + + + Note + + + Order # + + + Order Date + + + Order information + + + Order Status + + + Order Total + + + Payment + + + Payment Method + + + Payment Status + + + Payment method additional fee + + + Phone + + + Pickup point address + + + Print + + + Product(s) + + + Name + + + Price + + + Quantity + + + SKU + + + Total + + + Start date: {0}. End date: {1}. + + + Re-order + + + Retry payment + + + This order is not yet paid for. To pay now, click the "Retry payment" button. + + + Return Item(s) + + + {0} used reward points + + + {0} used purchased reward points + + + Shipments + + + Date delivered + + + Not yet + + + Email + + + Fax + + + Shipment # + + + Shipment #{0} + + + Order # + + + Phone + + + Pickup point address + + + Shipped product(s) + + + Name + + + Qty shipped + + + SKU + + + Date shipped + + + Not yet + + + Shipping Address + + + Shipping Method + + + Tracking number + + + View details + + + Shipment status events + + + Country + + + Date + + + Event + + + Location + + + Shipping + + + Shipping Address + + + Shipping Method + + + Shipping Status + + + Sub-Total + + + Discount + + + Tax + + + Tax {0}% + + + shipping]]> + + + shipping]]> + + + Discount + + + VAT number + + + Included Tax + + + Amount + + + Amount incl. Tax + + + Order discount incl. Tax + + + Earned reward points base amount incl. Tax + + + Earned reward points base amount excl. Tax + + + Invoice No. + + + Invoice Date + + + Tax % + + + Amount + + + Amount incl. Tax + + + Discount + + + Discount incl. tax + + + Base Amount + + + Tax Amount + + + Page {0} of {1} ({2} total) + + + First + + + First Page + + + Last + + + Last Page + + + Next + + + Next Page + + + Page {0} + + + Previous + + + Previous Page + + + Account + + + Account Activation + + + All product tags + + + Back in stock subscriptions + + + Blog + + + Checkout + + + Compare Products + + + Contact Us + + + Contact Vendor - {0} + + + My product reviews + + + Email validation + + + Forum Subscriptions + + + Login + + + Manufacturers + + + New Products + + + News Archive + + + Order Details + + + Page not found + + + Password Recovery + + + Private Messages + + + Email A Friend + + + Product Reviews + + + Products tagged with '{0}' + + + Profile + + + Recently Viewed Products + + + Register + + + Return Item(s) + + + Search + + + Post a private message + + + Shipment details + + + Shopping Cart + + + Sitemap + + + Store closed + + + Vendor info + + + Vendors + + + Apply for vendor account + + + View the private message + + + Wishlist + + + Email wishlist to a friend + + + Card code + + + Enter card code + + + Wrong card code + + + Cardholder name + + + Enter cardholder name + + + Card number + + + Enter card number + + + Wrong card number + + + Expiration date + + + Expire month is required + + + Expire year is required + + + Select credit card + + + Address: {0} + + + Address 2: {0} + + + Billing Information: + + + Company: {0} + + + Created on + + + Discount: + + + Fax: {0} + + + Gift card ({0}): + + + Name: {0} + + + Note + + + Order# {0} + + + ORDER + + + VatID: {0} + + + Date: {0} + + + Order notes: + + + Created on + + + Note + + + Order total: + + + Payment method: {0} + + + Payment Method Additional Fee: + + + Phone: {0} + + + Pickup point: + + + Product(s) + + + Name + + + Price + + + Qty + + + Total + + + {0} reward points: + + + {0} purchased reward points: + + + Shipping: + + + Shipping Information: + + + Shipping method: {0} + + + SKU + + + Sub-total: + + + Tax: + + + Tax {0}%: + + + VAT number: {0} + + + Tax % + + + Invoice + + + Invoice Date + + + Amount + + + Amount incl. tax + + + Discount + + + Discount incl. tax + + + VAT Amount + + + Amount to pay + + + Continued on next page ... + + + Additional items + + + Invoice discount + + + Currency + + + Page + + + Base Ammount + + + Total Base + + + Address: {0} + + + Address 2: {0} + + + Company: {0} + + + Name: {0} + + + Order #{0} + + + Phone: {0} + + + Product Name + + + Quantity + + + Shipment #{0} + + + Shipping method: {0} + + + SKU + + + Price + + + SKU + + + Stock quantity + + + Weight + + + Access admin area + + + Plugins. Access Web Service + + + Admin area. Allow Customer Impersonation + + + Public store. Display Prices + + + Public store. Enable shopping cart + + + Public store. Enable wishlist + + + Admin area. HTML Editor. Manage pictures + + + Admin area. Manage ACL + + + Admin area. Manage Activity Log + + + Admin area. Manage Affiliates + + + Admin area. Manage Attributes + + + Admin area. Manage Blog + + + Admin area. Manage Campaigns + + + Admin area. Manage Categories + + + Admin area. Manage Countries + + + Admin area. Manage Currencies + + + Admin area. Manage Current Carts + + + Admin area. Manage Customers + + + Admin area. Manage Discounts + + + Admin area. Manage Email Accounts + + + Admin area. Manage External Authentication Methods + + + Admin area. Manage Forums + + + Admin area. Manage Gift Cards + + + Admin area. Manage Languages + + + Admin area. Manage Maintenance + + + Admin area. Manage Manufacturers + + + Admin area. Manage Message Queue + + + Admin area. Manage Message Templates + + + Admin area. Manage News + + + Admin area. Manage Newsletter Subscribers + + + Admin area. Manage Orders + + + Admin area. Manage Payment Methods + + + Admin area. Manage Plugins + + + Admin area. Manage Polls + + + Admin area. Manage Product Reviews + + + Admin area. Manage Products + + + Admin area. Manage Product Tags + + + Admin area. Manage Recurring Payments + + + Admin area. Manage Return Requests + + + Admin area. Manage Schedule Tasks + + + Admin area. Manage Settings + + + Admin area. Manage Shipping Settings + + + Admin area. Manage Stores + + + Admin area. Manage System Log + + + Admin area. Manage Tax Settings + + + Admin area. Manage Topics + + + Admin area. Manage Vendors + + + Admin area. Manage Widgets + + + Admin area. Access order country report. + + + Public store. Allow navigation + + + Only registered users can vote. + + + Please select an answer + + + Community poll + + + {0} vote(s)... + + + Vote + + + {0} ({1} vote(s) - {2}%) + + + Inbox + + + Date + + + Delete selected + + + From + + + Mark selected as unread + + + Subject + + + To + + + Message cannot be empty + + + PM + + + Private Messages + + + Cancel + + + Message: + + + Post a Private Message + + + Send + + + Subject: + + + Subject is required. + + + To: + + + Date + + + Delete selected + + + Subject + + + To + + + Sent Items + + + Subject cannot be empty + + + ({0} Unread) + + + Back to messages + + + Delete + + + From: + + + Reply + + + Subject: + + + To: + + + View Message + + + You have {0} unread message(s) in your Inbox + + + - quantity {0} + + + Customers who bought this item also bought + + + Availability + + + Available in {0} + + + Out of Stock - on backorder and will be dispatched once in stock. + + + Out of stock - on backorder and will be dispatched once in stock ({0}). + + + In stock + + + {0} in stock + + + Out of stock + + + equates to {0} per {1} {2} + + + Home + + + Call for pricing + + + Compare products + + + Add to compare list + + + Clear list + + + Full description + + + Compare products list + + + Name + + + You have no items to compare. + + + Price + + + Short description + + + Compare products + + + Delivery date + + + Details + + + Sorry - this product is no longer available + + + Download sample + + + Email a friend + + + Send email + + + Friend's email + + + Enter friend's email. + + + Enter friend's email + + + Only registered customers can use email a friend feature + + + Personal message + + + Enter personal message (optional). + + + Your message has been sent. + + + Email a friend + + + Your email address + + + Enter your email address. + + + Enter your email + + + Please enter your price + + + The price must be from {0} to {1} + + + {0} excl tax + + + Featured products + + + Free shipping + + + Message + + + Recipient's Email + + + Recipient's Name + + + Your Email + + + Your Name + + + GTIN + + + {0} incl tax + + + Manufacturer + + + Manufacturer part number + + + Manufacturers + + + This product has a minimum quantity of {0} + + + New products + + + RSS + + + Click here to be informed automatically when we add new items to our site. + + + This product is sold out + + + Price + + + Old price + + + {0} per {1} day(s) + + + {0} per {1} week(s) + + + {0} per {1} month(s) + + + {0} per {1} year(s) + + + Rental price + + + shipping]]> + + + shipping]]> + + + Your price + + + From {0} + + + {0} [{1}{2}] + + + per item + + + Enter quantity: + + + The product has been added to your product comparison + + + product comparison]]> + + + The product has been added to your shopping cart + + + shopping cart]]> + + + The product has been added to your wishlist + + + wishlist]]> + + + Qty + + + Recently viewed products + + + Related products + + + Start date + + + End date + + + SKU + + + Products specifications + + + Product tags + + + All product tags + + + ({0}) + + + Popular tags + + + View all + + + Products tagged with '{0}' + + + PRICE BREAKS - The more you buy, the more you save + + + Price + + + Quantity + + + Vendor + + + Please note that this product is excluded from the reward points program. No points will be earned. + + + Add to wishlist + + + Update + + + Country + + + Date Of Birth + + + Full Name + + + Join Date + + + Latest Posts + + + No posts found + + + Profile Info + + + Posted + + + Profile: {0} + + + Statistics + + + Topic + + + Total Posts + + + Comments + + + Your return request has not been submitted because you haven't chosen any items. + + + Product + + + Unit price + + + Qty. to return + + + Return action + + + Return reason + + + Which items do you want to return? + + + Submit return request + + + Your return request has been submitted successfully. + + + order #{1}]]> + + + Upload (any additional document, scan, etc) + + + Why are you returning these items? + + + Reviews + + + Date + + + Existing reviews + + + Rating + + + Bad + + + Excellent + + + Review text + + + Review text is required. + + + Review title + + + Max length of product review title is {0} chars + + + Review title is required. + + + From + + + Only registered customers can set review helpfulness + + + Successfully voted + + + Was this review helpful? + + + You cannot vote for your own review + + + Only registered users can write reviews + + + Add your review + + + Be the first to review this product + + + review(s) + + + Product can be reviewed only after purchasing it + + + Product reviews for + + + A manager responded to this review + + + You will see the product review after approving by a store administrator. + + + Submit review + + + Product review is successfully added. + + + {0} review(s) + + + Write your own review + + + The points will be activated on {0} + + + Your current balance + + + Total + + + Amount + + + Date + + + Message + + + Points + + + Points Purchased + + + Points balance + + + Points balance purchased + + + History + + + Earned promotion for order #{0} + + + Registered as customer + + + Redeemed for order #{0} + + + Reduced promotion for order #{0} + + + Returned back for order #{0} + + + Minimum balance allowed to use is {0} reward points ({1}). + + + There is no balance history yet + + + Purchased with order #{0} + + + Purchased and earned with order #{0} + + + Search + + + Advanced search + + + Search + + + Category + + + Please enter some search keyword + + + Automatically search sub categories + + + Manufacturer + + + No products were found that matched your criteria. + + + Price range + + + From + + + to + + + Search store + + + Search In product descriptions + + + Search keyword + + + Search term minimum length is {0} characters + + + Vendor + + + Shopping cart + + + Add to cart + + + Update + + + Add to wishlist + + + Update + + + Add to compare list + + + Allowed quantities for this product: {0} + + + {0}. {1}. {2} + + + Available in {0} + + + Buying is disabled for this product + + + Product (Id={0}) cannot be loaded + + + Your cart has standard and auto-ship (recurring) items. Only one product type is allowed per order. + + + Your Shopping Cart is empty! + + + {0} [{1}] + + + Your cart has auto-ship (recurring) items with conflicting shipment schedules. Only one auto-ship schedule is allowed per order. + + + Continue shopping + + + Based on your selection, you may be interested in the following items + + + The price must be from {0} to {1} + + + Sorry, you've used this discount already + + + Sorry, this discount cannot be used with gift cards in the cart + + + Sorry, this discount cannot be used with reward points products in the cart + + + Sorry, this offer is expired + + + Sorry, this offer is not started yet + + + Discount Code + + + The coupon code was applied + + + Apply coupon + + + Entered coupon code - {0} + + + Enter your coupon here + + + The coupon code you entered couldn't be applied to your order + + + Estimate shipping + + + Estimate shipping + + + Country + + + {0} ({1}) + + + State / province + + + Enter your destination to get a shipping estimate + + + Zip / postal code + + + Your file successfully uploaded! + + + Gift Cards + + + The gift card code was applied + + + Add gift card + + + You cannot use gift cards with auto-ship (recurring) items. + + + Enter gift card code + + + The coupon code you entered couldn't be applied to your order + + + ({0}) + + + Image + + + Total + + + You save: {0} + + + Discounted qty: {0} + + + The maximum quantity allowed for purchase is {0}. + + + The maximum number of distinct products allowed in the cart is {0}. + + + Maximum file size is {0} KB + + + The maximum number of distinct products allowed in the wishlist is {0}. + + + {0} item(s) + + + There are {0} in your cart. + + + You have no items in your shopping cart. + + + Quantity + + + Unit price + + + Go to cart + + + The minimum quantity allowed for purchase is {0}. + + + Product is not available + + + Out of stock + + + Pre-order + + + Product(s) + + + Product is deleted + + + Product is not published + + + Qty. + + + Your quantity exceeds stock on hand. The maximum quantity that can be added is {0}. + + + Quantity should be positive + + + Enter valid recipient email + + + Enter valid recipient name + + + [Auto-ship, Every {0} {1}] + + + Remove + + + Rent + + + Enter rental end date + + + Enter rental start date + + + Start date: {0}. End date: {1}. + + + Rental start date should be less than end date + + + Rental start date should be the future date + + + This product requires the following product is added to the cart: {0} + + + Please select {0} + + + Enter valid sender email + + + Enter valid sender name + + + SKU + + + shipping]]> + + + shipping]]> + + + {0} : maximum length is {1} chars + + + {0} : minimum length is {1} chars + + + Calculated during checkout + + + Gift Card + + + ({0}) + + + {0} remaining + + + Total to pay + + + Invoice Discount + + + Payment method additional fee + + + {0} used reward points + + + {0} used purchased reward points + + + You will earn (based on {0}) + + + (*) when using purchased points + + + {0} point(s) + + + Shipping + + + ({0}) + + + Not required + + + Sub-Total + + + Discount + + + Tax + + + Included Tax + + + Total Amount + + + Total Amount incl. Tax + + + Tax % + + + Tax {0}% + + + Price + + + Update shopping cart + + + Update qty + + + Wishlist is disabled for this product + + + Sitemap + + + Categories + + + View the sitemap for this website below, with links to each of the pages and brief descriptions of what to find in each section + + + General + + + Manufacturers + + + Products + + + This store is currently closed + + + Please check back in a little while. + + + Select store theme + + + Show prices tax exclusive + + + Show prices tax inclusive + + + Tax display type + + + Enter + + + Please enter password to access this content: + + + Wrong password + + + Vendors + + + Apply for vendor account + + + You already applied for a vendor account. Please register as a new customer in order to apply for one more vendor account. + + + Submit + + + Description + + + Email + + + Enter your email address. + + + Email is required. + + + Vendor name + + + Enter vendor name. + + + Vendor name is required. + + + Picture + + + You can add only picture file + + + Your request has been submitted successfully. We'll contact you soon. + + + Vendor List + + + View all + + + Wishlist + + + Some product(s) from wishlist could not be moved to the cart for some reasons. + + + The wishlist is empty! + + + Email a friend + + + Send email + + + Friend's email + + + Enter friend's email. + + + Enter friend's email + + + Only registered customers can use email a friend feature + + + Personal message + + + Enter personal message (optional). + + + Your message has been sent. + + + Email your wishlist to a friend + + + Your email address + + + Enter your email address. + + + Enter your email + + + ({0}) + + + shipping]]> + + + shipping]]> + + + Update wishlist + + + Wishlist of {0} + + + Your wishlist URL for sharing +
    \ No newline at end of file diff --git a/upgradescripts/3.80-the next version/upgrade.sql b/upgradescripts/3.80-the next version/upgrade.sql index aec8d353091..c22005b560e 100644 --- a/upgradescripts/3.80-the next version/upgrade.sql +++ b/upgradescripts/3.80-the next version/upgrade.sql @@ -1,5829 +1,5894 @@ ---upgrade scripts from nopCommerce 3.80 to next version - ---new locale resources -declare @resources xml ---a resource will be deleted if its value is empty -set @resources=' - - - Included Tax: - - - Amount: - - - Amount incl. Tax: - - - Included Tax - - - Amount - - - Amount incl. Tax - - - Total to pay - - - Invoice Discount - - - - - - Tax % - - - - - - Tax % - - - Invoice - - - Invoice Date - - - Amount - - - Amount incl. tax - - - Discount - - - Discount incl. tax - - - - - - Tax Amount - - - Amount to pay - - - Base Ammount - - - Total Base - - - Continued on next page ... - - - Additional items - - - Invoice discount - - - Currency - - - Page - - - Order No. - - - Orderdate - - - {0} purchased reward points: - - - Invoice No. - - - Invoice Date - - - Amount - - - Total order base amount. - - - Amount incl. Tax - - - Total order amount incl. tax - - - Invoice No. - - - Invoice No. - - - Invoice Date - - - Invoice Date - - - - - - - - - Order discount incl. Tax - - - Order discount incl. Tax - - - non taxable - - - non taxable - - - Amount - - - Amount - - - Amount incl. Tax - - - Amount incl. Tax - - - Earned reward points base amount incl. Tax - - - Edit earned reward points base amount incl. Tax - - - Earned reward points base amount excl. Tax - - - Edit earned reward points base amount excl. Tax - - - - - - - - - Order discount incl. Tax - - - Order discount incl. Tax - - - non taxable: - - - Order discount incl. Tax - - - Earned reward points base amount incl. Tax - - - Earned reward points base amount excl. Tax - - - Edit Invoice data. - - - Save Invoice Data. - - - A new InvoiceId get''s assigned when saving an Id = null - - - Included Tax - - - Amount - - - Amount incl. Tax - - - Invoice No. - - - Invoice Date - - - - - - Tax % - - - Amount - - - Amount incl. Tax - - - Discount - - - Discount incl. tax - - - Base Amount - - - - - - Tax Amount - - - You haven''t written any reviews yet - - - Last used Invoice No. - - - Invoice ID counter. This is useful if you want your invoices to start from certain number. This only affects invoices henceforward. The value must be greater than the current maximum invoice ID. - - - Year of last invoice - - - Year of last invoice. If actual year is different from this setting, then InvoiceIdent starts from one. - - - Force entering email twice during registration. - - - Sets a maximum number of products per vendor. - - - Shipping methods used by offline shipping rate computation methods (e.g. "Fixed Rate Shipping" or "Shipping by weight"). - - - The price of the product. You can manage currency by selecting Configuration > Currencies. - - - Choose customer roles of this user. - - - Check if products should be exported/imported with product attributes. - - - Check to display "ship to the same address" option during checkout ("billing address" step). In this case "shipping address" with appropriate options (e.g. pick up in store) will be skipped. Also note that all billing countries should support shipping ("Allow shipping" checkbox ticked). - - - Task period should not exceed 24 days. - - - Payment restrictions - - - Choose a vendor associated with this product. This can be useful when running a multi-vendor store to keep track of goods associated with vendor. - - - You can download a CSV file with a list of states for other countries on the following page: - - - Enter tags ... - - - [None] - - - Hide shipping total if shipping not required - - - Check if you want Hide ''Shipping total'' label if shipping not required. - - - - - - - - - - - - - - - - - - - - - Client ID - - - Specify client ID. - - - Client secret - - - Specify secret key. - - - Webhook ID - - - Specify webhook ID. - - - Get webhook ID - - - Webhook was not created (see details in the log) - - - [None] - - - Default tax category - - - Select default tax category for products. - - - Added a new address attribute (ID = {0}) - - - Added a new address attribute value (ID = {0}) - - - Added a new affiliate (ID = {0}) - - - Added a new blog post (ID = {0}) - - - Added a new campaign (ID = {0}) - - - Added a new country (ID = {0}) - - - Added a new currency (ID = {0}) - - - Added a new customer attribute (ID = {0}) - - - Added a new customer attribute value (ID = {0}) - - - Added a new email account (ID = {0}) - - - Added a new language (ID = {0}) - - - Added a new measure dimension (ID = {0}) - - - Added a new measure weight (ID = {0}) - - - Added a new news (ID = {0}) - - - Installed a new plugin (FriendlyName: ''{0}'') - - - Added a new state province (ID = {0}) - - - Added a new store (ID = {0}) - - - Added a new vendor (ID = {0}) - - - Added a new warehouse (ID = {0}) - - - Deleted an address attribute (ID = {0}) - - - Deleted an address attribute value (ID = {0}) - - - Deleted an affiliate (ID = {0}) - - - Deleted a blog post (ID = {0}) - - - Deleted a blog post comment (ID = {0}) - - - Deleted a campaign (ID = {0}) - - - Deleted a country (ID = {0}) - - - Deleted a currency (ID = {0}) - - - Deleted a customer attribute (ID = {0}) - - - Deleted a customer attribute value (ID = {0}) - - - Deleted an email account (ID = {0}) - - - Deleted a language (ID = {0}) - - - Deleted a measure dimension (ID = {0}) - - - Deleted a measure weight (ID = {0}) - - - Deleted a message template (ID = {0}) - - - Deleted a news (ID = {0}) - - - Deleted a news comment (ID = {0}) - - - Uninstalled a plugin (FriendlyName: ''{0}'') - - - Deleted a product revie (ID = {0}) - - - Deleted a state or province (ID = {0}) - - - Deleted a store (ID = {0}) - - - Deleted a vendor (ID = {0}) - - - Deleted a warehouse (ID = {0}) - - - Edited an address attribute (ID = {0}) - - - Edited an address attribute value (ID = {0}) - - - Edited an affiliate (ID = {0}) - - - Edited a blog post (ID = {0}) - - - Edited a campaign (ID = {0}) - - - Edited a country (ID = {0}) - - - Edited a currency (ID = {0}) - - - Edited a customer attribute (ID = {0}) - - - Edited a customer attribute value (ID = {0}) - - - Edited an email account (ID = {0}) - - - Edited a language (ID = {0}) - - - Edited a measure dimension (ID = {0}) - - - Edited a measure weight (ID = {0}) - - - Edited a message template (ID = {0}) - - - Edited a news (ID = {0}) - - - Edited a plugin (FriendlyName: ''{0}'') - - - Edited a product revie (ID = {0}) - - - Edited a state or province (ID = {0}) - - - Edited a store (ID = {0}) - - - Edited a task (ID = {0}) - - - Edited a vendor (ID = {0}) - - - Edited a warehouse (ID = {0}) - - - Product review possible only after purchasing product - - - Check if product can be reviewed only by customer who have already ordered it. - - - Product can be reviewed only after purchasing it - - - - - - You can use ECB (European central bank) exchange rate provider only when the primary exchange rate currency is supported by ECB - - - Attached static file - - - The attached static file that will be sent in this email. - - - Contract ID - - - Specify contract identifier. - - - Product review possible only after product purchasing - - - Page size should be positive. - - - Page size should be positive. - - - Page size should be positive. - - - Enter tags ... - - - - - - - - - Show SKU on catalog pages - - - Check to show product SKU on catalog pages in public store. - - - Show SKU on product details page - - - Check to show product SKU on the product details page in public store. - - - - - - - - - Customer enters quantity - - - Allow customers enter the quantity of associated product. - - - - quantity {0} - - - {0} [{1}{2}] - - - per item - - - Enter quantity: - - - Email account - - - The email account that will be used to send this campaign. - - - Limited to customer roles - - - Choose one or several customer roles i.e. administrators, vendors, guests, who will be able to use this plugin. If you don''t need this option just leave this field empty. - - - Activate points immediately - - - Activates bonus points immediately after their calculation - - - Reward points activation - - - Specify how many days (hours) must elapse before earned points become active. Points earned by purchase cannot be redeemed until activated. For example, you may set the days before the points become available to 7. In this case, the points earned will be available for spending 7 days after the order gets chosen awarded status. - - - The points will be activated on {0} - - - Days - - - Hours - - - The points will be activated on {0} - - - Your current balance - - - Total - - - Amount - - - Specify if earned reward points are taxable - - - If Earned reward points are taxable, then OrderAmount and tax will be reduced (like discounts do). Otherwise payment amount is reduced like with gift cards. - - - Include shipping - - - Specify if shipping amount should be included in reward points calculation. - - - Include payment method fee - - - Specify if payment method additional fee should be included in reward points calculation. - - - Exclude gift card(s) - - - Specify if gift cards should be excluded in reward points calculation. This setting allows to earn reward points, although order was paid or partially paid with giftcards. - - - Exclude purchased reward points - - - Specify if purchased points should be excluded in reward points calculation. This setting allows to earn reward points, although order total is reduced or zero. - - - Earn points only when using purchased points - - - Specify if reward points can only be earned when using purchased reward points for payment. With this earning of reward points is connected to the use of purchased points. - - - Is reward point(s) - - - If this field is checked, reward points can be pruchased. - - - Overridden reward points exchange rate - - - Use this field to override reward points exchange rate setting when converting product price to reward points (points = [overridden exchange rate] * price). If not specified, then reward points exchange rate will be used. - - - Reward point(s) - - - Reward points - - - Sorry, this discount cannot be used with reward points products in the cart - - - {0} purchased reward points: - - - Purchased with order #{0} - - - Purchased and earned with order #{0} - - - {0} used reward points - - - {0} used purchased reward points - - - {0} used reward points - - - {0} used purchased reward points - - - Redeemed reward points - - - Redeemed reward points. - - - Redeemed reward points amount - - - Redeemed reward points amount. - - - Redeemed reward points purchased - - - Redeemed purchased reward points. - - - Redeemed reward points amount purchased - - - Redeemed purchased reward points amount. - - - Purchased value - - - Indicate if added reward points are purchased points. Negative values are also supported. - - - Points balance purchased - - - Points Purchased - - - Points purchased - - - Points balance purchased - - - Add customer reward points (points = {0}, purchased points = {1}) - - - Edited customer reward points (ID = {0}) - - - Exclude from reward points - - - If this field is checked, product will be excluded from reward points calculation. - - - At least one published currency is required - - - The customer cannot be in both ''Guests'' and ''Registered'' customer roles - - - Add the customer to ''Guests'' or ''Registered'' customer role - - - Valid Email is required for customer to be in ''Registered'' role - - - A non-admin user cannot impersonate as an administrator - - - At least one published language is required - - - This order item has an associated gift card record. Please delete it first - - - Order item is deleted - - - Captcha is enabled but the appropriate keys are not entered - - - The message template has been copied successfully - - - The product has been copied successfully - - - Entered page name already exists, so it will be replaced by ''{0}'' - - - Choose a delivery date which will be displayed in the public store. You can manage delivery dates by selecting Configuration > Shipping > Dates and ranges. - - - Product availability range - - - Choose the product availability range that indicates when the product is expected to be available when out of stock (e.g. Available in 10-14 days). You can manage availability ranges by selecting Configuration > Shipping > Dates and ranges. - - - None - - - Product availability range - - - Dates and ranges - - - List of delivery dates which will be available for choice in product details. - - - Product availability ranges - - - The new product availability range has been added successfully. - - - Add a new product availability range - - - back to product availability range list - - - The product availability range has been deleted successfully. - - - Edit product availability range details - - - Display order - - - The display order of this product availability range. 1 represents the top of the list. - - - Name - - - Enter product availability range name. - - - Please provide a name. - - - List of availability ranges which will be available for choice in product details. - - - The product availability range has been updated successfully. - - - Available in {0} - - - Out of stock - on backorder and will be dispatched once in stock ({0}). - - - Please note that this product is excluded from the reward points program. No points will be earned. - - - Available in {0} - - - {0} point(s) - - - You will earn (based on {0}) - - - (when using purchased points) - - - Pay by cheque or money order - - - Pay by credit / debit card - - - Pay by credit / debit card - - - You will be redirected to PayPal site to complete the payment - - - Pay by purchase order (PO) number - - - Your account already has been activated - - - Your password already has been changed. For changing it once more, you need to again recover the password. - - - Your subscription already has been deactivated. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - By Weight - - - Fixed Rate - - - Rate - - - Store - - - If an asterisk is selected, then this shipping rate will apply to all stores. - - - Warehouse - - - If an asterisk is selected, then this shipping rate will apply to all warehouses. - - - Country - - - If an asterisk is selected, then this shipping rate will apply to all customers, regardless of the country. - - - State / province - - - If an asterisk is selected, then this shipping rate will apply to all customers from the given country, regardless of the state. - - - Zip - - - Zip / postal code. If zip is empty, then this shipping rate will apply to all customers from the given country or state, regardless of the zip code. - - - Shipping method - - - Choose shipping method - - - Order weight from - - - Order weight from. - - - Order weight to - - - Order weight to. - - - Additional fixed cost - - - Specify an additional fixed cost per shopping cart for this option. Set to 0 if you don''t want an additional fixed cost to be applied. - - - Lower weight limit - - - Lower weight limit. This field can be used for \"per extra weight unit\" scenarios. - - - Charge percentage (of subtotal) - - - Charge percentage (of subtotal). - - - Rate per weight unit - - - Rate per weight unit. - - - Limit shipping methods to configured ones - - - If you check this option, then your customers will be limited to shipping options configured here. Otherwise, they''ll be able to choose any existing shipping options even they''ve not configured here (zero shipping fee in this case). - - - Data - - - Add record - - - Formula to calculate rates - - - [additional fixed cost] + ([order total weight] - [lower weight limit]) * [rate per weight unit] + [order subtotal] * [charge percentage] - - - Allow vendors to import products - - - Check if vendors are allowed to import products. - - - You save: {0} - - - Discounted qty: {0} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Add new tier price - - - Edit tier price details - - - Select customer role for which the tier price will be available. - - - End date - - - The end date of the tier price in Coordinated Universal Time (UTC). - - - Specify the price. - - - Specify quantity for which this tier price will be available. - - - Start date - - - The start date of the tier price in Coordinated Universal Time (UTC). - - - Option to limit this tier price to a certain store. If you have multiple stores, choose one from the list. - - - Deactivate gift cards after deleting of an order - - - Check to deactivate related gift cards when an order is deleted. - - - Complete order when delivered - - - Check if an order status should be set to "Complete" only when its shipping status is "Delivered". Otherwise, "Shipped" status will be enough. - - - order #{1}]]> - - - Edited a news comment (ID = {0}) - - - News comments must be approved - - - Check if news comments must be approved by administrator. - - - Is approved - - - News comment is successfully added. You will see it after approving by a store administrator. - - - Edited a blog comment (ID = {0}) - - - Blog comments must be approved - - - Check if blog comments must be approved by administrator. - - - Is approved - - - Blog comment is successfully added. You will see it after approving by a store administrator. - - - Stock quantity history - - - Here you can see a history of the product stock quantity changes. - - - Attribute combination - - - Created On - - - Message - - - Stock quantity - - - Quantity adjustment - - - Warehouse - - - Stock quantity history - - - The stock quantity has been increased by canceling the order #{0} - - - The stock quantity of combination has been edited - - - The stock quantity has been edited by copying the product #{0} - - - The stock quantity has been increased by deleting the order #{0} - - - The stock quantity has been increased by deleting an order item from the order #{0} - - - The stock quantity has been increased by deleting a shipment from the order #{0} - - - The stock quantity has been edited - - - Multiple warehouses. - - - The stock quantity has been changed by editing the order #{0} - - - The stock quantity has been changed by importing product - - - Products have been moved {0} {1} by importing product - - - Products have been moved {0} {1} - - - to the {0} - - - from the {0} - - - The stock quantity has been reduced by placing the order #{0} - - - The stock quantity has been reduced when an order item of the order #{0} was shipped - - - {0} - copy - - - {0}-copy - - - - - - - - - - - - - - - - - - - - - Deactivate gift cards after cancelling of an order - - - Check to deactivate related gift cards when an order is cancelled. - - - Activate gift cards after completing of an order - - - Check to activate related gift cards when an order is completed. - - - - - - - - - - - - - - - - - - - - - - - - - - - Do not forget to restart the application once a task has been modified. - - - - - - - or - - - - Choose an associated product - - - Store - - - Load products only from a specific store (available in this store). - - - Vendor - - - Load products only by a specific vendor (owned by this vendor). - - - Category - - - Load products only from a specific category. - - - Manufacturer - - - Load products only from a specific manufacturer. - - - Carts shared between storest - - - Determines whether shopping carts (and wishlist) are shared between stores (in multi-store environment). - - - Email validation - - - Your email already has been validated - - - Your email has been validated - - - Email validation - - - Email validation - - - New email - - - (not validated yet) - - - Shipping methods used by offline shipping providers. For example, "Manual (Fixed or By Weight)". - - - Ignored product type IDs (advanced) - - - Allow file uploads - - - Check if you want to allow customers to upload files when submitting return requests. - - - Upload (any additional document, scan, etc) - - - Uploaded file: - - - Download - - - Uploaded file - - - File uploaded by customer - - - Download - - - Reply text - - - The reply text (by a store owner). If specified, then it''ll be visible to a customer. Leave empty to ignore this functionality. - - - A manager responded to this review - - - Blog comments per store - - - Check to display blog comments written in the current store only. - - - News comments per store - - - Check to display news comments written in the current store only. - - - Store name - - - Store name - - - The associated product has attributes, keep in mind that customers can not select them in the product details page. - - - The associated product has required product attributes, so customers won''t be able to choose this product attribute value. - - - The associated product is downloadable, keep in mind that won''t be able to download it. - - - The associated product is a gift card, keep in mind that customers can not specify its details in the product details page. - - - All - - - {0} - {1} of {2} items - - - No items to display - - - Go to the first page - - - items per page - - - Go to the last page - - - More pages - - - Go to the next page - - - of {0} - - - Page - - - Go to the previous page - - - Refresh - - - Address (optional) - - - Maximum login failures - - - Maximum login failures to lockout account. Set 0 to disable this feature. - - - Lockout time (login failures) - - - Enter number of minutes to lockout users (for login failures). - - - Customer is locked out - - - Imported manufacturers are distinguished by ID. If the ID already exists, then its corresponding manufacturer will be updated. You should not specify ID (leave 0) for new manufacturers. - - - Imported categories are distinguished by ID. If the ID already exists, then its corresponding category will be updated. You should not specify ID (leave 0) for new categories. - - - For conditional expressions use the token %if (your conditions ) ... endif% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Fixed rate - - - By Country - - - Tax category - - - Rate - - - Store - - - If an asterisk is selected, then this shipping rate will apply to all stores. - - - Country - - - The country. - - - State / province - - - If an asterisk is selected, then this tax rate will apply to all customers from the given country, regardless of the state. - - - Zip - - - Zip / postal code. If zip is empty, then this tax rate will apply to all customers from the given country or state, regardless of the zip code. - - - Tax category - - - The tax category. - - - Percentage - - - The tax rate. - - - Add tax rate - - - New tax rate - - - ID - - - Search by a specific return request identifier. - - - End date - - - The end date for the search. - - - Return status - - - All - - - Search by a specific return request status e.g. Received. - - - Start date - - - The start date for the search. - - - - Product attributes are quantifiable or descriptive aspects of a product (such as, color). For example, if you were to create an attribute for color, with the values of blue, green, yellow, and so on, you may want to apply this attribute to shirts, which you sell in various colors (you can adjust a price or weight for any of existing attribute values). - You can add attribute for your product using existing list of attributes, or if you need to create a new one go to Catalog > Attributes > Product attributes. Please notice that if you want to manage inventory by product attributes (e.g. 5 green shirts and 3 blue ones), then ensure that "Inventory method" is set to "Track inventory by product attributes". - - - - Approve selected - - - Approve selected - - - Disapprove selected - - - Disapprove selected - - - Registered in the store - - - Indicating in which store the customer is registered - - - Consider associated products dimensions and weight - - - Check to consider associated products dimensions and weight on shipping, uncheck for example if the main product already includes them. - - - Created from - - - The creation from date for the search. - - - Created to - - - The creation to date for the search. - - - Message - - - Search in title and comment text. - - - Created from - - - The creation from date for the search. - - - Created to - - - The creation to date for the search. - - - Message - - - Search in comment text. - - - Retry last payment - - - Last payment failed - - - Approved - - - Search by a "Approved" property. - - - All - - - Approved only - - - Disapproved only - - - Approved - - - Search by a "Approved" property. - - - All - - - Approved only - - - Disapproved only - - - Approved - - - Search by a "Approved" property. - - - All - - - Approved only - - - Disapproved only - - - It looks like you have "ShoppingCartSettings.RoundPricesDuringCalculation" setting disabled. Keep in mind that this can lead to a discrepancy of the order total amount, as PayPal only rounds to two decimals. - - - - - - - - - - - - - - - - - - - - - Order ID - - - Order # - - - Order # - - - Order ID - - - Order # - - - Order ID - - - Order # - - - Order # - - - The unique number of this order. - - - Reward points base - - - Earned reward points base amount - - - Earned reward points base amount incl. Tax - - - Earned reward points base amount excl. Tax - - - Created order ID - - - Created order - - - Order ID - - - Order # - - - Order ID - - - Order # - - - The order associated to this shipment. - - - Order number mask - - - Order number mask, for creating custom order number. For example, RE-{YYYY}-{MM}. Leave this field empty if you don''t want to use custom order numbers. - - - {DD} - day of order creation date - - - {ID} -Order identifier - - - {MM} - month of order creation date - - - {YYYY} - year of order creation date - - - {YY} - last two digits of year of order creation date - - - - - - - - - Order - - - The gift card was purchased with this order. - - - Order - - - The gift card was purchased with this order. - - - Edited an order (Order number = {0}). See order notes for details - - - View order (Order number - {0}) - - - The unique number of the order. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Pass purchased items - - - Check to pass information about purchased items to PayPal. - - - Unduplicated passwords number - - - Specify the number of customer passwords that mustn''t be the same as the previous one, enter 0 if the customer can use the same password time after time. - - - You entered the password that is the same as one of the last passwords you used. Please create a new password. - - - Your password has expired, please create a new one - - - Password lifetime - - - Specify number of days for password expiration. Don''t forget to check "EnablePasswordLifetime" property on customer role edit page for those roles, who will have to change passwords. - - - Enable password lifetime - - - Check to force customers to change their passwords after a specified time. - - - ID - - - ID - - - - - - - - - This order is cancelled - - - Notify about new blog comments in Configuration - Settings - Blog settings.]]> - - - Allow back in stock subscriptions in Product info tab - Inventory section.]]> - - - This message template is used when a customer changes an email address in his account. The customer receives a message to confirm an email address used when changing email address. - - - This message template is used the option Email notification from Registration method dropdown list in Configuration - Settings - Customer settings is selected. The customer receives a message to confirm an email address used when registering. - - - This message template is used when the customer gets a notification about a new order being placed from this account. - - - Show alert for PM in Configuration - Settings - Forum settings.]]> - - - This message template is used when a customer forgets a password needed to log in the account. The customer receives a message with a link for entering a new password. - - - This message template is used to welcome a new customer after registration. - - - This message template is used when a new forum post in certain forum topic is created. The message is received by a store owner. - - - This message template is used when a new forum topic is created. The message is received by a store owner. - - - This message template is used to send a notification to a customer about getting a gift card. - - - Notify about new customer registration in Configuration - Settings - Customer settings.]]> - - - This message template is used to notify a customer about a new return request submitted from his/her account. - - - This message template is used when a new return request is created. The message is received by a store owner. - - - Notify about new news comments in Configuration - Settings - News settings.]]> - - - This message template is used when a customer subscribes to the newsletter to confirm the subscription. - - - Newsletter box. Allow to unsubscribe in Configuration - Settings - Customer settings.]]> - - - Notify admin when a new VAT number is submitted in Configuration - Settings - Tax settings.]]> - - - This message template is used to notify a customer that the certain order was canceled. The order can ba canceled by a customer on the account page or by store owner in Customers - Customers in Orders tab or in Sales - Orders. - - - This message template is used to notify a customer that the certain order was completed. The order gets the order status Complete when it''s paid and delivered, or it can be changed manually to Complete in Sales - Orders. - - - This message template is used to notify a customer that the certain order was paid. The order gets the payment status Paid when the amount was charged, or it can be changed manually Sales - Orders by clicking Mark as paid button in Payment status. - - - This message template is used to notify a store owner that the certain order was paid. The order gets the status Paid when the amount was charged, or it can be changed manually Sales - Orders by clicking Mark as paid button in Payment status. - - - This message template is used to notify a vendor that the certain order was paid. The order gets the status Paid when the amount was charged. - - - This message template is used to notify a customer that the certain order was placed. Orders can be viewed by a customer on the account page. - - - This message template is used to notify a store owner that the certain order was placed. Orders can be viewed by a store owner in Sales - Orders or in Customers menu. - - - This message template is used to notify a vendor that the certain order was placed. Orders can be viewed by a vendor in Sales - Orders. You can allow them to access this part of Admin Area in Configuration - Access control list. - - - This message template is used to notify a customer that the certain order was refunded. The customer can submit to refund the order when the payment status is paid on the account page, or it can be done by a store owner in Sales - Orders by clicking "Refund" button. - - - This message template is used to notify a store owner that the certain order was refunded. The customer can submit to refund the order when the payment status is paid on the account page, or it can be done by a store owner in Sales - Orders by clicking "Refund" button. - - - Notify about new product reviews in Configuration - Settings - Catalog settings.]]> - - - This message template is used to notify a store owner that the certain product attribute combination is getting low stock. You can set up the combination minimum quantity when creating or editing the product in Product attribute tab - Attributes combinations tab in Notify admin for quantity below field. - - - Minimum stock qty field.]]> - - - This message template is used to notify a customer that the certain recurring payment is canceled. Payment can be canceled by a customer in the account page or by a store owner in Sales - Recurring payments in History tab by clicking "Cancel recurring payment" button. - - - This message template is used to notify a store owner that the certain recurring payment is canceled. Payment can be canceled by a customer in the account page or by a store owner in Sales - Recurring payments in History tab by clicking "Cancel recurring payment" button. - - - This message template is used to notify a customer that the certain recurring payment is failed. For example, the amount can''t be charged from the provided credit card. - - - This message template is used to notify a customer that the request status to the certain order is changed. You can set up this option in Sales - Return requests by clicking "Notify customer about status change" button. - - - This message template is used to notify a store owner about a message sent through the contact form. - - - This message template is used to notify a vendor about a message sent through the contact form. - - - This message template is used to send "email a friend" message. - - - Display shipment events (customers).]]> - - - Display shipment events (customers).]]> - - - Allow customers to apply for vendor account.]]> - - - Allow vendors to edit info.]]> - - - This message template is used when a customer wants to share some product from the wishlist with a friend by sending an email. You can set up this option by ticking the checkbox Allow customers to email their wishlists in Configuration - Settings - Shopping cart settings. - - - Tax based on pickup point address - - - A value indicating whether to use pickup point address (when pickup point is chosen) for tax calculation. - - - It seems that you use Redis server for caching, keep in mind that enabling this setting create a lot of traffic between the Redis server and the application because of the large number of locales. - - - Export orders with products - - - Check if orders should be exported with products. - - - Requirement group is a useful feature for creating discount requirement templates. You can create a requirement group just once and then use it every time you want this limitation to be applied. You can include one requirement group into another one if needed.]]> - - - Add requirement group - - - You can choose one of the following requirement types, or add a requirement group to use several requirement types simultaneously. - - - Group name - - - Specify name of the requirement group (e.g. "Permitted customer roles"). - - - Add to group - - - Choose the group you want the requirement group you’re creating to be assigned to - - - Group - - - AND - - - OR - - - - - - Requirement - - - Default requirement group - - - Interaction type in this group is - - - The group is empty - - - Remove requirement - - - Remove group - - - Blog comments - - - Common - - - Additional sections - - - Catalog pages - - - Compare products - - - Export/Import - - - Performance - - - Product fields - - - Product page - - - Product sorting - - - Product reviews - - - Search - - - Share - - - Tax - - - Tags - - - Account - - - Common - - - Default fields - - - External authentication - - - Password and security - - - Profile - - - Time zone - - - Common - - - Feeds - - - Page sizes - - - Permissions - - - Full-Text - - - CAPTCHA - - - Common - - - Localization - - - Pdf - - - Security - - - SEO - - - Sitemap - - - Social media - - - Common - - - Other pages - - - Product - - - News comments - - - Common - - - Checkout - - - Common - - - Gift cards - - - Order totals - - - Pdf invoice - - - Common - - - Checkout - - - Common - - - Notifications - - - Common - - - Mini shopping cart - - - Wishlist - - - Common - - - Payment - - - Shipping - - - Tax dispaying - - - VAT - - - Catalog - - - Common - - - In order to use this functionality you have to disable the following setting: Configuration > Catalog settings > Ignore discounts (sitewide). - - -
    • At least two unique product identifiers are required. So each of your product shouldhave manufacturer (brand) and MPN (manufacturer part number) specified
    • Specify default tax values in your Google Merchant Center account settings
    • Specify default shipping values in your Google Merchant Center account settings
    • In order to get more info about required fields look at the following article http://www.google.com/support/merchants/bin/answer.py?answer=188494

    ]]>
    -
    - - You can download the list of allowed Google product category attributes here

    ]]>
    -
    - - If you''re using this gateway ensure that your primary store currency is supported by Paypal.

    To configure plugin follow these steps:
    1. Log into your Developer PayPal account (click here to create your account).
    2. Click on My Apps & Credentials from the Dashboard.
    3. Create new REST API app.
    4. Copy your Client ID and Secret key below.
    5. To be able to use recurring payments you need to set the webhook ID. You can get it manually in your PayPal account (enter the URL https://www.yourStore.com/Plugins/PaymentPayPalDirect/Webhook below REST API application credentials), or automatically by pressing "@T("Plugins.Payments.PayPalDirect.WebhookCreate")" button (not visible when running the site locally).

    ]]>
    -
    - - If you''re using this gateway ensure that your primary store currency is supported by Paypal.

    To use PDT, you must activate PDT and Auto Return in your PayPal account profile. You must also acquire a PDT identity token, which is used in all PDT communication you send to PayPal. Follow these steps to configure your account for PDT:

    1. Log in to your PayPal account (click here to create your account).
    2. Click the Profile subtab.
    3. Click Website Payment Preferences in the Seller Preferences column.
    4. Under Auto Return for Website Payments, click the On radio button.
    5. 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).
    6. Under Payment Data Transfer, click the On radio button.
    7. Click Save.
    8. Click Website Payment Preferences in the Seller Preferences column.
    9. Scroll down to the Payment Data Transfer section of the page to view your PDT identity token.

    Two ways to be able to receive IPN messages (optional):

    The first way is to check ''Enable IPN'' below. It will include in the request the url of you IPN handler

    The second way is to confugure your paypal account to activate this service; follow these steps:
    1. Log in to your Premier or Business account.
    2. Click the Profile subtab.
    3. Click Instant Payment Notification in the Selling Preferences column.
    4. Click the ''Edit IPN Settings'' button to update your settings.
    5. Select ''Receive IPN messages'' (Enabled) and enter the URL of your IPN handler (http://www.yourStore.com/Plugins/PaymentPayPalStandard/IPNHandler).
    6. Click Save, and you should get a message that you have successfully activated IPN.

    ]]>
    -
    - - To configure plugin follow one of these steps:
    1. If you are a Canada Post commercial customer, fill Customer number, Contract ID and API key below.
    2. If you are a Solutions for Small Business customer, specify your Customer number and API key below.
    3. If you are a non-contracted customer or you want to use the regular price of shipping paid by customers, fill the API key field only.

    Note: Canada Post gateway returns shipping price in the CAD currency, ensure that you have correctly configured exchange rate from PrimaryStoreCurrency to CAD.

    ]]>
    -
    - - Google Analytics is a free website stats tool from Google. It keeps track of statisticsabout the visitors and ecommerce conversion on your website.

    Follow the next steps to enable Google Analytics integration:
    • Create a Google Analyticsaccount and follow the wizard to add your website
    • Copy the Tracking ID into the ''ID'' box below
    • Click the ''Save'' button below and Google Analytics will be integrated into your store

    If you would like to switch between Google Analytics (used by default) and Universal Analytics, then please use the buttons below:

    ]]>
    -
    - - Please note that {ECOMMERCE} line works only when you have "Disable order completed page" order setting unticked.

    ]]>
    -
    - - Here you can find third-party extensions and themes which are developed by our community and partners.They are also available in our marketplace

    ]]>
    -
    - - A CAPTCHA is a program that can tell whether its user is a human or a computer.You''ve probably seen them — colorful images with distorted text at the bottom ofWeb registration forms. CAPTCHAs are used by many websites to prevent abuse from"bots," or automated programs usually written to generate spam. No computer programcan read distorted text as well as humans can, so bots cannot navigate sites protectedby CAPTCHAs. nopCommerce uses reCAPTCHA.

    ]]>
    -
    - - You can download more nopCommerce plugins in our marketplace

    ]]>
    -
    - - Edit values - - - Edit condition - - - Edit rules - - - {0} categories were imported - - - {0} manufacturers were imported - - - {0} products were imported - - - {0} states and provinces were imported - - - Add to wishlist - - - Update - - - - - - Store - - - Search by a specific store. - - - Rounding type - - - The rounding type. - - - Default rounding - - - Rounding up with 0.05 intervals (0.06 round to 0.10) - - - Rounding down with 0.05 intervals (0.06 round to 0.05) - - - Rounding up with 0.10 intervals (1.05 round to 1.10) - - - Rounding down with 0.10 intervals (1.05 round to 1.00) - - - Rounding with 0.50 intervals - - - Rounding with 1.00 intervals (1.01-1.49 round to 1.00, 1.50-1.99 round to 2.00) - - - Rounding up with 1.00 intervals (1.01–1.99 round to 2.00) - - - Picture zoom - - - Check to enable picture zoom on product details page. - - - Display "Blog" - - - Check if "Blog" menu item should be displayed in the top menu. - - - Display "Forums" - - - Check if "Forums" menu item should be displayed in the top menu. - - - Display "Contact us" - - - Check if "Contact us" menu item should be displayed in the top menu. - - - Display "My account" - - - Check if "My account" menu item should be displayed in the top menu. - - - Display "Home page" - - - Check if "Home page" menu item should be displayed in the top menu. - - - Display "New products" - - - Check if "New products" menu item should be displayed in the top menu. - - - Display "Search" - - - Check if "Search" menu item should be displayed in the top menu. - - - Top menu items - -
    -' - -CREATE TABLE #LocaleStringResourceTmp - ( - [ResourceName] [nvarchar](200) NOT NULL, - [ResourceValue] [nvarchar](max) NOT NULL - ) - -INSERT INTO #LocaleStringResourceTmp (ResourceName, ResourceValue) -SELECT nref.value('@Name', 'nvarchar(200)'), nref.value('Value[1]', 'nvarchar(MAX)') -FROM @resources.nodes('//Language/LocaleResource') AS R(nref) - ---do it for each existing language -DECLARE @ExistingLanguageId int -DECLARE cur_existinglanguage CURSOR FOR -SELECT [Id] -FROM [Language] -OPEN cur_existinglanguage -FETCH NEXT FROM cur_existinglanguage INTO @ExistingLanguageId -WHILE @@FETCH_STATUS = 0 -BEGIN - DECLARE @ResourceName nvarchar(200) - DECLARE @ResourceValue nvarchar(MAX) - DECLARE cur_localeresource CURSOR FOR - SELECT ResourceName, ResourceValue - FROM #LocaleStringResourceTmp - OPEN cur_localeresource - FETCH NEXT FROM cur_localeresource INTO @ResourceName, @ResourceValue - WHILE @@FETCH_STATUS = 0 - BEGIN - IF (EXISTS (SELECT 1 FROM [LocaleStringResource] WHERE LanguageId=@ExistingLanguageId AND ResourceName=@ResourceName)) - BEGIN - UPDATE [LocaleStringResource] - SET [ResourceValue]=@ResourceValue - WHERE LanguageId=@ExistingLanguageId AND ResourceName=@ResourceName - END - ELSE - BEGIN - INSERT INTO [LocaleStringResource] - ( - [LanguageId], - [ResourceName], - [ResourceValue] - ) - VALUES - ( - @ExistingLanguageId, - @ResourceName, - @ResourceValue - ) - END - - IF (@ResourceValue is null or @ResourceValue = '') - BEGIN - DELETE [LocaleStringResource] - WHERE LanguageId=@ExistingLanguageId AND ResourceName=@ResourceName - END - - FETCH NEXT FROM cur_localeresource INTO @ResourceName, @ResourceValue - END - CLOSE cur_localeresource - DEALLOCATE cur_localeresource - - --fetch next language identifier - FETCH NEXT FROM cur_existinglanguage INTO @ExistingLanguageId -END -CLOSE cur_existinglanguage -DEALLOCATE cur_existinglanguage - -DROP TABLE #LocaleStringResourceTmp -GO - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'ordersettings.invoiceident') - BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'ordersettings.invoiceident', 0, 0) - END -GO - -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'ordersettings.invoiceyear') - BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'ordersettings.invoiceyear', 2016, 0) - END -GO ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'shippingsettings.hideshippingtotal') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'shippingsettings.hideshippingtotal', N'False', 0) -END -GO - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'taxsettings.defaulttaxcategoryid') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'taxsettings.defaulttaxcategoryid', N'0', 0) -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewAddressAttribute') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'AddNewAddressAttribute', N'Add a new address attribute', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewAffiliate') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'AddNewAffiliate', N'Add a new affiliate', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewBlogPost') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'AddNewBlogPost', N'Add a new blog post', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewCampaign') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'AddNewCampaign', N'Add a new campaign', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewCountry') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'AddNewCountry', N'Add a new country', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewCurrency') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'AddNewCurrency', N'Add a new currency', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewCustomerAttribute') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'AddNewCustomerAttribute', N'Add a new customer attribute', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewCustomerAttributeValue') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'AddNewCustomerAttributeValue', N'Add a new customer attribute value', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewEmailAccount') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'AddNewEmailAccount', N'Add a new email account', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewLanguage') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'AddNewLanguage', N'Add a new language', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewMeasureDimension') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'AddNewMeasureDimension', N'Add a new measure dimension', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewMeasureWeight') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'AddNewMeasureWeight', N'Add a new measure weight', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewNews') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'AddNewNews', N'Add a new news', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'InstallNewPlugin') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'InstallNewPlugin', N'Install a new plugin', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewStateProvince') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'AddNewStateProvince', N'Add a new state or province', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewStore') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'AddNewStore', N'Add a new store', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewVendor') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'AddNewVendor', N'Add a new vendor', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewWarehouse') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'AddNewWarehouse', N'Add a new warehouse', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteAddressAttribute') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'DeleteAddressAttribute', N'Delete an address attribute', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteAffiliate') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'DeleteAffiliate', N'Delete an affiliate', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteBlogPost') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'DeleteBlogPost', N'Delete a blog post', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteCampaign') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'DeleteCampaign', N'Delete a campaign', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteCountry') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'DeleteCountry', N'Delete a country', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteCurrency') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'DeleteCurrency', N'Delete a currency', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteCustomerAttribute') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'DeleteCustomerAttribute', N'Delete a customer attribute', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteCustomerAttributeValue') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'DeleteCustomerAttributeValue', N'Delete a customer attribute value', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteEmailAccount') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'DeleteEmailAccount', N'Delete an email account', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteLanguage') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'DeleteLanguage', N'Delete a language', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteMeasureDimension') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'DeleteMeasureDimension', N'Delete a measure dimension', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteMeasureWeight') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'DeleteMeasureWeight', N'Delete a measure weight', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteMessageTemplate') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'DeleteMessageTemplate', N'Delete a message template', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteNews') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'DeleteNews', N'Delete a news', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'UninstallPlugin') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'UninstallPlugin', N'Uninstall a plugin', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteProductReview') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'DeleteProductReview', N'Delete a product review', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteStateProvince') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'DeleteStateProvince', N'Delete a state or province', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteStore') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'DeleteStore', N'Delete a store', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteVendor') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'DeleteVendor', N'Delete a vendor', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteWarehouse') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'DeleteWarehouse', N'Delete a warehouse', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditAddressAttribute') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'EditAddressAttribute', N'Edit an address attribute', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditAffiliate') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'EditAffiliate', N'Edit an affiliate', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditBlogPost') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'EditBlogPost', N'Edit a blog post', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditCampaign') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'EditCampaign', N'Edit a campaign', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditCountry') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'EditCountry', N'Edit a country', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditCurrency') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'EditCurrency', N'Edit a currency', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditCustomerAttribute') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'EditCustomerAttribute', N'Edit a customer attribute', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditCustomerAttributeValue') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'EditCustomerAttributeValue', N'Edit a customer attribute value', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditEmailAccount') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'EditEmailAccount', N'Edit an email account', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditLanguage') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'EditLanguage', N'Edit a language', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditMeasureDimension') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'EditMeasureDimension', N'Edit a measure dimension', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditMeasureWeight') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'EditMeasureWeight', N'Edit a measure weight', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditMessageTemplate') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'EditMessageTemplate', N'Edit a message template', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditNews') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'EditNews', N'Edit a news', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditPlugin') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'EditPlugin', N'Edit a plugin', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditProductReview') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'EditProductReview', N'Edit a product review', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditStateProvince') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'EditStateProvince', N'Edit a state or province', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditStore') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'EditStore', N'Edit a store', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditTask') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'EditTask', N'Edit a task', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditVendor') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'EditVendor', N'Edit a vendor', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditWarehouse') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'EditWarehouse', N'Edit a warehouse', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteBlogPostComment') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'DeleteBlogPostComment', N'Delete a blog post comment', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteNewsComment') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'DeleteNewsComment', N'Delete a news comment', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewAddressAttributeValue') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'AddNewAddressAttributeValue', N'Add a new address attribute value', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditAddressAttributeValue') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'EditAddressAttributeValue', N'Edit an address attribute value', N'true') -END -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteAddressAttributeValue') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'DeleteAddressAttributeValue', N'Delete an address attribute value', N'true') -END -GO - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'catalogsettings.productreviewpossibleonlyafterpurchasing') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'catalogsettings.productreviewpossibleonlyafterpurchasing', N'False', 0) -END -GO - - --new setting - IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'catalogsettings.exportimportusedropdownlistsforassociatedentities') - BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'catalogsettings.exportimportusedropdownlistsforassociatedentities', N'True', 0) - END - GO - - --new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'catalogsettings.showskuoncatalogpages') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'catalogsettings.showskuoncatalogpages', N'False', 0) -END -GO - ---rename settings -UPDATE [Setting] -SET [Name] = N'catalogsettings.showskuonproductdetailspage' -WHERE [Name] = N'catalogsettings.showproductsku' -GO - ---new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[ProductAttributeValue]') and NAME='CustomerEntersQty') -BEGIN - ALTER TABLE [ProductAttributeValue] - ADD [CustomerEntersQty] bit NULL -END -GO - -UPDATE [ProductAttributeValue] -SET [CustomerEntersQty] = 0 -WHERE [CustomerEntersQty] IS NULL -GO - -ALTER TABLE [ProductAttributeValue] ALTER COLUMN [CustomerEntersQty] bit NOT NULL -GO - ---new or update setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'shoppingcartsettings.renderassociatedattributevaluequantity') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'shoppingcartsettings.renderassociatedattributevaluequantity', N'True', 0); -END -ELSE -BEGIN - UPDATE [Setting] - SET [Value] = N'True' - WHERE [Name] = N'shoppingcartsettings.renderassociatedattributevaluequantity' -END -GO - ---update column -ALTER TABLE [RewardPointsHistory] ALTER COLUMN [PointsBalance] int NULL -GO - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'rewardpointssettings.activationdelay') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'rewardpointssettings.activationdelay', N'0', 0) -END -GO - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'rewardpointssettings.activationdelayperiodid') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'rewardpointssettings.activationdelayperiodid', N'0', 0) -END -GO - - ---new discount coupon code logic -DELETE FROM [GenericAttribute] -WHERE [KeyGroup] = 'Customer' and [Key] = 'DiscountCouponCode' -GO - ---new table -IF NOT EXISTS (SELECT 1 FROM sys.objects WHERE object_id = OBJECT_ID(N'[ProductAvailabilityRange]') and OBJECTPROPERTY(object_id, N'IsUserTable') = 1) -BEGIN - CREATE TABLE [dbo].[ProductAvailabilityRange]( - [Id] [int] IDENTITY(1,1) NOT NULL, - [Name] nvarchar(400) NOT NULL, - [DisplayOrder] int NOT NULL, - PRIMARY KEY CLUSTERED - ( - [Id] ASC - )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) - ) -END -GO - ---add a new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Product]') and NAME='ProductAvailabilityRangeId') -BEGIN - ALTER TABLE [Product] - ADD [ProductAvailabilityRangeId] int NULL -END -GO - -UPDATE [Product] -SET [ProductAvailabilityRangeId] = 0 -WHERE [ProductAvailabilityRangeId] IS NULL -GO - -ALTER TABLE [Product] ALTER COLUMN [ProductAvailabilityRangeId] int NOT NULL -GO - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'paymentsettings.showpaymentmethoddescriptions') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'paymentsettings.showpaymentmethoddescriptions', N'True', 0) -END -GO - - ---ensure that dbo is added to existing stored procedures -IF EXISTS ( - SELECT * - FROM sys.objects - WHERE object_id = OBJECT_ID(N'[FullText_IsSupported]') AND OBJECTPROPERTY(object_id,N'IsProcedure') = 1) -DROP PROCEDURE [FullText_IsSupported] -GO -CREATE PROCEDURE [dbo].[FullText_IsSupported] -AS -BEGIN - EXEC(' - SELECT CASE SERVERPROPERTY(''IsFullTextInstalled'') - WHEN 1 THEN - CASE DatabaseProperty (DB_NAME(DB_ID()), ''IsFulltextEnabled'') - WHEN 1 THEN 1 - ELSE 0 - END - ELSE 0 - END') -END -GO - - -IF EXISTS ( - SELECT * - FROM sys.objects - WHERE object_id = OBJECT_ID(N'[FullText_Enable]') AND OBJECTPROPERTY(object_id,N'IsProcedure') = 1) -DROP PROCEDURE [FullText_Enable] -GO -CREATE PROCEDURE [dbo].[FullText_Enable] -AS -BEGIN - --create catalog - EXEC(' - IF NOT EXISTS (SELECT 1 FROM sys.fulltext_catalogs WHERE [name] = ''nopCommerceFullTextCatalog'') - CREATE FULLTEXT CATALOG [nopCommerceFullTextCatalog] AS DEFAULT') - - --create indexes - DECLARE @create_index_text nvarchar(4000) - SET @create_index_text = ' - IF NOT EXISTS (SELECT 1 FROM sys.fulltext_indexes WHERE object_id = object_id(''[Product]'')) - CREATE FULLTEXT INDEX ON [Product]([Name], [ShortDescription], [FullDescription]) - KEY INDEX [' + dbo.[nop_getprimarykey_indexname] ('Product') + '] ON [nopCommerceFullTextCatalog] WITH CHANGE_TRACKING AUTO' - EXEC(@create_index_text) - - SET @create_index_text = ' - IF NOT EXISTS (SELECT 1 FROM sys.fulltext_indexes WHERE object_id = object_id(''[LocalizedProperty]'')) - CREATE FULLTEXT INDEX ON [LocalizedProperty]([LocaleValue]) - KEY INDEX [' + dbo.[nop_getprimarykey_indexname] ('LocalizedProperty') + '] ON [nopCommerceFullTextCatalog] WITH CHANGE_TRACKING AUTO' - EXEC(@create_index_text) - - SET @create_index_text = ' - IF NOT EXISTS (SELECT 1 FROM sys.fulltext_indexes WHERE object_id = object_id(''[ProductTag]'')) - CREATE FULLTEXT INDEX ON [ProductTag]([Name]) - KEY INDEX [' + dbo.[nop_getprimarykey_indexname] ('ProductTag') + '] ON [nopCommerceFullTextCatalog] WITH CHANGE_TRACKING AUTO' - EXEC(@create_index_text) -END -GO - - - -IF EXISTS ( - SELECT * - FROM sys.objects - WHERE object_id = OBJECT_ID(N'[FullText_Disable]') AND OBJECTPROPERTY(object_id,N'IsProcedure') = 1) -DROP PROCEDURE [FullText_Disable] -GO -CREATE PROCEDURE [dbo].[FullText_Disable] -AS -BEGIN - EXEC(' - --drop indexes - IF EXISTS (SELECT 1 FROM sys.fulltext_indexes WHERE object_id = object_id(''[Product]'')) - DROP FULLTEXT INDEX ON [Product] - ') - - EXEC(' - IF EXISTS (SELECT 1 FROM sys.fulltext_indexes WHERE object_id = object_id(''[LocalizedProperty]'')) - DROP FULLTEXT INDEX ON [LocalizedProperty] - ') - - EXEC(' - IF EXISTS (SELECT 1 FROM sys.fulltext_indexes WHERE object_id = object_id(''[ProductTag]'')) - DROP FULLTEXT INDEX ON [ProductTag] - ') - - --drop catalog - EXEC(' - IF EXISTS (SELECT 1 FROM sys.fulltext_catalogs WHERE [name] = ''nopCommerceFullTextCatalog'') - DROP FULLTEXT CATALOG [nopCommerceFullTextCatalog] - ') -END -GO - - - - -IF EXISTS ( - SELECT * - FROM sys.objects - WHERE object_id = OBJECT_ID(N'[LanguagePackImport]') AND OBJECTPROPERTY(object_id,N'IsProcedure') = 1) -DROP PROCEDURE [LanguagePackImport] -GO -CREATE PROCEDURE [dbo].[LanguagePackImport] -( - @LanguageId int, - @XmlPackage xml -) -AS -BEGIN - IF EXISTS(SELECT * FROM [Language] WHERE [Id] = @LanguageId) - BEGIN - CREATE TABLE #LocaleStringResourceTmp - ( - [LanguageId] [int] NOT NULL, - [ResourceName] [nvarchar](200) NOT NULL, - [ResourceValue] [nvarchar](MAX) NOT NULL - ) - - INSERT INTO #LocaleStringResourceTmp (LanguageID, ResourceName, ResourceValue) - SELECT @LanguageId, nref.value('@Name', 'nvarchar(200)'), nref.value('Value[1]', 'nvarchar(MAX)') - FROM @XmlPackage.nodes('//Language/LocaleResource') AS R(nref) - - DECLARE @ResourceName nvarchar(200) - DECLARE @ResourceValue nvarchar(MAX) - DECLARE cur_localeresource CURSOR FOR - SELECT LanguageID, ResourceName, ResourceValue - FROM #LocaleStringResourceTmp - OPEN cur_localeresource - FETCH NEXT FROM cur_localeresource INTO @LanguageId, @ResourceName, @ResourceValue - WHILE @@FETCH_STATUS = 0 - BEGIN - IF (EXISTS (SELECT 1 FROM [LocaleStringResource] WHERE LanguageID=@LanguageId AND ResourceName=@ResourceName)) - BEGIN - UPDATE [LocaleStringResource] - SET [ResourceValue]=@ResourceValue - WHERE LanguageID=@LanguageId AND ResourceName=@ResourceName - END - ELSE - BEGIN - INSERT INTO [LocaleStringResource] - ( - [LanguageId], - [ResourceName], - [ResourceValue] - ) - VALUES - ( - @LanguageId, - @ResourceName, - @ResourceValue - ) - END - - - FETCH NEXT FROM cur_localeresource INTO @LanguageId, @ResourceName, @ResourceValue - END - CLOSE cur_localeresource - DEALLOCATE cur_localeresource - - DROP TABLE #LocaleStringResourceTmp - END -END - - --new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'fixedorbyweightsettings.shippingbyweightenabled') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'fixedorbyweightsettings.shippingbyweightenabled', N'False', 0) -END -GO - ---rename settings -UPDATE [Setting] -SET [Name] = N'fixedorbyweightsettings.limitmethodstocreated' -WHERE [Name] = N'shippingbyweightsettings.limitmethodstocreated' -GO - ---rename settings -UPDATE [Setting] -SET [Name] = N'shippingratecomputationmethod.fixedorbyweight.rate.shippingmethodid' + SUBSTRING(name, 62, len(name)) -WHERE [Name] like N'shippingratecomputationmethod.fixedrate.rate.shippingmethodid%' -GO - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'vendorsettings.allowvendorstoimportproducts') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'vendorsettings.allowvendorstoimportproducts', N'True', 0) -END -GO - ---add a new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[TierPrice]') and NAME='StartDateTimeUtc') -BEGIN - ALTER TABLE [TierPrice] - ADD [StartDateTimeUtc] datetime NULL -END -GO - ---add a new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[TierPrice]') and NAME='EndDateTimeUtc') -BEGIN - ALTER TABLE [TierPrice] - ADD [EndDateTimeUtc] datetime NULL -END -GO - ---add a tier prices instead of product special prices -IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Product]') and NAME='SpecialPrice') -BEGIN - EXEC(' - INSERT INTO [dbo].[TierPrice]([ProductId], [StoreId], [CustomerRoleId], [Quantity], [Price], [StartDateTimeUtc], [EndDateTimeUtc]) - SELECT [Id], 0, NULL, 1, [SpecialPrice], [SpecialPriceStartDateTimeUtc], [SpecialPriceEndDateTimeUtc] - FROM [dbo].[Product] - WHERE [SpecialPrice] <> 0') -END -GO - -UPDATE [Product] -SET [HasTierPrices] = 1 -WHERE [Id] IN (SELECT [ProductId] FROM [dbo].[TierPrice]) -GO - ---drop column -IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Product]') and NAME='SpecialPrice') -BEGIN - ALTER TABLE [Product] DROP COLUMN [SpecialPrice] -END -GO - ---drop column -IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Product]') and NAME='SpecialPriceStartDateTimeUtc') -BEGIN - ALTER TABLE [Product] DROP COLUMN [SpecialPriceStartDateTimeUtc] -END -GO - ---drop column -IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Product]') and NAME='SpecialPriceEndDateTimeUtc') -BEGIN - ALTER TABLE [Product] DROP COLUMN [SpecialPriceEndDateTimeUtc] -END -GO - ---delete setting -DELETE FROM [Setting] -WHERE [name] = N'producteditordettings.specialprice' -GO - ---delete setting -DELETE FROM [Setting] -WHERE [name] = N'producteditordettings.specialpricestartdate' -GO - ---delete setting -DELETE FROM [Setting] -WHERE [name] = N'producteditordettings.specialpriceenddate' -GO - ---add a new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[TierPrice]') and NAME='StartDateTimeUtc') -BEGIN - ALTER TABLE [TierPrice] - ADD [StartDateTimeUtc] datetime NULL -END -GO - ---add a new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[TierPrice]') and NAME='EndDateTimeUtc') -BEGIN - ALTER TABLE [TierPrice] - ADD [EndDateTimeUtc] datetime NULL -END -GO - -IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[OrderItem]') and NAME='VatRate') - EXEC sp_rename 'OrderItem.VatRate', 'TaxRate', 'COLUMN' -GO - ---add a new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[OrderItem]') and NAME='TaxRate') -BEGIN - ALTER TABLE [OrderItem] - ADD [TaxRate] decimal(18,4) NULL -END -GO - -IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[OrderItem]') and NAME='TaxRate') -BEGIN - - UPDATE OrderItem - SET [TaxRate] = 0 - WHERE ([TaxRate] IS NULL) AND (PriceInclTax = PriceExclTax) - - UPDATE OrderItem - SET [TaxRate] = round((PriceInclTax / PriceExclTax - 1) * 100, 2) - WHERE ([TaxRate] IS NULL) AND PriceExclTax <> 0 - - ALTER TABLE [OrderItem] - ALTER COLUMN [TaxRate] DECIMAL(18,4) NOT NULL -END -GO - ---add a new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='InvoiceId') -BEGIN - ALTER TABLE [dbo].[Order] - ADD InvoiceId NVARCHAR(20) NULL; -END - -GO -DROP INDEX IF EXISTS [UI_Order_InvoiceId] ON [dbo].[Order] -GO - -IF NOT EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'[dbo].[Order]') AND name = N'UI_Order_InvoiceId') -BEGIN - CREATE UNIQUE NONCLUSTERED INDEX UI_Order_InvoiceId - ON dbo.[Order](InvoiceId, StoreId) - WHERE InvoiceId IS NOT NULL; -END -GO - ---add a new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='InvoiceDateUtc') -BEGIN - ALTER TABLE [dbo].[Order] - ADD InvoiceDateUtc DATETIME NULL; -END - -GO - ---add a new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='OrderAmount') -BEGIN - ALTER TABLE [dbo].[Order] - ADD OrderAmount DECIMAL(18,4) NULL; -END - -GO - -IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='OrderAmount') -BEGIN - - UPDATE [dbo].[Order] - SET [OrderAmount] = OrderSubtotalExclTax - ISNULL(OrderSubTotalDiscountExclTax, 0) + ISNULL(OrderShippingExclTax, 0) + ISNULL(PaymentMethodAdditionalFeeExclTax, 0) - ISNULL(OrderDiscount, 0) - WHERE CustomerTaxDisplayTypeId = 10 AND OrderSubtotalExclTax IS NOT NULL - - UPDATE [dbo].[Order] - SET [OrderAmount] = OrderSubtotalInclTax - ISNULL(OrderSubTotalDiscountInclTax, 0) + ISNULL(OrderShippingInclTax, 0) + ISNULL(PaymentMethodAdditionalFeeInclTax, 0) - ISNULL(OrderDiscount, 0) - ISNULL(OrderTax, 0) - WHERE CustomerTaxDisplayTypeId = 0 AND OrderSubtotalInclTax IS NOT NULL -END - -GO - ---add a new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='OrderAmountIncl') -BEGIN - ALTER TABLE [dbo].[Order] - ADD OrderAmountIncl DECIMAL(18,4) NULL; -END - -GO - -IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='OrderAmountIncl') -BEGIN - - UPDATE [dbo].[Order] - SET [OrderAmountIncl] = OrderSubtotalExclTax - ISNULL(OrderSubTotalDiscountExclTax, 0) + ISNULL(OrderShippingExclTax, 0) + ISNULL(PaymentMethodAdditionalFeeExclTax, 0) - ISNULL(OrderDiscount, 0) + ISNULL(OrderTax, 0) - WHERE CustomerTaxDisplayTypeId = 10 AND OrderSubtotalExclTax IS NOT NULL - - UPDATE [dbo].[Order] - SET [OrderAmountIncl] = OrderSubtotalInclTax - ISNULL(OrderSubTotalDiscountInclTax, 0) + ISNULL(OrderShippingInclTax, 0) + ISNULL(PaymentMethodAdditionalFeeInclTax, 0) - ISNULL(OrderDiscount, 0) - WHERE CustomerTaxDisplayTypeId = 0 AND OrderSubtotalExclTax IS NOT NULL -END - -GO - ---add a new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='OrderDiscountIncl') -BEGIN - ALTER TABLE [dbo].[Order] - ADD OrderDiscountIncl DECIMAL(18,4) NULL; -END -GO -IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='OrderDiscountIncl') - UPDATE [dbo].[Order] - SET [OrderDiscount] = OrderSubtotalExclTax - ISNULL(OrderSubTotalDiscountExclTax, 0) + ISNULL(OrderShippingExclTax, 0) + ISNULL(PaymentMethodAdditionalFeeExclTax, 0) + ISNULL(OrderTax, 0), - [OrderDiscountIncl] = [OrderDiscount] - WHERE CustomerTaxDisplayTypeId = 10 - - UPDATE [dbo].[Order] - SET - [OrderDiscountIncl] = OrderSubtotalInclTax - ISNULL(OrderSubTotalDiscountInclTax, 0) + ISNULL(OrderShippingInclTax, 0) + ISNULL(PaymentMethodAdditionalFeeInclTax, 0) - WHERE CustomerTaxDisplayTypeId = 0 -GO - -IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='OrderDiscountIncl') -ALTER TABLE [Order] ALTER COLUMN OrderDiscountIncl DECIMAL(18,4) NOT NULL -GO - ---add a new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='OrderShippingNonTaxable') -BEGIN - ALTER TABLE [dbo].[Order] - ADD OrderShippingNonTaxable DECIMAL(18,4) NULL; -END -GO - ---init -IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='OrderShippingNonTaxable') - UPDATE [dbo].[Order] - SET OrderShippingNonTaxable = 0 - where OrderShippingNonTaxable is Null - /*update old orders? like. Will not work for taxempted customers - [OrderShippingNonTax] = OrderShippingExclTax, - OrderShippingExclTax = 0, - OrderShippingInclTax = 0 - WHERE OrderShippingExclTax = OrderShippingInclTax */ -GO - -IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='OrderShippingNonTaxable') -ALTER TABLE [Order] ALTER COLUMN OrderShippingNonTaxable DECIMAL(18,4) NOT NULL -GO - ---add a new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='PaymentMethodAdditionalFeeNonTaxable') -BEGIN - ALTER TABLE [dbo].[Order] - ADD PaymentMethodAdditionalFeeNonTaxable DECIMAL(18,4) NULL; -END -GO - ---init -IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='PaymentMethodAdditionalFeeNonTaxable') - UPDATE [dbo].[Order] - SET [PaymentMethodAdditionalFeeNonTaxable] = 0 - where [PaymentMethodAdditionalFeeNonTaxable] is Null - /*update old orders? like. Will not work for taxempted customers - [PaymentMethodAdditionalFeeNonTaxable] = PaymentMethodAdditionalFeeInclTax, - PaymentMethodAdditionalFeeExclTax = 0, - PaymentMethodAdditionalFeeInclTax = 0 - WHERE PaymentMethodAdditionalFeeInclTax = PaymentMethodAdditionalFeeExclTax */ -GO - -IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='PaymentMethodAdditionalFeeNonTaxable') -ALTER TABLE [Order] ALTER COLUMN PaymentMethodAdditionalFeeNonTaxable DECIMAL(18,4) NOT NULL -GO - ---add a new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='EarnedRewardPointsBaseAmountIncl') -BEGIN - ALTER TABLE [dbo].[Order] - ADD EarnedRewardPointsBaseAmountIncl DECIMAL(18,4) NULL; -END -GO -IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='EarnedRewardPointsBaseAmountIncl') -UPDATE [dbo].[Order] - SET EarnedRewardPointsBaseAmountIncl = 0 - WHERE EarnedRewardPointsBaseAmountIncl IS NULL -GO -IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='EarnedRewardPointsBaseAmountIncl') -ALTER TABLE [Order] ALTER COLUMN EarnedRewardPointsBaseAmountIncl DECIMAL(18,4) NOT NULL -GO - ---add a new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='EarnedRewardPointsBaseAmountExcl') -BEGIN - ALTER TABLE [dbo].[Order] - ADD EarnedRewardPointsBaseAmountExcl DECIMAL(18,4) NULL; -END -GO -IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='EarnedRewardPointsBaseAmountExcl') -UPDATE [dbo].[Order] - SET EarnedRewardPointsBaseAmountExcl = 0 - WHERE EarnedRewardPointsBaseAmountExcl IS NULL -GO -IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='EarnedRewardPointsBaseAmountExcl') -ALTER TABLE [Order] ALTER COLUMN EarnedRewardPointsBaseAmountExcl DECIMAL(18,4) NOT NULL -GO - ---a stored procedure update -IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[ProductLoadAllPaged]') AND OBJECTPROPERTY(object_id,N'IsProcedure') = 1) -DROP PROCEDURE [ProductLoadAllPaged] -GO -CREATE PROCEDURE [dbo].[ProductLoadAllPaged] -( - @CategoryIds nvarchar(MAX) = null, --a list of category IDs (comma-separated list). e.g. 1,2,3 - @ManufacturerId int = 0, - @StoreId int = 0, - @VendorId int = 0, - @WarehouseId int = 0, - @ProductTypeId int = null, --product type identifier, null - load all products - @VisibleIndividuallyOnly bit = 0, --0 - load all products , 1 - "visible indivially" only - @MarkedAsNewOnly bit = 0, --0 - load all products , 1 - "marked as new" only - @ProductTagId int = 0, - @FeaturedProducts bit = null, --0 featured only , 1 not featured only, null - load all products - @PriceMin decimal(18, 4) = null, - @PriceMax decimal(18, 4) = null, - @Keywords nvarchar(4000) = null, - @SearchDescriptions bit = 0, --a value indicating whether to search by a specified "keyword" in product descriptions - @SearchManufacturerPartNumber bit = 0, -- a value indicating whether to search by a specified "keyword" in manufacturer part number - @SearchSku bit = 0, --a value indicating whether to search by a specified "keyword" in product SKU - @SearchProductTags bit = 0, --a value indicating whether to search by a specified "keyword" in product tags - @UseFullTextSearch bit = 0, - @FullTextMode int = 0, --0 - using CONTAINS with , 5 - using CONTAINS and OR with , 10 - using CONTAINS and AND with - @FilteredSpecs nvarchar(MAX) = null, --filter by specification attribute options (comma-separated list of IDs). e.g. 14,15,16 - @LanguageId int = 0, - @OrderBy int = 0, --0 - position, 5 - Name: A to Z, 6 - Name: Z to A, 10 - Price: Low to High, 11 - Price: High to Low, 15 - creation date - @AllowedCustomerRoleIds nvarchar(MAX) = null, --a list of customer role IDs (comma-separated list) for which a product should be shown (if a subjet to ACL) - @PageIndex int = 0, - @PageSize int = 2147483644, - @ShowHidden bit = 0, - @OverridePublished bit = null, --null - process "Published" property according to "showHidden" parameter, true - load only "Published" products, false - load only "Unpublished" products - @LoadFilterableSpecificationAttributeOptionIds bit = 0, --a value indicating whether we should load the specification attribute option identifiers applied to loaded products (all pages) - @FilterableSpecificationAttributeOptionIds nvarchar(MAX) = null OUTPUT, --the specification attribute option identifiers applied to loaded products (all pages). returned as a comma separated list of identifiers - @TotalRecords int = null OUTPUT -) -AS -BEGIN - - /* Products that filtered by keywords */ - CREATE TABLE #KeywordProducts - ( - [ProductId] int NOT NULL - ) - - DECLARE - @SearchKeywords bit, - @OriginalKeywords nvarchar(4000), - @sql nvarchar(max), - @sql_orderby nvarchar(max) - - SET NOCOUNT ON - - --filter by keywords - SET @Keywords = isnull(@Keywords, '') - SET @Keywords = rtrim(ltrim(@Keywords)) - SET @OriginalKeywords = @Keywords - IF ISNULL(@Keywords, '') != '' - BEGIN - SET @SearchKeywords = 1 - - IF @UseFullTextSearch = 1 - BEGIN - --remove wrong chars (' ") - SET @Keywords = REPLACE(@Keywords, '''', '') - SET @Keywords = REPLACE(@Keywords, '"', '') - - --full-text search - IF @FullTextMode = 0 - BEGIN - --0 - using CONTAINS with - SET @Keywords = ' "' + @Keywords + '*" ' - END - ELSE - BEGIN - --5 - using CONTAINS and OR with - --10 - using CONTAINS and AND with - - --clean multiple spaces - WHILE CHARINDEX(' ', @Keywords) > 0 - SET @Keywords = REPLACE(@Keywords, ' ', ' ') - - DECLARE @concat_term nvarchar(100) - IF @FullTextMode = 5 --5 - using CONTAINS and OR with - BEGIN - SET @concat_term = 'OR' - END - IF @FullTextMode = 10 --10 - using CONTAINS and AND with - BEGIN - SET @concat_term = 'AND' - END - - --now let's build search string - declare @fulltext_keywords nvarchar(4000) - set @fulltext_keywords = N'' - declare @index int - - set @index = CHARINDEX(' ', @Keywords, 0) - - -- if index = 0, then only one field was passed - IF(@index = 0) - set @fulltext_keywords = ' "' + @Keywords + '*" ' - ELSE - BEGIN - DECLARE @first BIT - SET @first = 1 - WHILE @index > 0 - BEGIN - IF (@first = 0) - SET @fulltext_keywords = @fulltext_keywords + ' ' + @concat_term + ' ' - ELSE - SET @first = 0 - - SET @fulltext_keywords = @fulltext_keywords + '"' + SUBSTRING(@Keywords, 1, @index - 1) + '*"' - SET @Keywords = SUBSTRING(@Keywords, @index + 1, LEN(@Keywords) - @index) - SET @index = CHARINDEX(' ', @Keywords, 0) - end - - -- add the last field - IF LEN(@fulltext_keywords) > 0 - SET @fulltext_keywords = @fulltext_keywords + ' ' + @concat_term + ' ' + '"' + SUBSTRING(@Keywords, 1, LEN(@Keywords)) + '*"' - END - SET @Keywords = @fulltext_keywords - END - END - ELSE - BEGIN - --usual search by PATINDEX - SET @Keywords = '%' + @Keywords + '%' - END - --PRINT @Keywords - - --product name - SET @sql = ' - INSERT INTO #KeywordProducts ([ProductId]) - SELECT p.Id - FROM Product p with (NOLOCK) - WHERE ' - IF @UseFullTextSearch = 1 - SET @sql = @sql + 'CONTAINS(p.[Name], @Keywords) ' - ELSE - SET @sql = @sql + 'PATINDEX(@Keywords, p.[Name]) > 0 ' - - - --localized product name - SET @sql = @sql + ' - UNION - SELECT lp.EntityId - FROM LocalizedProperty lp with (NOLOCK) - WHERE - lp.LocaleKeyGroup = N''Product'' - AND lp.LanguageId = ' + ISNULL(CAST(@LanguageId AS nvarchar(max)), '0') + ' - AND lp.LocaleKey = N''Name''' - IF @UseFullTextSearch = 1 - SET @sql = @sql + ' AND CONTAINS(lp.[LocaleValue], @Keywords) ' - ELSE - SET @sql = @sql + ' AND PATINDEX(@Keywords, lp.[LocaleValue]) > 0 ' - - - IF @SearchDescriptions = 1 - BEGIN - --product short description - SET @sql = @sql + ' - UNION - SELECT p.Id - FROM Product p with (NOLOCK) - WHERE ' - IF @UseFullTextSearch = 1 - SET @sql = @sql + 'CONTAINS(p.[ShortDescription], @Keywords) ' - ELSE - SET @sql = @sql + 'PATINDEX(@Keywords, p.[ShortDescription]) > 0 ' - - - --product full description - SET @sql = @sql + ' - UNION - SELECT p.Id - FROM Product p with (NOLOCK) - WHERE ' - IF @UseFullTextSearch = 1 - SET @sql = @sql + 'CONTAINS(p.[FullDescription], @Keywords) ' - ELSE - SET @sql = @sql + 'PATINDEX(@Keywords, p.[FullDescription]) > 0 ' - - - - --localized product short description - SET @sql = @sql + ' - UNION - SELECT lp.EntityId - FROM LocalizedProperty lp with (NOLOCK) - WHERE - lp.LocaleKeyGroup = N''Product'' - AND lp.LanguageId = ' + ISNULL(CAST(@LanguageId AS nvarchar(max)), '0') + ' - AND lp.LocaleKey = N''ShortDescription''' - IF @UseFullTextSearch = 1 - SET @sql = @sql + ' AND CONTAINS(lp.[LocaleValue], @Keywords) ' - ELSE - SET @sql = @sql + ' AND PATINDEX(@Keywords, lp.[LocaleValue]) > 0 ' - - - --localized product full description - SET @sql = @sql + ' - UNION - SELECT lp.EntityId - FROM LocalizedProperty lp with (NOLOCK) - WHERE - lp.LocaleKeyGroup = N''Product'' - AND lp.LanguageId = ' + ISNULL(CAST(@LanguageId AS nvarchar(max)), '0') + ' - AND lp.LocaleKey = N''FullDescription''' - IF @UseFullTextSearch = 1 - SET @sql = @sql + ' AND CONTAINS(lp.[LocaleValue], @Keywords) ' - ELSE - SET @sql = @sql + ' AND PATINDEX(@Keywords, lp.[LocaleValue]) > 0 ' - END - - --manufacturer part number (exact match) - IF @SearchManufacturerPartNumber = 1 - BEGIN - SET @sql = @sql + ' - UNION - SELECT p.Id - FROM Product p with (NOLOCK) - WHERE p.[ManufacturerPartNumber] = @OriginalKeywords ' - END - - --SKU (exact match) - IF @SearchSku = 1 - BEGIN - SET @sql = @sql + ' - UNION - SELECT p.Id - FROM Product p with (NOLOCK) - WHERE p.[Sku] = @OriginalKeywords ' - END - - IF @SearchProductTags = 1 - BEGIN - --product tags (exact match) - SET @sql = @sql + ' - UNION - SELECT pptm.Product_Id - FROM Product_ProductTag_Mapping pptm with(NOLOCK) INNER JOIN ProductTag pt with(NOLOCK) ON pt.Id = pptm.ProductTag_Id - WHERE pt.[Name] = @OriginalKeywords ' - - --localized product tags - SET @sql = @sql + ' - UNION - SELECT pptm.Product_Id - FROM LocalizedProperty lp with (NOLOCK) INNER JOIN Product_ProductTag_Mapping pptm with(NOLOCK) ON lp.EntityId = pptm.ProductTag_Id - WHERE - lp.LocaleKeyGroup = N''ProductTag'' - AND lp.LanguageId = ' + ISNULL(CAST(@LanguageId AS nvarchar(max)), '0') + ' - AND lp.LocaleKey = N''Name'' - AND lp.[LocaleValue] = @OriginalKeywords ' - END - - --PRINT (@sql) - EXEC sp_executesql @sql, N'@Keywords nvarchar(4000), @OriginalKeywords nvarchar(4000)', @Keywords, @OriginalKeywords - - END - ELSE - BEGIN - SET @SearchKeywords = 0 - END - - --filter by category IDs - SET @CategoryIds = isnull(@CategoryIds, '') - CREATE TABLE #FilteredCategoryIds - ( - CategoryId int not null - ) - INSERT INTO #FilteredCategoryIds (CategoryId) - SELECT CAST(data as int) FROM [nop_splitstring_to_table](@CategoryIds, ',') - DECLARE @CategoryIdsCount int - SET @CategoryIdsCount = (SELECT COUNT(1) FROM #FilteredCategoryIds) - - --filter by customer role IDs (access control list) - SET @AllowedCustomerRoleIds = isnull(@AllowedCustomerRoleIds, '') - CREATE TABLE #FilteredCustomerRoleIds - ( - CustomerRoleId int not null - ) - INSERT INTO #FilteredCustomerRoleIds (CustomerRoleId) - SELECT CAST(data as int) FROM [nop_splitstring_to_table](@AllowedCustomerRoleIds, ',') - DECLARE @FilteredCustomerRoleIdsCount int - SET @FilteredCustomerRoleIdsCount = (SELECT COUNT(1) FROM #FilteredCustomerRoleIds) - - --paging - DECLARE @PageLowerBound int - DECLARE @PageUpperBound int - DECLARE @RowsToReturn int - SET @RowsToReturn = @PageSize * (@PageIndex + 1) - SET @PageLowerBound = @PageSize * @PageIndex - SET @PageUpperBound = @PageLowerBound + @PageSize + 1 - - CREATE TABLE #DisplayOrderTmp - ( - [Id] int IDENTITY (1, 1) NOT NULL, - [ProductId] int NOT NULL - ) - - SET @sql = ' - SELECT p.Id - FROM - Product p with (NOLOCK)' - - IF @CategoryIdsCount > 0 - BEGIN - SET @sql = @sql + ' - LEFT JOIN Product_Category_Mapping pcm with (NOLOCK) - ON p.Id = pcm.ProductId' - END - - IF @ManufacturerId > 0 - BEGIN - SET @sql = @sql + ' - LEFT JOIN Product_Manufacturer_Mapping pmm with (NOLOCK) - ON p.Id = pmm.ProductId' - END - - IF ISNULL(@ProductTagId, 0) != 0 - BEGIN - SET @sql = @sql + ' - LEFT JOIN Product_ProductTag_Mapping pptm with (NOLOCK) - ON p.Id = pptm.Product_Id' - END - - --searching by keywords - IF @SearchKeywords = 1 - BEGIN - SET @sql = @sql + ' - JOIN #KeywordProducts kp - ON p.Id = kp.ProductId' - END - - SET @sql = @sql + ' - WHERE - p.Deleted = 0' - - --filter by category - IF @CategoryIdsCount > 0 - BEGIN - SET @sql = @sql + ' - AND pcm.CategoryId IN (SELECT CategoryId FROM #FilteredCategoryIds)' - - IF @FeaturedProducts IS NOT NULL - BEGIN - SET @sql = @sql + ' - AND pcm.IsFeaturedProduct = ' + CAST(@FeaturedProducts AS nvarchar(max)) - END - END - - --filter by manufacturer - IF @ManufacturerId > 0 - BEGIN - SET @sql = @sql + ' - AND pmm.ManufacturerId = ' + CAST(@ManufacturerId AS nvarchar(max)) - - IF @FeaturedProducts IS NOT NULL - BEGIN - SET @sql = @sql + ' - AND pmm.IsFeaturedProduct = ' + CAST(@FeaturedProducts AS nvarchar(max)) - END - END - - --filter by vendor - IF @VendorId > 0 - BEGIN - SET @sql = @sql + ' - AND p.VendorId = ' + CAST(@VendorId AS nvarchar(max)) - END - - --filter by warehouse - IF @WarehouseId > 0 - BEGIN - --we should also ensure that 'ManageInventoryMethodId' is set to 'ManageStock' (1) - --but we skip it in order to prevent hard-coded values (e.g. 1) and for better performance - SET @sql = @sql + ' - AND - ( - (p.UseMultipleWarehouses = 0 AND - p.WarehouseId = ' + CAST(@WarehouseId AS nvarchar(max)) + ') - OR - (p.UseMultipleWarehouses > 0 AND - EXISTS (SELECT 1 FROM ProductWarehouseInventory [pwi] - WHERE [pwi].WarehouseId = ' + CAST(@WarehouseId AS nvarchar(max)) + ' AND [pwi].ProductId = p.Id)) - )' - END - - --filter by product type - IF @ProductTypeId is not null - BEGIN - SET @sql = @sql + ' - AND p.ProductTypeId = ' + CAST(@ProductTypeId AS nvarchar(max)) - END - - --filter by "visible individually" - IF @VisibleIndividuallyOnly = 1 - BEGIN - SET @sql = @sql + ' - AND p.VisibleIndividually = 1' - END - - --filter by "marked as new" - IF @MarkedAsNewOnly = 1 - BEGIN - SET @sql = @sql + ' - AND p.MarkAsNew = 1 - AND (getutcdate() BETWEEN ISNULL(p.MarkAsNewStartDateTimeUtc, ''1/1/1900'') and ISNULL(p.MarkAsNewEndDateTimeUtc, ''1/1/2999''))' - END - - --filter by product tag - IF ISNULL(@ProductTagId, 0) != 0 - BEGIN - SET @sql = @sql + ' - AND pptm.ProductTag_Id = ' + CAST(@ProductTagId AS nvarchar(max)) - END - - --"Published" property - IF (@OverridePublished is null) - BEGIN - --process according to "showHidden" - IF @ShowHidden = 0 - BEGIN - SET @sql = @sql + ' - AND p.Published = 1' - END - END - ELSE IF (@OverridePublished = 1) - BEGIN - --published only - SET @sql = @sql + ' - AND p.Published = 1' - END - ELSE IF (@OverridePublished = 0) - BEGIN - --unpublished only - SET @sql = @sql + ' - AND p.Published = 0' - END - - --show hidden - IF @ShowHidden = 0 - BEGIN - SET @sql = @sql + ' - AND p.Deleted = 0 - AND (getutcdate() BETWEEN ISNULL(p.AvailableStartDateTimeUtc, ''1/1/1900'') and ISNULL(p.AvailableEndDateTimeUtc, ''1/1/2999''))' - END - - --min price - IF @PriceMin is not null - BEGIN - SET @sql = @sql + ' - AND (p.Price >= ' + CAST(@PriceMin AS nvarchar(max)) + ')' - END - - --max price - IF @PriceMax is not null - BEGIN - SET @sql = @sql + ' - AND (p.Price <= ' + CAST(@PriceMax AS nvarchar(max)) + ')' - END - - --show hidden and ACL - IF @ShowHidden = 0 and @FilteredCustomerRoleIdsCount > 0 - BEGIN - SET @sql = @sql + ' - AND (p.SubjectToAcl = 0 OR EXISTS ( - SELECT 1 FROM #FilteredCustomerRoleIds [fcr] - WHERE - [fcr].CustomerRoleId IN ( - SELECT [acl].CustomerRoleId - FROM [AclRecord] acl with (NOLOCK) - WHERE [acl].EntityId = p.Id AND [acl].EntityName = ''Product'' - ) - ))' - END - - --filter by store - IF @StoreId > 0 - BEGIN - SET @sql = @sql + ' - AND (p.LimitedToStores = 0 OR EXISTS ( - SELECT 1 FROM [StoreMapping] sm with (NOLOCK) - WHERE [sm].EntityId = p.Id AND [sm].EntityName = ''Product'' and [sm].StoreId=' + CAST(@StoreId AS nvarchar(max)) + ' - ))' - END - - --prepare filterable specification attribute option identifier (if requested) - IF @LoadFilterableSpecificationAttributeOptionIds = 1 - BEGIN - CREATE TABLE #FilterableSpecs - ( - [SpecificationAttributeOptionId] int NOT NULL - ) - DECLARE @sql_filterableSpecs nvarchar(max) - SET @sql_filterableSpecs = ' - INSERT INTO #FilterableSpecs ([SpecificationAttributeOptionId]) - SELECT DISTINCT [psam].SpecificationAttributeOptionId - FROM [Product_SpecificationAttribute_Mapping] [psam] WITH (NOLOCK) - WHERE [psam].[AllowFiltering] = 1 - AND [psam].[ProductId] IN (' + @sql + ')' - - EXEC sp_executesql @sql_filterableSpecs - - --build comma separated list of filterable identifiers - SELECT @FilterableSpecificationAttributeOptionIds = COALESCE(@FilterableSpecificationAttributeOptionIds + ',' , '') + CAST(SpecificationAttributeOptionId as nvarchar(4000)) - FROM #FilterableSpecs - - DROP TABLE #FilterableSpecs - END - - --filter by specification attribution options - SET @FilteredSpecs = isnull(@FilteredSpecs, '') - CREATE TABLE #FilteredSpecs - ( - SpecificationAttributeOptionId int not null - ) - INSERT INTO #FilteredSpecs (SpecificationAttributeOptionId) - SELECT CAST(data as int) FROM [nop_splitstring_to_table](@FilteredSpecs, ',') - - CREATE TABLE #FilteredSpecsWithAttributes - ( - SpecificationAttributeId int not null, - SpecificationAttributeOptionId int not null - ) - INSERT INTO #FilteredSpecsWithAttributes (SpecificationAttributeId, SpecificationAttributeOptionId) - SELECT sao.SpecificationAttributeId, fs.SpecificationAttributeOptionId - FROM #FilteredSpecs fs INNER JOIN SpecificationAttributeOption sao ON sao.Id = fs.SpecificationAttributeOptionId - ORDER BY sao.SpecificationAttributeId - - DECLARE @SpecAttributesCount int - SET @SpecAttributesCount = (SELECT COUNT(1) FROM #FilteredSpecsWithAttributes) - IF @SpecAttributesCount > 0 - BEGIN - --do it for each specified specification option - DECLARE @SpecificationAttributeOptionId int - DECLARE @SpecificationAttributeId int - DECLARE @LastSpecificationAttributeId int - SET @LastSpecificationAttributeId = 0 - DECLARE cur_SpecificationAttributeOption CURSOR FOR - SELECT SpecificationAttributeId, SpecificationAttributeOptionId - FROM #FilteredSpecsWithAttributes - - OPEN cur_SpecificationAttributeOption - FOREACH: - FETCH NEXT FROM cur_SpecificationAttributeOption INTO @SpecificationAttributeId, @SpecificationAttributeOptionId - IF (@LastSpecificationAttributeId <> 0 AND @SpecificationAttributeId <> @LastSpecificationAttributeId OR @@FETCH_STATUS <> 0) - SET @sql = @sql + ' - AND p.Id in (select psam.ProductId from [Product_SpecificationAttribute_Mapping] psam with (NOLOCK) where psam.AllowFiltering = 1 and psam.SpecificationAttributeOptionId IN (SELECT SpecificationAttributeOptionId FROM #FilteredSpecsWithAttributes WHERE SpecificationAttributeId = ' + CAST(@LastSpecificationAttributeId AS nvarchar(max)) + '))' - SET @LastSpecificationAttributeId = @SpecificationAttributeId - IF @@FETCH_STATUS = 0 GOTO FOREACH - CLOSE cur_SpecificationAttributeOption - DEALLOCATE cur_SpecificationAttributeOption - END - - --sorting - SET @sql_orderby = '' - IF @OrderBy = 5 /* Name: A to Z */ - SET @sql_orderby = ' p.[Name] ASC' - ELSE IF @OrderBy = 6 /* Name: Z to A */ - SET @sql_orderby = ' p.[Name] DESC' - ELSE IF @OrderBy = 10 /* Price: Low to High */ - SET @sql_orderby = ' p.[Price] ASC' - ELSE IF @OrderBy = 11 /* Price: High to Low */ - SET @sql_orderby = ' p.[Price] DESC' - ELSE IF @OrderBy = 15 /* creation date */ - SET @sql_orderby = ' p.[CreatedOnUtc] DESC' - ELSE /* default sorting, 0 (position) */ - BEGIN - --category position (display order) - IF @CategoryIdsCount > 0 SET @sql_orderby = ' pcm.DisplayOrder ASC' - - --manufacturer position (display order) - IF @ManufacturerId > 0 - BEGIN - IF LEN(@sql_orderby) > 0 SET @sql_orderby = @sql_orderby + ', ' - SET @sql_orderby = @sql_orderby + ' pmm.DisplayOrder ASC' - END - - --name - IF LEN(@sql_orderby) > 0 SET @sql_orderby = @sql_orderby + ', ' - SET @sql_orderby = @sql_orderby + ' p.[Name] ASC' - END - - SET @sql = @sql + ' - ORDER BY' + @sql_orderby - - SET @sql = ' - INSERT INTO #DisplayOrderTmp ([ProductId])' + @sql - - --PRINT (@sql) - EXEC sp_executesql @sql - - DROP TABLE #FilteredCategoryIds - DROP TABLE #FilteredSpecs - DROP TABLE #FilteredSpecsWithAttributes - DROP TABLE #FilteredCustomerRoleIds - DROP TABLE #KeywordProducts - - CREATE TABLE #PageIndex - ( - [IndexId] int IDENTITY (1, 1) NOT NULL, - [ProductId] int NOT NULL - ) - INSERT INTO #PageIndex ([ProductId]) - SELECT ProductId - FROM #DisplayOrderTmp - GROUP BY ProductId - ORDER BY min([Id]) - - --total records - SET @TotalRecords = @@rowcount - - DROP TABLE #DisplayOrderTmp - - --return products - SELECT TOP (@RowsToReturn) - p.* - FROM - #PageIndex [pi] - INNER JOIN Product p with (NOLOCK) on p.Id = [pi].[ProductId] - WHERE - [pi].IndexId > @PageLowerBound AND - [pi].IndexId < @PageUpperBound - ORDER BY - [pi].IndexId - - DROP TABLE #PageIndex -END -GO - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'ordersettings.deactivategiftcardsafterdeletingorder') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'ordersettings.deactivategiftcardsafterdeletingorder', N'False', 0) -END -GO - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'ordersettings.completeorderwhendelivered') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'ordersettings.completeorderwhendelivered', N'True', 0) -END -GO - ---new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[NewsComment]') and NAME='IsApproved') -BEGIN - ALTER TABLE [NewsComment] - ADD [IsApproved] bit NULL -END -GO - -UPDATE [NewsComment] -SET [IsApproved] = 1 -WHERE [IsApproved] IS NULL -GO - -ALTER TABLE [NewsComment] ALTER COLUMN [IsApproved] bit NOT NULL -GO - ---new activity type -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditNewsComment') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'EditNewsComment', N'Edited a news comment', N'true') -END -GO - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'newssettings.newscommentsmustbeapproved') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'newssettings.newscommentsmustbeapproved', N'False', 0) -END -GO - ---new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[BlogComment]') and NAME='IsApproved') -BEGIN - ALTER TABLE [BlogComment] - ADD [IsApproved] bit NULL -END -GO - -UPDATE [BlogComment] -SET [IsApproved] = 1 -WHERE [IsApproved] IS NULL -GO - -ALTER TABLE [BlogComment] ALTER COLUMN [IsApproved] bit NOT NULL -GO - ---new activity type -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditBlogComment') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'EditBlogComment', N'Edited a blog comment', N'true') -END -GO - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'blogsettings.blogcommentsmustbeapproved') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'blogsettings.blogcommentsmustbeapproved', N'False', 0) -END -GO - ---drop column -IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[News]') and NAME='CommentCount') -BEGIN - ALTER TABLE [News] DROP COLUMN [CommentCount] -END -GO - ---drop column -IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[BlogPost]') and NAME='CommentCount') -BEGIN - ALTER TABLE [BlogPost] DROP COLUMN [CommentCount] -END -GO - --- new message template - IF NOT EXISTS (SELECT 1 FROM [dbo].[MessageTemplate] WHERE [Name] = N'NewReturnRequest.CustomerNotification') - BEGIN - DECLARE @NewLine AS CHAR(2) = CHAR(13) + CHAR(10) - INSERT [dbo].[MessageTemplate] ([Name], [BccEmailAddresses], [Subject], [Body], [IsActive], [AttachedDownloadId], [EmailAccountId], [LimitedToStores], [DelayPeriodId]) - VALUES (N'NewReturnRequest.CustomerNotification', NULL, N'%Store.Name%. New return request.', N'

    ' + @NewLine + '%Store.Name%' + @NewLine + '
    ' + @NewLine + '
    ' + @NewLine + 'Hello %Customer.FullName%!' + @NewLine + '
    ' + @NewLine + 'You have just submitted a new return request. Details are below:' + @NewLine + '
    ' + @NewLine + 'Request ID: %ReturnRequest.CustomNumber%' + @NewLine + '
    ' + @NewLine + 'Product: %ReturnRequest.Product.Quantity% x Product: %ReturnRequest.Product.Name%' + @NewLine + '
    ' + @NewLine + 'Reason for return: %ReturnRequest.Reason%' + @NewLine + '
    ' + @NewLine + 'Requested action: %ReturnRequest.RequestedAction%' + @NewLine + '
    ' + @NewLine + 'Customer comments:' + @NewLine + '
    ' + @NewLine + '%ReturnRequest.CustomerComment%' + @NewLine + '

    ' + @NewLine, 1, 0, 0, 0, 0) - END - GO - - --new table -IF NOT EXISTS (SELECT 1 FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[StockQuantityHistory]') and OBJECTPROPERTY(object_id, N'IsUserTable') = 1) -BEGIN - CREATE TABLE [dbo].[StockQuantityHistory] - ( - [Id] int IDENTITY(1,1) NOT NULL, - [ProductId] int NOT NULL, - [CombinationId] int NULL, - [WarehouseId] int NULL, - [QuantityAdjustment] int NOT NULL, - [StockQuantity] int NOT NULL, - [Message] NVARCHAR (MAX) NULL, - [CreatedOnUtc] datetime NOT NULL - PRIMARY KEY CLUSTERED - ( - [Id] ASC - ) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) - ) -END -GO - -IF EXISTS (SELECT 1 FROM sys.objects WHERE name = 'StockQuantityHistory_Product' AND parent_object_id = Object_id('StockQuantityHistory') AND Objectproperty(object_id, N'IsForeignKey') = 1) -BEGIN - ALTER TABLE [dbo].StockQuantityHistory - DROP CONSTRAINT StockQuantityHistory_Product -END -GO - -ALTER TABLE [dbo].[StockQuantityHistory] WITH CHECK ADD CONSTRAINT [StockQuantityHistory_Product] FOREIGN KEY([ProductId]) -REFERENCES [dbo].[Product] ([Id]) -ON DELETE CASCADE -GO - ---initial stock quantity history -DECLARE cur_initialhistory CURSOR FOR -SELECT [Product].Id, NULL, [Product].WarehouseId, [Product].StockQuantity, NULL -FROM [Product] -UNION ALL -SELECT [ProductAttributeCombination].ProductId, [ProductAttributeCombination].Id, NULL, [ProductAttributeCombination].StockQuantity, NULL -FROM [ProductAttributeCombination] -UNION ALL -SELECT [ProductWarehouseInventory].ProductId, NULL, [ProductWarehouseInventory].WarehouseId, [ProductWarehouseInventory].StockQuantity, [ProductWarehouseInventory].Id -FROM [ProductWarehouseInventory] - -DECLARE @productId int -DECLARE @combinationId int -DECLARE @warehouseId int -DECLARE @quantity int -DECLARE @warehouseInventoryId int - -OPEN cur_initialhistory -FETCH NEXT FROM cur_initialhistory INTO @productId, @combinationId, @warehouseId, @quantity, @warehouseInventoryId - -WHILE @@FETCH_STATUS = 0 -BEGIN - IF @warehouseId = 0 - BEGIN - SET @warehouseId = NULL; - END - - DECLARE @message nvarchar(200) - SET @message = 'Initialization of history table (original quantity set) during upgrade from a previous version' - IF @warehouseInventoryId IS NOT NULL - BEGIN - SET @message = 'Multiple warehouses. ' + @message; - END - - IF (@quantity IS NOT NULL AND @quantity <> 0 AND - NOT EXISTS (SELECT 1 FROM [StockQuantityHistory] WHERE ProductId = @productId AND - (CombinationId = @combinationId OR (CombinationId IS NULL AND @combinationId IS NULL)) AND (WarehouseId = @warehouseId OR (WarehouseId IS NULL AND @warehouseId IS NULL)))) - BEGIN - INSERT INTO [StockQuantityHistory] - ([ProductId], [CombinationId], [WarehouseId], [QuantityAdjustment], [StockQuantity], [Message], [CreatedOnUtc]) - VALUES - (@productId, @combinationId, @warehouseId, @quantity, @quantity, @message, GETUTCDATE()) - END - - FETCH NEXT FROM cur_initialhistory INTO @productId, @combinationId, @warehouseId, @quantity, @warehouseInventoryId -END - -CLOSE cur_initialhistory -DEALLOCATE cur_initialhistory -GO - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'producteditorsettings.stockquantityhistory') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'producteditorsettings.stockquantityhistory', N'False', 0) -END -GO - - ---new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Customer]') and NAME='RequireReLogin') -BEGIN - ALTER TABLE [Customer] - ADD [RequireReLogin] bit NULL -END -GO - -UPDATE [Customer] -SET [RequireReLogin] = 0 -WHERE [RequireReLogin] IS NULL -GO - -ALTER TABLE [Customer] ALTER COLUMN [RequireReLogin] bit NOT NULL -GO - - ---delete setting -DELETE FROM [Setting] -WHERE [name] = N'rewardpointssettings.PointsForPurchases_Awarded' -GO - ---delete setting -DELETE FROM [Setting] -WHERE [name] = N'rewardpointssettings.PointsForPurchases_Canceled' -GO - ---delete setting -DELETE FROM [Setting] -WHERE [name] = N'ordersettings.GiftCards_Activated_OrderStatusId' -GO - ---delete setting -DELETE FROM [Setting] -WHERE [name] = N'ordersettings.GiftCards_Deactivated_OrderStatusId' -GO - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'ordersettings.activategiftcardsaftercompletingorder') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'ordersettings.activategiftcardsaftercompletingorder', N'False', 0) -END -GO - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'ordersettings.deactivategiftcardsaftercancellingorder') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'ordersettings.deactivategiftcardsaftercancellingorder', N'False', 0) -END -GO - - ---update a stored procedure -IF EXISTS ( - SELECT * - FROM sys.objects - WHERE object_id = OBJECT_ID(N'[LanguagePackImport]') AND OBJECTPROPERTY(object_id,N'IsProcedure') = 1) -DROP PROCEDURE [LanguagePackImport] -GO -CREATE PROCEDURE [dbo].[LanguagePackImport] -( - @LanguageId int, - @XmlPackage xml, - @UpdateExistingResources bit -) -AS -BEGIN - IF EXISTS(SELECT * FROM [Language] WHERE [Id] = @LanguageId) - BEGIN - CREATE TABLE #LocaleStringResourceTmp - ( - [LanguageId] [int] NOT NULL, - [ResourceName] [nvarchar](200) NOT NULL, - [ResourceValue] [nvarchar](MAX) NOT NULL - ) - - INSERT INTO #LocaleStringResourceTmp (LanguageId, ResourceName, ResourceValue) - SELECT @LanguageId, nref.value('@Name', 'nvarchar(200)'), nref.value('Value[1]', 'nvarchar(MAX)') - FROM @XmlPackage.nodes('//Language/LocaleResource') AS R(nref) - - DECLARE @ResourceName nvarchar(200) - DECLARE @ResourceValue nvarchar(MAX) - DECLARE cur_localeresource CURSOR FOR - SELECT LanguageId, ResourceName, ResourceValue - FROM #LocaleStringResourceTmp - OPEN cur_localeresource - FETCH NEXT FROM cur_localeresource INTO @LanguageId, @ResourceName, @ResourceValue - WHILE @@FETCH_STATUS = 0 - BEGIN - IF (EXISTS (SELECT 1 FROM [LocaleStringResource] WHERE LanguageId=@LanguageId AND ResourceName=@ResourceName)) - BEGIN - IF (@UpdateExistingResources = 1) - BEGIN - UPDATE [LocaleStringResource] - SET [ResourceValue]=@ResourceValue - WHERE LanguageId=@LanguageId AND ResourceName=@ResourceName - END - END - ELSE - BEGIN - INSERT INTO [LocaleStringResource] - ( - [LanguageId], - [ResourceName], - [ResourceValue] - ) - VALUES - ( - @LanguageId, - @ResourceName, - @ResourceValue - ) - END - - - FETCH NEXT FROM cur_localeresource INTO @LanguageId, @ResourceName, @ResourceValue - END - CLOSE cur_localeresource - DEALLOCATE cur_localeresource - - DROP TABLE #LocaleStringResourceTmp - END -END -GO - - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'paymentsettings.skippaymentInfostepforredirectionpaymentmethods') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'paymentsettings.skippaymentInfostepforredirectionpaymentmethods', N'False', 0) -END -GO - - ---updated some indexes (required for case sensitive SQL Server collations) -IF EXISTS (SELECT 1 from sys.indexes WHERE [NAME]=N'IX_NewsletterSubscription_Email_StoreId' and object_id=object_id(N'[dbo].[NewsLetterSubscription]')) -BEGIN - DROP INDEX [IX_NewsletterSubscription_Email_StoreId] ON [NewsLetterSubscription] -END -GO -CREATE NONCLUSTERED INDEX [IX_NewsletterSubscription_Email_StoreId] ON [NewsLetterSubscription] ([Email] ASC, [StoreId] ASC) -GO - -IF EXISTS (SELECT 1 from sys.indexes WHERE [NAME]=N'IX_Product_ShowOnHomepage' and object_id=object_id(N'[dbo].[Product]')) -BEGIN - DROP INDEX [IX_Product_ShowOnHomepage] ON [Product] -END -GO -CREATE NONCLUSTERED INDEX [IX_Product_ShowOnHomepage] ON [Product] ([ShowOnHomePage] ASC) -GO - ---update a stored procedure -IF EXISTS ( - SELECT * - FROM sys.objects - WHERE object_id = OBJECT_ID(N'[DeleteGuests]') AND OBJECTPROPERTY(object_id,N'IsProcedure') = 1) -DROP PROCEDURE [DeleteGuests] -GO -CREATE PROCEDURE [dbo].[DeleteGuests] -( - @OnlyWithoutShoppingCart bit = 1, - @CreatedFromUtc datetime, - @CreatedToUtc datetime, - @TotalRecordsDeleted int = null OUTPUT -) -AS -BEGIN - CREATE TABLE #tmp_guests (CustomerId int) - - INSERT #tmp_guests (CustomerId) - SELECT [Id] FROM [Customer] c with (NOLOCK) - WHERE - --created from - ((@CreatedFromUtc is null) OR (c.[CreatedOnUtc] > @CreatedFromUtc)) - AND - --created to - ((@CreatedToUtc is null) OR (c.[CreatedOnUtc] < @CreatedToUtc)) - AND - --shopping cart items - ((@OnlyWithoutShoppingCart=0) OR (NOT EXISTS(SELECT 1 FROM [ShoppingCartItem] sci with (NOLOCK) inner join [Customer] with (NOLOCK) on sci.[CustomerId]=c.[Id]))) - AND - --guests only - (EXISTS(SELECT 1 FROM [Customer_CustomerRole_Mapping] ccrm with (NOLOCK) inner join [Customer] with (NOLOCK) on ccrm.[Customer_Id]=c.[Id] inner join [CustomerRole] cr with (NOLOCK) on cr.[Id]=ccrm.[CustomerRole_Id] WHERE cr.[SystemName] = N'Guests')) - AND - --no orders - (NOT EXISTS(SELECT 1 FROM [Order] o with (NOLOCK) inner join [Customer] with (NOLOCK) on o.[CustomerId]=c.[Id])) - AND - --no blog comments - (NOT EXISTS(SELECT 1 FROM [BlogComment] bc with (NOLOCK) inner join [Customer] with (NOLOCK) on bc.[CustomerId]=c.[Id])) - AND - --no news comments - (NOT EXISTS(SELECT 1 FROM [NewsComment] nc with (NOLOCK)inner join [Customer] with (NOLOCK) on nc.[CustomerId]=c.[Id])) - AND - --no product reviews - (NOT EXISTS(SELECT 1 FROM [ProductReview] pr with (NOLOCK) inner join [Customer] with (NOLOCK) on pr.[CustomerId]=c.[Id])) - AND - --no product reviews helpfulness - (NOT EXISTS(SELECT 1 FROM [ProductReviewHelpfulness] prh with (NOLOCK) inner join [Customer] with (NOLOCK) on prh.[CustomerId]=c.[Id])) - AND - --no poll voting - (NOT EXISTS(SELECT 1 FROM [PollVotingRecord] pvr with (NOLOCK) inner join [Customer] with (NOLOCK) on pvr.[CustomerId]=c.[Id])) - AND - --no forum topics - (NOT EXISTS(SELECT 1 FROM [Forums_Topic] ft with (NOLOCK) inner join [Customer] with (NOLOCK) on ft.[CustomerId]=c.[Id])) - AND - --no forum posts - (NOT EXISTS(SELECT 1 FROM [Forums_Post] fp with (NOLOCK) inner join [Customer] with (NOLOCK) on fp.[CustomerId]=c.[Id])) - AND - --no system accounts - (c.IsSystemAccount = 0) - - --delete guests - DELETE [Customer] - WHERE [Id] IN (SELECT [CustomerId] FROM #tmp_guests) - - --delete attributes - DELETE [GenericAttribute] - WHERE ([EntityId] IN (SELECT [CustomerId] FROM #tmp_guests)) - AND - ([KeyGroup] = N'Customer') - - --total records - SELECT @TotalRecordsDeleted = COUNT(1) FROM #tmp_guests - - DROP TABLE #tmp_guests -END -GO - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'commonsettings.sitemapcustomurls') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'commonsettings.sitemapcustomurls', N'', 0) -END -GO - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'shoppingcartsettings.cartssharedbetweenstores') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'shoppingcartsettings.cartssharedbetweenstores', N'False', 0) -END -GO - ---new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Customer]') and NAME='EmailToRevalidate') -BEGIN - ALTER TABLE [Customer] - ADD [EmailToRevalidate] nvarchar(1000) NULL -END -GO - --- new message template - IF NOT EXISTS (SELECT 1 FROM [dbo].[MessageTemplate] WHERE [Name] = N'Customer.EmailRevalidationMessage') - BEGIN - DECLARE @NewLine AS CHAR(2) = CHAR(13) + CHAR(10) - INSERT [dbo].[MessageTemplate] ([Name], [BccEmailAddresses], [Subject], [Body], [IsActive], [AttachedDownloadId], [EmailAccountId], [LimitedToStores], [DelayPeriodId]) - VALUES (N'Customer.EmailRevalidationMessage', NULL, N'%Store.Name%. Email validation.', N'

    ' + @NewLine + '%Store.Name%' + @NewLine + '
    ' + @NewLine + '
    ' + @NewLine + 'Hello %Customer.FullName%!' + @NewLine + '
    ' + @NewLine + 'To validate your new email address click here .' + @NewLine + '
    ' + @NewLine + '
    ' + @NewLine + '%Store.Name%' + @NewLine + '

    ' + @NewLine, 1, 0, 0, 0, 0) - END - GO - ---new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='RewardPointsHistoryEntryId') -BEGIN - ALTER TABLE [Order] - ADD [RewardPointsHistoryEntryId] int NULL -END -GO - -IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='RewardPointsWereAdded') -BEGIN - --column RewardPointsWereAdded was replaced with RewardPointsHistoryEntryId - --ensure that the new column value is not null to specify that reward points were added (earned) to an order - EXEC(' - UPDATE [Order] - SET [RewardPointsHistoryEntryId] = 0 - WHERE [RewardPointsWereAdded] = 1') -END -GO - ---drop column -IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='RewardPointsWereAdded') -BEGIN - ALTER TABLE [Order] DROP COLUMN [RewardPointsWereAdded] -END -GO - - ---update plugin locales (renamed) -UPDATE [LocaleStringResource] -SET [ResourceName] = REPLACE([ResourceName], 'Plugins.Feed.Froogle.','Plugins.Feed.GoogleShopping.') -WHERE [ResourceName] like 'Plugins.Feed.Froogle.%' -GO - ---update settings -UPDATE [Setting] -SET [Name] = REPLACE([Name], 'frooglesettings.','googlesShoppingsettings.') -WHERE [Name] like 'frooglesettings.%' -GO - - ---new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[ProductTemplate]') and NAME='IgnoredProductTypes') -BEGIN - ALTER TABLE [ProductTemplate] - ADD [IgnoredProductTypes] nvarchar(MAX) NULL -END -GO - -UPDATE [ProductTemplate] -SET [IgnoredProductTypes] = '10' -WHERE [ViewPath] = N'ProductTemplate.Simple' -GO - -UPDATE [ProductTemplate] -SET [IgnoredProductTypes] = '5' -WHERE [ViewPath] = N'ProductTemplate.Grouped' -GO - - ---new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[ReturnRequest]') and NAME='UploadedFileId') -BEGIN - ALTER TABLE [ReturnRequest] - ADD [UploadedFileId] int NULL -END -GO - -UPDATE [ReturnRequest] -SET [UploadedFileId] = 0 -WHERE [UploadedFileId] IS NULL -GO - -ALTER TABLE [ReturnRequest] ALTER COLUMN [UploadedFileId] int NOT NULL -GO - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'ordersettings.returnrequestsallowfiles') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'ordersettings.returnrequestsallowfiles', N'False', 0) -END -GO - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'ordersettings.returnrequestsfilemaximumsize') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'ordersettings.returnrequestsfilemaximumsize', N'2048', 0) -END -GO - ---new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[ProductReview]') and NAME='ReplyText') -BEGIN - ALTER TABLE [ProductReview] - ADD [ReplyText] nvarchar(MAX) NULL -END -GO - ---new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[BlogComment]') and NAME='StoreId') -BEGIN - ALTER TABLE [dbo].[BlogComment] - ADD [StoreId] int NULL -END -GO - -DECLARE @DefaultStoreId int -SET @DefaultStoreId = (SELECT TOP (1) Id FROM [dbo].[Store]); ---set default value to store column -UPDATE [dbo].[BlogComment] -SET StoreId = @DefaultStoreId -WHERE StoreId IS NULL -GO - -ALTER TABLE [dbo].[BlogComment] ALTER COLUMN [StoreId] int NOT NULL -GO - -IF EXISTS (SELECT 1 FROM sys.objects WHERE name = 'BlogComment_Store' AND parent_object_id = Object_id('BlogComment') AND Objectproperty(object_id, N'IsForeignKey') = 1) -ALTER TABLE [dbo].[BlogComment] -DROP CONSTRAINT BlogComment_Store -GO - -ALTER TABLE [dbo].[BlogComment] WITH CHECK ADD CONSTRAINT [BlogComment_Store] FOREIGN KEY([StoreId]) -REFERENCES [dbo].[Store] ([Id]) -ON DELETE CASCADE -GO - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'blogsettings.showblogcommentsperstore') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'blogsettings.showblogcommentsperstore', N'False', 0) -END -GO - ---new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[NewsComment]') and NAME='StoreId') -BEGIN - ALTER TABLE [dbo].[NewsComment] - ADD [StoreId] int NULL -END -GO - -DECLARE @DefaultStoreId int -SET @DefaultStoreId = (SELECT TOP (1) Id FROM [dbo].[Store]); ---set default value to store column -UPDATE [dbo].[NewsComment] -SET StoreId = @DefaultStoreId -WHERE StoreId IS NULL -GO - -ALTER TABLE [dbo].[NewsComment] ALTER COLUMN [StoreId] int NOT NULL -GO - -IF EXISTS (SELECT 1 FROM sys.objects WHERE name = 'NewsComment_Store' AND parent_object_id = Object_id('NewsComment') AND Objectproperty(object_id, N'IsForeignKey') = 1) -ALTER TABLE [dbo].[NewsComment] -DROP CONSTRAINT NewsComment_Store -GO - -ALTER TABLE [dbo].[NewsComment] WITH CHECK ADD CONSTRAINT [NewsComment_Store] FOREIGN KEY([StoreId]) -REFERENCES [dbo].[Store] ([Id]) -ON DELETE CASCADE -GO - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'newssettings.shownewscommentsperstore') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'newssettings.shownewscommentsperstore', N'False', 0) -END -GO - - ---new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Vendor]') and NAME='AddressId') -BEGIN - ALTER TABLE [Vendor] - ADD [AddressId] int NULL -END -GO - -UPDATE [Vendor] -SET [AddressId] = 0 -WHERE [AddressId] IS NULL -GO - -ALTER TABLE [Vendor] ALTER COLUMN [AddressId] int NOT NULL -GO - - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'customersettings.failedpasswordallowedattempts') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'customersettings.failedpasswordallowedattempts', N'0', 0) -END -GO - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'customersettings.failedpasswordlockoutminutes') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'customersettings.failedpasswordlockoutminutes', N'30', 0) -END -GO - - ---new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Customer]') and NAME='FailedLoginAttempts') -BEGIN - ALTER TABLE [Customer] - ADD [FailedLoginAttempts] int NULL -END -GO - -UPDATE [Customer] -SET [FailedLoginAttempts] = 0 -WHERE [FailedLoginAttempts] IS NULL -GO - -ALTER TABLE [Customer] ALTER COLUMN [FailedLoginAttempts] int NOT NULL -GO - ---new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Customer]') and NAME='CannotLoginUntilDateUtc') -BEGIN - ALTER TABLE [Customer] - ADD [CannotLoginUntilDateUtc] datetime NULL -END -GO - - --new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'fixedorbycountrystateziptaxsettings.countrystatezipenabled') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'fixedorbycountrystateziptaxsettings.countrystatezipenabled', N'False', 0) -END -GO - ---rename settings -UPDATE [Setting] -SET [Name] = N'tax.taxprovider.fixedorbycountrystatezip.taxcategoryid' + SUBSTRING(name, 40, len(name)) -WHERE [Name] like N'tax.taxprovider.fixedrate.taxcategoryid%' -GO - ---new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Customer]') and NAME='RegisteredInStoreId') -BEGIN - ALTER TABLE [dbo].[Customer] - ADD [RegisteredInStoreId] int NULL -END -GO - -declare @DefaultStoreId int; -if ((select count(id) from [dbo].[Store]) = 1) -set @DefaultStoreId = (select top(1) id from [dbo].[Store]) -else -set @DefaultStoreId = 0; ---set default value to store column -UPDATE [dbo].[Customer] set [RegisteredInStoreId] = @DefaultStoreId where [RegisteredInStoreId] is NULL - -ALTER TABLE [dbo].[Customer] ALTER COLUMN [RegisteredInStoreId] int NOT NULL -GO - - --new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'shippingsettings.considerassociatedproductsdimensions') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'shippingsettings.considerassociatedproductsdimensions', N'True', 0) -END -GO - - --new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'commonsettings.bbcodeeditoropenlinksinnewwindow') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'commonsettings.bbcodeeditoropenlinksinnewwindow', N'false', 0) -END -GO - - --new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'paymentsettings.cancelrecurringpaymentsafterfailedpayment') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'paymentsettings.cancelrecurringpaymentsafterfailedpayment', N'False', 0) -END -GO - ---new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[RecurringPayment]') and NAME='LastPaymentFailed') -BEGIN - ALTER TABLE [RecurringPayment] - ADD [LastPaymentFailed] bit NULL -END -GO - -UPDATE [RecurringPayment] -SET [LastPaymentFailed] = 0 -WHERE [LastPaymentFailed] IS NULL -GO - -ALTER TABLE [RecurringPayment] ALTER COLUMN [LastPaymentFailed] bit NOT NULL -GO - --- new message template -IF NOT EXISTS (SELECT 1 FROM [dbo].[MessageTemplate] WHERE [Name] = N'RecurringPaymentCancelled.CustomerNotification') -BEGIN - DECLARE @NewLine AS CHAR(2) = CHAR(13) + CHAR(10) - INSERT [dbo].[MessageTemplate] ([Name], [BccEmailAddresses], [Subject], [Body], [IsActive], [AttachedDownloadId], [EmailAccountId], [LimitedToStores], [DelayPeriodId]) - VALUES (N'RecurringPaymentCancelled.CustomerNotification', NULL, N'%Store.Name%. Recurring payment cancelled', N'

    ' + @NewLine + '%Store.Name%' + @NewLine + '
    ' + @NewLine + '
    ' + @NewLine + 'Hello %Customer.FullName%,' + @NewLine + '
    ' + @NewLine + '%if (%RecurringPayment.CancelAfterFailedPayment%) It appears your credit card didn''t go through for this recurring payment (%Order.OrderURLForCustomer%)' + @NewLine + '
    ' + @NewLine + 'So your subscription has been canceled. endif% %if (!%RecurringPayment.CancelAfterFailedPayment%) The recurring payment ID=%RecurringPayment.ID% was cancelled. endif%' + @NewLine + '

    ' + @NewLine, 1, 0, 0, 0, 0) -END -GO - --- new message template -IF NOT EXISTS (SELECT 1 FROM [dbo].[MessageTemplate] WHERE [Name] = N'RecurringPaymentFailed.CustomerNotification') -BEGIN - DECLARE @NewLine AS CHAR(2) = CHAR(13) + CHAR(10) - INSERT [dbo].[MessageTemplate] ([Name], [BccEmailAddresses], [Subject], [Body], [IsActive], [AttachedDownloadId], [EmailAccountId], [LimitedToStores], [DelayPeriodId]) - VALUES (N'RecurringPaymentFailed.CustomerNotification', NULL, N'%Store.Name%. Last recurring payment failed', N'

    ' + @NewLine + '%Store.Name%' + @NewLine + '
    ' + @NewLine + '
    ' + @NewLine + 'Hello %Customer.FullName%,' + @NewLine + '
    ' + @NewLine + 'It appears your credit card didn''t go through for this recurring payment (%Order.OrderURLForCustomer%)' + @NewLine + '
    %if (%RecurringPayment.RecurringPaymentType% == "Manual") ' + @NewLine + 'You can recharge balance and manually retry payment or cancel it on the order history page. endif% %if (%RecurringPayment.RecurringPaymentType% == "Automatic") ' + @NewLine + 'You can recharge balance and wait, we will try to make the payment again, or you can cancel it on the order history page. endif%' + @NewLine + '

    ' + @NewLine, 1, 0, 0, 0, 0) -END -GO - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'shoppingcartsettings.renderproductattributeprices') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'shoppingcartsettings.renderproductattributeprices', N'True', 0) -END -GO - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'rewardpointssettings.earnedrewardpointsaretaxable') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'rewardpointssettings.earnedrewardpointsaretaxable', N'False', 0) -END -GO -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = lower(N'rewardpointssettings.AwardPointsIncludeShipping')) -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (lower(N'rewardpointssettings.AwardPointsIncludeShipping'), N'True', 0) -END -GO -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = lower(N'rewardpointssettings.AwardPointsIncludePaymentMethodAdditionalFee')) -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (lower(N'rewardpointssettings.AwardPointsIncludePaymentMethodAdditionalFee'), N'True', 0) -END -GO -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = lower(N'rewardpointssettings.AwardPointsExcludeGiftCard')) -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (lower(N'rewardpointssettings.AwardPointsExcludeGiftCard'), N'True', 0) -END -GO -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = lower(N'rewardpointssettings.AwardPointsExcludePurchasedRewardPoints')) -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (lower(N'rewardpointssettings.AwardPointsExcludePurchasedRewardPoints'), N'True', 0) -END -GO -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = lower(N'rewardpointssettings.EarnRewardPointsOnlyWhenUsingPurchasedRewardPoints')) -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (lower(N'rewardpointssettings.EarnRewardPointsOnlyWhenUsingPurchasedRewardPoints'), N'False', 0) -END -GO ---add a new columns -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[RewardPointsHistory]') and NAME='PointsPurchased') -BEGIN - ALTER TABLE [RewardPointsHistory] - ADD PointsPurchased [int] NULL, PointsBalancePurchased [int] NULL, [PurchasedWithOrderItemId] [int] NULL, [UsedAmountPurchased] decimal(18,4) NULL -END -GO - ---create fk -IF NOT EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[RewardPoints_PurchasedWithOrderItem]') AND parent_object_id = OBJECT_ID(N'[dbo].[RewardPointsHistory]')) -ALTER TABLE [dbo].[RewardPointsHistory] WITH CHECK ADD CONSTRAINT [RewardPoints_PurchasedWithOrderItem] FOREIGN KEY([PurchasedWithOrderItemId]) -REFERENCES [dbo].[OrderItem] ([Id]) -GO - ---enable it -IF EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[RewardPoints_PurchasedWithOrderItem]') AND parent_object_id = OBJECT_ID(N'[dbo].[RewardPointsHistory]')) -ALTER TABLE [dbo].[RewardPointsHistory] CHECK CONSTRAINT [RewardPoints_PurchasedWithOrderItem] -GO - ---init new col. -UPDATE [RewardPointsHistory] -SET [PointsPurchased] = 0, [UsedAmountPurchased] = 0, [PointsBalancePurchased] = 0 -WHERE [PointsPurchased] IS NULL -GO - -ALTER TABLE [RewardPointsHistory] ALTER COLUMN [PointsPurchased] [int] NOT NULL -GO -ALTER TABLE [RewardPointsHistory] ALTER COLUMN [UsedAmountPurchased] decimal(18,4) NOT NULL -GO - -IF NOT EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'[dbo].[RewardPointsHistory]') AND name = N'IX_RewardPointsHistory_Customer') -CREATE NONCLUSTERED INDEX [IX_RewardPointsHistory_Customer] ON [dbo].[RewardPointsHistory] -( - [CustomerId] ASC -) -GO - ---add a new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Product]') and NAME='IsRewardPoints') -BEGIN - ALTER TABLE [Product] - ADD [IsRewardPoints] [bit] NULL, - OverriddenRPExchangeRate decimal(18,4) NULL -END -GO - ---init new col. -UPDATE [Product] -SET [IsRewardPoints] = 0 -WHERE [IsRewardPoints] IS NULL -GO - -ALTER TABLE [Product] ALTER COLUMN [IsRewardPoints] [bit] NOT NULL -GO - ---add a new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Product]') and NAME='ExcludeFromRewardPoints') -BEGIN - ALTER TABLE [Product] - ADD ExcludeFromRewardPoints [bit] NULL -END -GO - ---init new col. -UPDATE [Product] -SET [ExcludeFromRewardPoints] = 0 -WHERE [ExcludeFromRewardPoints] IS NULL -GO - -ALTER TABLE [Product] ALTER COLUMN [ExcludeFromRewardPoints] [bit] NOT NULL -GO - ---new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='CustomOrderNumber') -BEGIN - ALTER TABLE [Order] - ADD [CustomOrderNumber] nvarchar(MAX) NULL -END -GO - -UPDATE [Order] -SET [CustomOrderNumber] = [id] -WHERE [CustomOrderNumber] IS NULL -GO - -ALTER TABLE [Order] ALTER COLUMN [CustomOrderNumber] nvarchar(MAX) NOT NULL -GO - - --new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'ordersettings.customordernumbermask') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'ordersettings.customordernumbermask', N'{ID}', 0) -END -GO - - --new table -IF NOT EXISTS (SELECT 1 FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[CustomerPassword]') and OBJECTPROPERTY(object_id, N'IsUserTable') = 1) -BEGIN - CREATE TABLE [dbo].[CustomerPassword] - ( - [Id] int IDENTITY(1,1) NOT NULL, - [CustomerId] int NOT NULL, - [Password] NVARCHAR (MAX) NULL, - [PasswordFormatId] INT NOT NULL, - [PasswordSalt] NVARCHAR (MAX) NULL, - [CreatedOnUtc] datetime NOT NULL - PRIMARY KEY CLUSTERED - ( - [Id] ASC - ) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) - ) -END -GO - -IF EXISTS (SELECT 1 FROM sys.objects WHERE name = 'CustomerPassword_Customer' AND parent_object_id = Object_id('CustomerPassword') AND Objectproperty(object_id, N'IsForeignKey') = 1) -BEGIN - ALTER TABLE [dbo].CustomerPassword - DROP CONSTRAINT CustomerPassword_Customer -END -GO - -ALTER TABLE [dbo].[CustomerPassword] WITH CHECK ADD CONSTRAINT [CustomerPassword_Customer] FOREIGN KEY([CustomerId]) -REFERENCES [dbo].[Customer] ([Id]) -ON DELETE CASCADE -GO - ---move customer passwords into a new table -IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Customer]') and (NAME='Password' or NAME='PasswordFormatId' or NAME='PasswordSalt')) -BEGIN - EXEC(' - INSERT INTO [dbo].[CustomerPassword]([CustomerId], [Password], [PasswordFormatId], [PasswordSalt], [CreatedOnUtc]) - SELECT [Id], [Password], [PasswordFormatId], [PasswordSalt], [CreatedOnUtc] - FROM [dbo].[Customer]') -END -GO - ---drop column -IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Customer]') and NAME='Password') -BEGIN - ALTER TABLE [Customer] DROP COLUMN [Password] -END -GO - ---drop column -IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Customer]') and NAME='PasswordFormatId') -BEGIN - ALTER TABLE [Customer] DROP COLUMN [PasswordFormatId] -END -GO - ---drop column -IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Customer]') and NAME='PasswordSalt') -BEGIN - ALTER TABLE [Customer] DROP COLUMN [PasswordSalt] -END -GO - - --new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'customersettings.unduplicatedpasswordsnumber') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'customersettings.unduplicatedpasswordsnumber', N'4', 0) -END -GO - - --new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'customersettings.passwordlifetime') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'customersettings.passwordlifetime', N'90', 0) -END -GO - ---new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[CustomerRole]') and NAME='EnablePasswordLifetime') -BEGIN - ALTER TABLE [CustomerRole] - ADD [EnablePasswordLifetime] bit NULL -END -GO - -UPDATE [CustomerRole] -SET [EnablePasswordLifetime] = 0 -WHERE [EnablePasswordLifetime] IS NULL -GO - -ALTER TABLE [CustomerRole] ALTER COLUMN [EnablePasswordLifetime] bit NOT NULL -GO - ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddCustomerRewardPoints') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'AddCustomerRewardPoints', N'Add customer reward points', N'true') -END -GO ---new activity types -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditCustomerRewardPoints') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'EditCustomerRewardPoints', N'Edit customer reward points', N'true') -END -GO -GO - - --- new message template - IF NOT EXISTS (SELECT 1 FROM [dbo].[MessageTemplate] WHERE [Name] = N'Service.ContactUs') - BEGIN - DECLARE @NewLine AS CHAR(2) = CHAR(13) + CHAR(10) - INSERT [dbo].[MessageTemplate] ([Name], [BccEmailAddresses], [Subject], [Body], [IsActive], [AttachedDownloadId], [EmailAccountId], [LimitedToStores], [DelayPeriodId]) - VALUES (N'Service.ContactUs', NULL, N'%Store.Name%. Contact us', N'

    ' + @NewLine + '%ContactUs.Body%' + @NewLine + '

    ' + @NewLine, 1, 0, 0, 0, 0) - END - GO - --- new message template - IF NOT EXISTS (SELECT 1 FROM [dbo].[MessageTemplate] WHERE [Name] = N'Service.ContactVendor') - BEGIN - DECLARE @NewLine AS CHAR(2) = CHAR(13) + CHAR(10) - INSERT [dbo].[MessageTemplate] ([Name], [BccEmailAddresses], [Subject], [Body], [IsActive], [AttachedDownloadId], [EmailAccountId], [LimitedToStores], [DelayPeriodId]) - VALUES (N'Service.ContactVendor', NULL, N'%Store.Name%. Contact us', N'

    ' + @NewLine + '%ContactUs.Body%' + @NewLine + '

    ' + @NewLine, 1, 0, 0, 0, 0) - END - GO - - --now vendors have "Manage product reviews" permission -IF EXISTS ( - SELECT 1 - FROM [dbo].[PermissionRecord] - WHERE [SystemName] = N'ManageProductReviews') -BEGIN - DECLARE @PermissionRecordId INT - SET @PermissionRecordId = (SELECT [Id] FROM [dbo].[PermissionRecord] WHERE [SystemName] = N'ManageProductReviews') - - --add it to vendor role by default - DECLARE @VendorCustomerRoleId int - SELECT @VendorCustomerRoleId = Id - FROM [CustomerRole] - WHERE IsSystemRole=1 and [SystemName] = N'Vendors' - - IF NOT EXISTS ( - SELECT 1 - FROM [dbo].[PermissionRecord_Role_Mapping] - WHERE [PermissionRecord_Id] = @PermissionRecordId AND [CustomerRole_Id] = @VendorCustomerRoleId) - BEGIN - INSERT [dbo].[PermissionRecord_Role_Mapping] ([PermissionRecord_Id], [CustomerRole_Id]) - VALUES (@PermissionRecordId, @VendorCustomerRoleId) - END -END -GO - - --new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'taxsettings.taxbasedonpickuppointaddress') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'taxsettings.taxbasedonpickuppointaddress', N'False', 0) -END -GO - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'ordersettings.exportwithproducts') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'ordersettings.exportwithproducts', N'True', 0) -END -GO - ---new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[DiscountRequirement]') and NAME='InteractionTypeId') -BEGIN - ALTER TABLE [DiscountRequirement] - ADD [InteractionTypeId] int NULL -END -GO - ---new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[DiscountRequirement]') and NAME='ParentId') -BEGIN - ALTER TABLE [DiscountRequirement] - ADD [ParentId] int NULL -END -GO - ---new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[DiscountRequirement]') and NAME='IsGroup') -BEGIN - ALTER TABLE [DiscountRequirement] - ADD [IsGroup] bit NULL -END -GO - -UPDATE [DiscountRequirement] -SET [IsGroup] = 0 -WHERE [IsGroup] IS NULL -GO - -ALTER TABLE [DiscountRequirement] ALTER COLUMN [IsGroup] bit NOT NULL -GO - ---add default requirement group for existing requirements -DECLARE cursor_defaultGroup CURSOR FOR SELECT [DiscountId] FROM [DiscountRequirement] -DECLARE @discountId int - -OPEN cursor_defaultGroup -FETCH NEXT FROM cursor_defaultGroup INTO @discountId -WHILE @@FETCH_STATUS = 0 -BEGIN - IF NOT EXISTS (SELECT 1 FROM [DiscountRequirement] WHERE [DiscountId] = @discountId AND [ParentId] IS NULL AND [IsGroup] = 1) - BEGIN - INSERT INTO [DiscountRequirement] - ([DiscountId], [DiscountRequirementRuleSystemName], [InteractionTypeId], [ParentId], [IsGroup]) - VALUES - (@discountId, 'Default requirement group', 0, NULL, 1); - - DECLARE @requirementId int = (SELECT SCOPE_IDENTITY()); - - UPDATE [DiscountRequirement] - SET [ParentId] = @requirementId, [InteractionTypeId] = NULL - WHERE [DiscountId] = @discountId AND [Id] <> @requirementId - END - FETCH NEXT FROM cursor_defaultGroup INTO @discountId -END - -CLOSE cursor_defaultGroup -DEALLOCATE cursor_defaultGroup -GO - - --new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'adminareasettings.useisodatetimeconverterinjson') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'adminareasettings.useisodatetimeconverterinjson', N'True', 0) -END -GO - ---new activity type -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'ImportCategories') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'ImportCategories', N'Categories were imported', N'True') -END -GO - ---new activity type -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'ImportManufacturers') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'ImportManufacturers', N'Manufacturers were imported', N'True') -END -GO - ---new activity type -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'ImportProducts') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'ImportProducts', N'Products were imported', N'True') -END -GO - ---new activity type -IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'ImportStates') -BEGIN - INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) - VALUES (N'ImportStates', N'States and provinces were imported', N'True') -END -GO - ---update DownloadActivationType according to the new enum value -UPDATE [Product] -SET [DownloadActivationTypeId] = 0 -WHERE [DownloadActivationTypeId] = 1 -GO - ---new column -IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Currency]') and NAME='RoundingTypeId') -BEGIN - ALTER TABLE [Currency] - ADD [RoundingTypeId] INT NULL -END -GO - -UPDATE [Currency] -SET [RoundingTypeId] = 0 -WHERE [RoundingTypeId] IS NULL -GO - --- Rounding with 1.00 intervals (The system used in Sweden since 30 September 2010. https://en.wikipedia.org/wiki/Cash_rounding#Rounding_with_1.00_intervals) -UPDATE [Currency] -SET [RoundingTypeId] = 60 -WHERE [CurrencyCode] = 'SEK' -GO - -ALTER TABLE [Currency] ALTER COLUMN [RoundingTypeId] INT NOT NULL -GO - - --new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'adminareasettings.usericheditorinmessagetemplates') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'adminareasettings.usericheditorinmessagetemplates', N'False', 0) -END -GO - - --new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'mediasettings.azurecachecontrolheader') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'mediasettings.azurecachecontrolheader', N'', 0) -END -GO - ---add stored procedure for getting category tree -IF EXISTS (SELECT * FROM sysobjects WHERE id = OBJECT_ID(N'[nop_padright]') AND xtype IN (N'FN', N'IF', N'TF')) -DROP FUNCTION [dbo].[nop_padright] -GO - -CREATE FUNCTION [dbo].[nop_padright] -( - @source INT, - @symbol NVARCHAR(MAX), - @length INT -) -RETURNS NVARCHAR(MAX) -AS -BEGIN - RETURN RIGHT(REPLICATE(@symbol, @length)+ RTRIM(CAST(@source AS NVARCHAR(MAX))), @length) -END -GO - -IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[CategoryLoadAllPaged]') AND OBJECTPROPERTY(object_id, N'IsProcedure') = 1) -DROP PROCEDURE [dbo].[CategoryLoadAllPaged] -GO - -CREATE PROCEDURE [dbo].[CategoryLoadAllPaged] -( - @ShowHidden BIT = 0, - @Name NVARCHAR(MAX) = NULL, - @StoreId INT = 0, - @CustomerRoleIds NVARCHAR(MAX) = NULL, - @PageIndex INT = 0, - @PageSize INT = 2147483644, - @TotalRecords INT = NULL OUTPUT -) -AS -BEGIN - SET NOCOUNT ON - - --filter by customer role IDs (access control list) - SET @CustomerRoleIds = ISNULL(@CustomerRoleIds, '') - CREATE TABLE #FilteredCustomerRoleIds - ( - CustomerRoleId INT NOT NULL - ) - INSERT INTO #FilteredCustomerRoleIds (CustomerRoleId) - SELECT CAST(data AS INT) FROM [nop_splitstring_to_table](@CustomerRoleIds, ',') - DECLARE @FilteredCustomerRoleIdsCount INT = (SELECT COUNT(1) FROM #FilteredCustomerRoleIds) - - --ordered categories - CREATE TABLE #OrderedCategoryIds - ( - [Id] int IDENTITY (1, 1) NOT NULL, - [CategoryId] int NOT NULL - ) - - --get max length of DisplayOrder and Id columns (used for padding Order column) - DECLARE @lengthId INT = (SELECT LEN(MAX(Id)) FROM [Category]) - DECLARE @lengthOrder INT = (SELECT LEN(MAX(DisplayOrder)) FROM [Category]) - - --get category tree - ;WITH [CategoryTree] - AS (SELECT [Category].[Id] AS [Id], dbo.[nop_padright] ([Category].[DisplayOrder], '0', @lengthOrder) + '-' + dbo.[nop_padright] ([Category].[Id], '0', @lengthId) AS [Order] - FROM [Category] WHERE [Category].[ParentCategoryId] = 0 - UNION ALL - SELECT [Category].[Id] AS [Id], [CategoryTree].[Order] + '|' + dbo.[nop_padright] ([Category].[DisplayOrder], '0', @lengthOrder) + '-' + dbo.[nop_padright] ([Category].[Id], '0', @lengthId) AS [Order] - FROM [Category] - INNER JOIN [CategoryTree] ON [CategoryTree].[Id] = [Category].[ParentCategoryId]) - INSERT INTO #OrderedCategoryIds ([CategoryId]) - SELECT [Category].[Id] - FROM [CategoryTree] - RIGHT JOIN [Category] ON [CategoryTree].[Id] = [Category].[Id] - - --filter results - WHERE [Category].[Deleted] = 0 - AND (@ShowHidden = 1 OR [Category].[Published] = 1) - AND (@Name IS NULL OR @Name = '' OR [Category].[Name] LIKE ('%' + @Name + '%')) - AND (@ShowHidden = 1 OR @FilteredCustomerRoleIdsCount = 0 OR [Category].[SubjectToAcl] = 0 - OR EXISTS (SELECT 1 FROM #FilteredCustomerRoleIds [roles] WHERE [roles].[CustomerRoleId] IN - (SELECT [acl].[CustomerRoleId] FROM [AclRecord] acl WITH (NOLOCK) WHERE [acl].[EntityId] = [Category].[Id] AND [acl].[EntityName] = 'Category') - ) - ) - AND (@StoreId = 0 OR [Category].[LimitedToStores] = 0 - OR EXISTS (SELECT 1 FROM [StoreMapping] sm WITH (NOLOCK) - WHERE [sm].[EntityId] = [Category].[Id] AND [sm].[EntityName] = 'Category' AND [sm].[StoreId] = @StoreId - ) - ) - ORDER BY ISNULL([CategoryTree].[Order], 1) - - --total records - SET @TotalRecords = @@ROWCOUNT - - --paging - SELECT [Category].* FROM #OrderedCategoryIds AS [Result] INNER JOIN [Category] ON [Result].[CategoryId] = [Category].[Id] - WHERE ([Result].[Id] > @PageSize * @PageIndex AND [Result].[Id] <= @PageSize * (@PageIndex + 1)) - ORDER BY [Result].[Id] - - DROP TABLE #FilteredCustomerRoleIds - DROP TABLE #OrderedCategoryIds -END -GO - - --new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'commonsettings.usestoredprocedureforloadingcategories') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'commonsettings.usestoredprocedureforloadingcategories', N'False', 0) -END -GO - - --new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'storeinformationsettings.displayminiprofilerforadminonly') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'storeinformationsettings.displayminiprofilerforadminonly', N'False', 0) -END -GO - ---indicating whether to display default menu items -DECLARE @displayMenuItems bit -IF NOT EXISTS (SELECT 1 FROM [Category] where ParentCategoryId=0 and Deleted=0 and Published=1) - set @displayMenuItems = N'True' -ELSE - set @displayMenuItems = N'False' - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'displaydefaultmenuitemsettings.displayhomepagemenuitem') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'displaydefaultmenuitemsettings.displayhomepagemenuitem', @displayMenuItems, 0) -END - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'displaydefaultmenuitemsettings.displaynewproductsmenuitem') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'displaydefaultmenuitemsettings.displaynewproductsmenuitem', @displayMenuItems, 0) -END - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'displaydefaultmenuitemsettings.displayproductsearchmenuitem') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'displaydefaultmenuitemsettings.displayproductsearchmenuitem', @displayMenuItems, 0) -END - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'displaydefaultmenuitemsettings.displaycustomerinfomenuitem') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'displaydefaultmenuitemsettings.displaycustomerinfomenuitem', @displayMenuItems, 0) -END - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'displaydefaultmenuitemsettings.displayblogmenuitem') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'displaydefaultmenuitemsettings.displayblogmenuitem', @displayMenuItems, 0) -END - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'displaydefaultmenuitemsettings.displayforumsmenuitem') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'displaydefaultmenuitemsettings.displayforumsmenuitem', @displayMenuItems, 0) -END - ---new setting -IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'displaydefaultmenuitemsettings.displaycontactusmenuitem ') -BEGIN - INSERT [Setting] ([Name], [Value], [StoreId]) - VALUES (N'displaydefaultmenuitemsettings.displaycontactusmenuitem ', @displayMenuItems, 0) -END +--upgrade scripts from nopCommerce 3.80 to next version + +--new locale resources +declare @resources xml +--a resource will be deleted if its value is empty +set @resources=' + + + Assign Invoice ID from Task + + + Assign invoice id using background task. This assures that the same invoice id isn''t used more than ones and that there are no wholes in the number series. + + + Order total to pay + + + Order total to pay: + + + Total to pay + + + Total + + + Order total to pay + + + Order total amount + + + Order Total to pay + + + Order Total Amount + + + Order Total to pay: + + + Order Total Amount: + + + Order total to pay + + + Order total amount (incl.) + + + Order Total to pay + + + Order Total Amount + + + Included Tax: + + + Amount: + + + Amount incl. Tax: + + + Included Tax + + + Amount + + + Amount incl. Tax + + + Total to pay + + + Invoice Discount + + + + + + Tax % + + + + + + Tax % + + + Invoice + + + Invoice Date + + + Amount + + + Amount incl. tax + + + Discount + + + Discount incl. tax + + + + + + Tax Amount + + + Amount to pay + + + Base Ammount + + + Total Base + + + Continued on next page ... + + + Additional items + + + Invoice discount + + + Currency + + + Page + + + Order No. + + + Orderdate + + + {0} purchased reward points: + + + Invoice No. + + + Invoice Date + + + Amount + + + Total order base amount. + + + Amount incl. Tax + + + Total order amount incl. tax + + + Invoice No. + + + Invoice No. + + + Invoice Date + + + Invoice Date + + + + + + + + + Order discount incl. Tax + + + Order discount incl. Tax + + + non taxable + + + non taxable + + + Amount + + + Amount + + + Amount incl. Tax + + + Amount incl. Tax + + + Earned reward points base amount incl. Tax + + + Edit earned reward points base amount incl. Tax + + + Earned reward points base amount excl. Tax + + + Edit earned reward points base amount excl. Tax + + + + + + + + + Order discount incl. Tax + + + Order discount incl. Tax + + + non taxable: + + + Order discount incl. Tax + + + Earned reward points base amount incl. Tax + + + Earned reward points base amount excl. Tax + + + Edit Invoice data. + + + Save Invoice Data. + + + A new InvoiceId get''s assigned when saving an Id = null + + + Included Tax + + + Amount + + + Amount incl. Tax + + + Invoice No. + + + Invoice Date + + + + + + Tax % + + + Amount + + + Amount incl. Tax + + + Discount + + + Discount incl. tax + + + Base Amount + + + + + + Tax Amount + + + You haven''t written any reviews yet + + + Last used Invoice No. + + + Invoice ID counter. This is useful if you want your invoices to start from certain number. This only affects invoices henceforward. The value must be greater than the current maximum invoice ID. + + + Year of last invoice + + + Year of last invoice. If actual year is different from this setting, then InvoiceIdent starts from one. + + + Force entering email twice during registration. + + + Sets a maximum number of products per vendor. + + + Shipping methods used by offline shipping rate computation methods (e.g. "Fixed Rate Shipping" or "Shipping by weight"). + + + The price of the product. You can manage currency by selecting Configuration > Currencies. + + + Choose customer roles of this user. + + + Check if products should be exported/imported with product attributes. + + + Check to display "ship to the same address" option during checkout ("billing address" step). In this case "shipping address" with appropriate options (e.g. pick up in store) will be skipped. Also note that all billing countries should support shipping ("Allow shipping" checkbox ticked). + + + Task period should not exceed 24 days. + + + Payment restrictions + + + Choose a vendor associated with this product. This can be useful when running a multi-vendor store to keep track of goods associated with vendor. + + + You can download a CSV file with a list of states for other countries on the following page: + + + Enter tags ... + + + [None] + + + Hide shipping total if shipping not required + + + Check if you want Hide ''Shipping total'' label if shipping not required. + + + + + + + + + + + + + + + + + + + + + Client ID + + + Specify client ID. + + + Client secret + + + Specify secret key. + + + Webhook ID + + + Specify webhook ID. + + + Get webhook ID + + + Webhook was not created (see details in the log) + + + [None] + + + Default tax category + + + Select default tax category for products. + + + Added a new address attribute (ID = {0}) + + + Added a new address attribute value (ID = {0}) + + + Added a new affiliate (ID = {0}) + + + Added a new blog post (ID = {0}) + + + Added a new campaign (ID = {0}) + + + Added a new country (ID = {0}) + + + Added a new currency (ID = {0}) + + + Added a new customer attribute (ID = {0}) + + + Added a new customer attribute value (ID = {0}) + + + Added a new email account (ID = {0}) + + + Added a new language (ID = {0}) + + + Added a new measure dimension (ID = {0}) + + + Added a new measure weight (ID = {0}) + + + Added a new news (ID = {0}) + + + Installed a new plugin (FriendlyName: ''{0}'') + + + Added a new state province (ID = {0}) + + + Added a new store (ID = {0}) + + + Added a new vendor (ID = {0}) + + + Added a new warehouse (ID = {0}) + + + Deleted an address attribute (ID = {0}) + + + Deleted an address attribute value (ID = {0}) + + + Deleted an affiliate (ID = {0}) + + + Deleted a blog post (ID = {0}) + + + Deleted a blog post comment (ID = {0}) + + + Deleted a campaign (ID = {0}) + + + Deleted a country (ID = {0}) + + + Deleted a currency (ID = {0}) + + + Deleted a customer attribute (ID = {0}) + + + Deleted a customer attribute value (ID = {0}) + + + Deleted an email account (ID = {0}) + + + Deleted a language (ID = {0}) + + + Deleted a measure dimension (ID = {0}) + + + Deleted a measure weight (ID = {0}) + + + Deleted a message template (ID = {0}) + + + Deleted a news (ID = {0}) + + + Deleted a news comment (ID = {0}) + + + Uninstalled a plugin (FriendlyName: ''{0}'') + + + Deleted a product revie (ID = {0}) + + + Deleted a state or province (ID = {0}) + + + Deleted a store (ID = {0}) + + + Deleted a vendor (ID = {0}) + + + Deleted a warehouse (ID = {0}) + + + Edited an address attribute (ID = {0}) + + + Edited an address attribute value (ID = {0}) + + + Edited an affiliate (ID = {0}) + + + Edited a blog post (ID = {0}) + + + Edited a campaign (ID = {0}) + + + Edited a country (ID = {0}) + + + Edited a currency (ID = {0}) + + + Edited a customer attribute (ID = {0}) + + + Edited a customer attribute value (ID = {0}) + + + Edited an email account (ID = {0}) + + + Edited a language (ID = {0}) + + + Edited a measure dimension (ID = {0}) + + + Edited a measure weight (ID = {0}) + + + Edited a message template (ID = {0}) + + + Edited a news (ID = {0}) + + + Edited a plugin (FriendlyName: ''{0}'') + + + Edited a product revie (ID = {0}) + + + Edited a state or province (ID = {0}) + + + Edited a store (ID = {0}) + + + Edited a task (ID = {0}) + + + Edited a vendor (ID = {0}) + + + Edited a warehouse (ID = {0}) + + + Product review possible only after purchasing product + + + Check if product can be reviewed only by customer who have already ordered it. + + + Product can be reviewed only after purchasing it + + + + + + You can use ECB (European central bank) exchange rate provider only when the primary exchange rate currency is supported by ECB + + + Attached static file + + + The attached static file that will be sent in this email. + + + Contract ID + + + Specify contract identifier. + + + Product review possible only after product purchasing + + + Page size should be positive. + + + Page size should be positive. + + + Page size should be positive. + + + Enter tags ... + + + + + + + + + Show SKU on catalog pages + + + Check to show product SKU on catalog pages in public store. + + + Show SKU on product details page + + + Check to show product SKU on the product details page in public store. + + + + + + + + + Customer enters quantity + + + Allow customers enter the quantity of associated product. + + + - quantity {0} + + + {0} [{1}{2}] + + + per item + + + Enter quantity: + + + Email account + + + The email account that will be used to send this campaign. + + + Limited to customer roles + + + Choose one or several customer roles i.e. administrators, vendors, guests, who will be able to use this plugin. If you don''t need this option just leave this field empty. + + + Activate points immediately + + + Activates bonus points immediately after their calculation + + + Reward points activation + + + Specify how many days (hours) must elapse before earned points become active. Points earned by purchase cannot be redeemed until activated. For example, you may set the days before the points become available to 7. In this case, the points earned will be available for spending 7 days after the order gets chosen awarded status. + + + The points will be activated on {0} + + + Days + + + Hours + + + The points will be activated on {0} + + + Your current balance + + + Total + + + Amount + + + Specify if earned reward points are taxable + + + If Earned reward points are taxable, then OrderAmount and tax will be reduced (like discounts do). Otherwise payment amount is reduced like with gift cards. + + + Include shipping + + + Specify if shipping amount should be included in reward points calculation. + + + Include payment method fee + + + Specify if payment method additional fee should be included in reward points calculation. + + + Exclude gift card(s) + + + Specify if gift cards should be excluded in reward points calculation. This setting allows to earn reward points, although order was paid or partially paid with giftcards. + + + Exclude purchased reward points + + + Specify if purchased points should be excluded in reward points calculation. This setting allows to earn reward points, although order total is reduced or zero. + + + Earn points only when using purchased points + + + Specify if reward points can only be earned when using purchased reward points for payment. With this earning of reward points is connected to the use of purchased points. + + + Is reward point(s) + + + If this field is checked, reward points can be pruchased. + + + Overridden reward points exchange rate + + + Use this field to override reward points exchange rate setting when converting product price to reward points (points = [overridden exchange rate] * price). If not specified, then reward points exchange rate will be used. + + + Reward point(s) + + + Reward points + + + Sorry, this discount cannot be used with reward points products in the cart + + + {0} purchased reward points: + + + Purchased with order #{0} + + + Purchased and earned with order #{0} + + + {0} used reward points + + + {0} used purchased reward points + + + {0} used reward points + + + {0} used purchased reward points + + + Redeemed reward points + + + Redeemed reward points. + + + Redeemed reward points amount + + + Redeemed reward points amount. + + + Redeemed reward points purchased + + + Redeemed purchased reward points. + + + Redeemed reward points amount purchased + + + Redeemed purchased reward points amount. + + + Purchased value + + + Indicate if added reward points are purchased points. Negative values are also supported. + + + Points balance purchased + + + Points Purchased + + + Points purchased + + + Points balance purchased + + + Add customer reward points (points = {0}, purchased points = {1}) + + + Edited customer reward points (ID = {0}) + + + Exclude from reward points + + + If this field is checked, product will be excluded from reward points calculation. + + + At least one published currency is required + + + The customer cannot be in both ''Guests'' and ''Registered'' customer roles + + + Add the customer to ''Guests'' or ''Registered'' customer role + + + Valid Email is required for customer to be in ''Registered'' role + + + A non-admin user cannot impersonate as an administrator + + + At least one published language is required + + + This order item has an associated gift card record. Please delete it first + + + Order item is deleted + + + Captcha is enabled but the appropriate keys are not entered + + + The message template has been copied successfully + + + The product has been copied successfully + + + Entered page name already exists, so it will be replaced by ''{0}'' + + + Choose a delivery date which will be displayed in the public store. You can manage delivery dates by selecting Configuration > Shipping > Dates and ranges. + + + Product availability range + + + Choose the product availability range that indicates when the product is expected to be available when out of stock (e.g. Available in 10-14 days). You can manage availability ranges by selecting Configuration > Shipping > Dates and ranges. + + + None + + + Product availability range + + + Dates and ranges + + + List of delivery dates which will be available for choice in product details. + + + Product availability ranges + + + The new product availability range has been added successfully. + + + Add a new product availability range + + + back to product availability range list + + + The product availability range has been deleted successfully. + + + Edit product availability range details + + + Display order + + + The display order of this product availability range. 1 represents the top of the list. + + + Name + + + Enter product availability range name. + + + Please provide a name. + + + List of availability ranges which will be available for choice in product details. + + + The product availability range has been updated successfully. + + + Available in {0} + + + Out of stock - on backorder and will be dispatched once in stock ({0}). + + + Please note that this product is excluded from the reward points program. No points will be earned. + + + Available in {0} + + + {0} point(s) + + + You will earn (based on {0}) + + + (when using purchased points) + + + Pay by cheque or money order + + + Pay by credit / debit card + + + Pay by credit / debit card + + + You will be redirected to PayPal site to complete the payment + + + Pay by purchase order (PO) number + + + Your account already has been activated + + + Your password already has been changed. For changing it once more, you need to again recover the password. + + + Your subscription already has been deactivated. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + By Weight + + + Fixed Rate + + + Rate + + + Store + + + If an asterisk is selected, then this shipping rate will apply to all stores. + + + Warehouse + + + If an asterisk is selected, then this shipping rate will apply to all warehouses. + + + Country + + + If an asterisk is selected, then this shipping rate will apply to all customers, regardless of the country. + + + State / province + + + If an asterisk is selected, then this shipping rate will apply to all customers from the given country, regardless of the state. + + + Zip + + + Zip / postal code. If zip is empty, then this shipping rate will apply to all customers from the given country or state, regardless of the zip code. + + + Shipping method + + + Choose shipping method + + + Order weight from + + + Order weight from. + + + Order weight to + + + Order weight to. + + + Additional fixed cost + + + Specify an additional fixed cost per shopping cart for this option. Set to 0 if you don''t want an additional fixed cost to be applied. + + + Lower weight limit + + + Lower weight limit. This field can be used for \"per extra weight unit\" scenarios. + + + Charge percentage (of subtotal) + + + Charge percentage (of subtotal). + + + Rate per weight unit + + + Rate per weight unit. + + + Limit shipping methods to configured ones + + + If you check this option, then your customers will be limited to shipping options configured here. Otherwise, they''ll be able to choose any existing shipping options even they''ve not configured here (zero shipping fee in this case). + + + Data + + + Add record + + + Formula to calculate rates + + + [additional fixed cost] + ([order total weight] - [lower weight limit]) * [rate per weight unit] + [order subtotal] * [charge percentage] + + + Allow vendors to import products + + + Check if vendors are allowed to import products. + + + You save: {0} + + + Discounted qty: {0} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Add new tier price + + + Edit tier price details + + + Select customer role for which the tier price will be available. + + + End date + + + The end date of the tier price in Coordinated Universal Time (UTC). + + + Specify the price. + + + Specify quantity for which this tier price will be available. + + + Start date + + + The start date of the tier price in Coordinated Universal Time (UTC). + + + Option to limit this tier price to a certain store. If you have multiple stores, choose one from the list. + + + Deactivate gift cards after deleting of an order + + + Check to deactivate related gift cards when an order is deleted. + + + Complete order when delivered + + + Check if an order status should be set to "Complete" only when its shipping status is "Delivered". Otherwise, "Shipped" status will be enough. + + + order #{1}]]> + + + Edited a news comment (ID = {0}) + + + News comments must be approved + + + Check if news comments must be approved by administrator. + + + Is approved + + + News comment is successfully added. You will see it after approving by a store administrator. + + + Edited a blog comment (ID = {0}) + + + Blog comments must be approved + + + Check if blog comments must be approved by administrator. + + + Is approved + + + Blog comment is successfully added. You will see it after approving by a store administrator. + + + Stock quantity history + + + Here you can see a history of the product stock quantity changes. + + + Attribute combination + + + Created On + + + Message + + + Stock quantity + + + Quantity adjustment + + + Warehouse + + + Stock quantity history + + + The stock quantity has been increased by canceling the order #{0} + + + The stock quantity of combination has been edited + + + The stock quantity has been edited by copying the product #{0} + + + The stock quantity has been increased by deleting the order #{0} + + + The stock quantity has been increased by deleting an order item from the order #{0} + + + The stock quantity has been increased by deleting a shipment from the order #{0} + + + The stock quantity has been edited + + + Multiple warehouses. + + + The stock quantity has been changed by editing the order #{0} + + + The stock quantity has been changed by importing product + + + Products have been moved {0} {1} by importing product + + + Products have been moved {0} {1} + + + to the {0} + + + from the {0} + + + The stock quantity has been reduced by placing the order #{0} + + + The stock quantity has been reduced when an order item of the order #{0} was shipped + + + {0} - copy + + + {0}-copy + + + + + + + + + + + + + + + + + + + + + Deactivate gift cards after cancelling of an order + + + Check to deactivate related gift cards when an order is cancelled. + + + Activate gift cards after completing of an order + + + Check to activate related gift cards when an order is completed. + + + + + + + + + + + + + + + + + + + + + + + + + + + Do not forget to restart the application once a task has been modified. + + + + + + - or - + + + Choose an associated product + + + Store + + + Load products only from a specific store (available in this store). + + + Vendor + + + Load products only by a specific vendor (owned by this vendor). + + + Category + + + Load products only from a specific category. + + + Manufacturer + + + Load products only from a specific manufacturer. + + + Carts shared between storest + + + Determines whether shopping carts (and wishlist) are shared between stores (in multi-store environment). + + + Email validation + + + Your email already has been validated + + + Your email has been validated + + + Email validation + + + Email validation + + + New email + + + (not validated yet) + + + Shipping methods used by offline shipping providers. For example, "Manual (Fixed or By Weight)". + + + Ignored product type IDs (advanced) + + + Allow file uploads + + + Check if you want to allow customers to upload files when submitting return requests. + + + Upload (any additional document, scan, etc) + + + Uploaded file: + + + Download + + + Uploaded file + + + File uploaded by customer + + + Download + + + Reply text + + + The reply text (by a store owner). If specified, then it''ll be visible to a customer. Leave empty to ignore this functionality. + + + A manager responded to this review + + + Blog comments per store + + + Check to display blog comments written in the current store only. + + + News comments per store + + + Check to display news comments written in the current store only. + + + Store name + + + Store name + + + The associated product has attributes, keep in mind that customers can not select them in the product details page. + + + The associated product has required product attributes, so customers won''t be able to choose this product attribute value. + + + The associated product is downloadable, keep in mind that won''t be able to download it. + + + The associated product is a gift card, keep in mind that customers can not specify its details in the product details page. + + + All + + + {0} - {1} of {2} items + + + No items to display + + + Go to the first page + + + items per page + + + Go to the last page + + + More pages + + + Go to the next page + + + of {0} + + + Page + + + Go to the previous page + + + Refresh + + + Address (optional) + + + Maximum login failures + + + Maximum login failures to lockout account. Set 0 to disable this feature. + + + Lockout time (login failures) + + + Enter number of minutes to lockout users (for login failures). + + + Customer is locked out + + + Imported manufacturers are distinguished by ID. If the ID already exists, then its corresponding manufacturer will be updated. You should not specify ID (leave 0) for new manufacturers. + + + Imported categories are distinguished by ID. If the ID already exists, then its corresponding category will be updated. You should not specify ID (leave 0) for new categories. + + + For conditional expressions use the token %if (your conditions ) ... endif% + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Fixed rate + + + By Country + + + Tax category + + + Rate + + + Store + + + If an asterisk is selected, then this shipping rate will apply to all stores. + + + Country + + + The country. + + + State / province + + + If an asterisk is selected, then this tax rate will apply to all customers from the given country, regardless of the state. + + + Zip + + + Zip / postal code. If zip is empty, then this tax rate will apply to all customers from the given country or state, regardless of the zip code. + + + Tax category + + + The tax category. + + + Percentage + + + The tax rate. + + + Add tax rate + + + New tax rate + + + ID + + + Search by a specific return request identifier. + + + End date + + + The end date for the search. + + + Return status + + + All + + + Search by a specific return request status e.g. Received. + + + Start date + + + The start date for the search. + + + + Product attributes are quantifiable or descriptive aspects of a product (such as, color). For example, if you were to create an attribute for color, with the values of blue, green, yellow, and so on, you may want to apply this attribute to shirts, which you sell in various colors (you can adjust a price or weight for any of existing attribute values). + You can add attribute for your product using existing list of attributes, or if you need to create a new one go to Catalog > Attributes > Product attributes. Please notice that if you want to manage inventory by product attributes (e.g. 5 green shirts and 3 blue ones), then ensure that "Inventory method" is set to "Track inventory by product attributes". + + + + Approve selected + + + Approve selected + + + Disapprove selected + + + Disapprove selected + + + Registered in the store + + + Indicating in which store the customer is registered + + + Consider associated products dimensions and weight + + + Check to consider associated products dimensions and weight on shipping, uncheck for example if the main product already includes them. + + + Created from + + + The creation from date for the search. + + + Created to + + + The creation to date for the search. + + + Message + + + Search in title and comment text. + + + Created from + + + The creation from date for the search. + + + Created to + + + The creation to date for the search. + + + Message + + + Search in comment text. + + + Retry last payment + + + Last payment failed + + + Approved + + + Search by a "Approved" property. + + + All + + + Approved only + + + Disapproved only + + + Approved + + + Search by a "Approved" property. + + + All + + + Approved only + + + Disapproved only + + + Approved + + + Search by a "Approved" property. + + + All + + + Approved only + + + Disapproved only + + + It looks like you have "ShoppingCartSettings.RoundPricesDuringCalculation" setting disabled. Keep in mind that this can lead to a discrepancy of the order total amount, as PayPal only rounds to two decimals. + + + + + + + + + + + + + + + + + + + + + Order ID + + + Order # + + + Order # + + + Order ID + + + Order # + + + Order ID + + + Order # + + + Order # + + + The unique number of this order. + + + Reward points base + + + Earned reward points base amount + + + Earned reward points base amount incl. Tax + + + Earned reward points base amount excl. Tax + + + Created order ID + + + Created order + + + Order ID + + + Order # + + + Order ID + + + Order # + + + The order associated to this shipment. + + + Order number mask + + + Order number mask, for creating custom order number. For example, RE-{YYYY}-{MM}. Leave this field empty if you don''t want to use custom order numbers. + + + {DD} - day of order creation date + + + {ID} -Order identifier + + + {MM} - month of order creation date + + + {YYYY} - year of order creation date + + + {YY} - last two digits of year of order creation date + + + + + + + + + Order + + + The gift card was purchased with this order. + + + Order + + + The gift card was purchased with this order. + + + Edited an order (Order number = {0}). See order notes for details + + + View order (Order number - {0}) + + + The unique number of the order. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Pass purchased items + + + Check to pass information about purchased items to PayPal. + + + Unduplicated passwords number + + + Specify the number of customer passwords that mustn''t be the same as the previous one, enter 0 if the customer can use the same password time after time. + + + You entered the password that is the same as one of the last passwords you used. Please create a new password. + + + Your password has expired, please create a new one + + + Password lifetime + + + Specify number of days for password expiration. Don''t forget to check "EnablePasswordLifetime" property on customer role edit page for those roles, who will have to change passwords. + + + Enable password lifetime + + + Check to force customers to change their passwords after a specified time. + + + ID + + + ID + + + + + + + + + This order is cancelled + + + Notify about new blog comments in Configuration - Settings - Blog settings.]]> + + + Allow back in stock subscriptions in Product info tab - Inventory section.]]> + + + This message template is used when a customer changes an email address in his account. The customer receives a message to confirm an email address used when changing email address. + + + This message template is used the option Email notification from Registration method dropdown list in Configuration - Settings - Customer settings is selected. The customer receives a message to confirm an email address used when registering. + + + This message template is used when the customer gets a notification about a new order being placed from this account. + + + Show alert for PM in Configuration - Settings - Forum settings.]]> + + + This message template is used when a customer forgets a password needed to log in the account. The customer receives a message with a link for entering a new password. + + + This message template is used to welcome a new customer after registration. + + + This message template is used when a new forum post in certain forum topic is created. The message is received by a store owner. + + + This message template is used when a new forum topic is created. The message is received by a store owner. + + + This message template is used to send a notification to a customer about getting a gift card. + + + Notify about new customer registration in Configuration - Settings - Customer settings.]]> + + + This message template is used to notify a customer about a new return request submitted from his/her account. + + + This message template is used when a new return request is created. The message is received by a store owner. + + + Notify about new news comments in Configuration - Settings - News settings.]]> + + + This message template is used when a customer subscribes to the newsletter to confirm the subscription. + + + Newsletter box. Allow to unsubscribe in Configuration - Settings - Customer settings.]]> + + + Notify admin when a new VAT number is submitted in Configuration - Settings - Tax settings.]]> + + + This message template is used to notify a customer that the certain order was canceled. The order can ba canceled by a customer on the account page or by store owner in Customers - Customers in Orders tab or in Sales - Orders. + + + This message template is used to notify a customer that the certain order was completed. The order gets the order status Complete when it''s paid and delivered, or it can be changed manually to Complete in Sales - Orders. + + + This message template is used to notify a customer that the certain order was paid. The order gets the payment status Paid when the amount was charged, or it can be changed manually Sales - Orders by clicking Mark as paid button in Payment status. + + + This message template is used to notify a store owner that the certain order was paid. The order gets the status Paid when the amount was charged, or it can be changed manually Sales - Orders by clicking Mark as paid button in Payment status. + + + This message template is used to notify a vendor that the certain order was paid. The order gets the status Paid when the amount was charged. + + + This message template is used to notify a customer that the certain order was placed. Orders can be viewed by a customer on the account page. + + + This message template is used to notify a store owner that the certain order was placed. Orders can be viewed by a store owner in Sales - Orders or in Customers menu. + + + This message template is used to notify a vendor that the certain order was placed. Orders can be viewed by a vendor in Sales - Orders. You can allow them to access this part of Admin Area in Configuration - Access control list. + + + This message template is used to notify a customer that the certain order was refunded. The customer can submit to refund the order when the payment status is paid on the account page, or it can be done by a store owner in Sales - Orders by clicking "Refund" button. + + + This message template is used to notify a store owner that the certain order was refunded. The customer can submit to refund the order when the payment status is paid on the account page, or it can be done by a store owner in Sales - Orders by clicking "Refund" button. + + + Notify about new product reviews in Configuration - Settings - Catalog settings.]]> + + + This message template is used to notify a store owner that the certain product attribute combination is getting low stock. You can set up the combination minimum quantity when creating or editing the product in Product attribute tab - Attributes combinations tab in Notify admin for quantity below field. + + + Minimum stock qty field.]]> + + + This message template is used to notify a customer that the certain recurring payment is canceled. Payment can be canceled by a customer in the account page or by a store owner in Sales - Recurring payments in History tab by clicking "Cancel recurring payment" button. + + + This message template is used to notify a store owner that the certain recurring payment is canceled. Payment can be canceled by a customer in the account page or by a store owner in Sales - Recurring payments in History tab by clicking "Cancel recurring payment" button. + + + This message template is used to notify a customer that the certain recurring payment is failed. For example, the amount can''t be charged from the provided credit card. + + + This message template is used to notify a customer that the request status to the certain order is changed. You can set up this option in Sales - Return requests by clicking "Notify customer about status change" button. + + + This message template is used to notify a store owner about a message sent through the contact form. + + + This message template is used to notify a vendor about a message sent through the contact form. + + + This message template is used to send "email a friend" message. + + + Display shipment events (customers).]]> + + + Display shipment events (customers).]]> + + + Allow customers to apply for vendor account.]]> + + + Allow vendors to edit info.]]> + + + This message template is used when a customer wants to share some product from the wishlist with a friend by sending an email. You can set up this option by ticking the checkbox Allow customers to email their wishlists in Configuration - Settings - Shopping cart settings. + + + Tax based on pickup point address + + + A value indicating whether to use pickup point address (when pickup point is chosen) for tax calculation. + + + It seems that you use Redis server for caching, keep in mind that enabling this setting create a lot of traffic between the Redis server and the application because of the large number of locales. + + + Export orders with products + + + Check if orders should be exported with products. + + + Requirement group is a useful feature for creating discount requirement templates. You can create a requirement group just once and then use it every time you want this limitation to be applied. You can include one requirement group into another one if needed.]]> + + + Add requirement group + + + You can choose one of the following requirement types, or add a requirement group to use several requirement types simultaneously. + + + Group name + + + Specify name of the requirement group (e.g. "Permitted customer roles"). + + + Add to group + + + Choose the group you want the requirement group you’re creating to be assigned to + + + Group + + + AND + + + OR + + + + + + Requirement + + + Default requirement group + + + Interaction type in this group is + + + The group is empty + + + Remove requirement + + + Remove group + + + Blog comments + + + Common + + + Additional sections + + + Catalog pages + + + Compare products + + + Export/Import + + + Performance + + + Product fields + + + Product page + + + Product sorting + + + Product reviews + + + Search + + + Share + + + Tax + + + Tags + + + Account + + + Common + + + Default fields + + + External authentication + + + Password and security + + + Profile + + + Time zone + + + Common + + + Feeds + + + Page sizes + + + Permissions + + + Full-Text + + + CAPTCHA + + + Common + + + Localization + + + Pdf + + + Security + + + SEO + + + Sitemap + + + Social media + + + Common + + + Other pages + + + Product + + + News comments + + + Common + + + Checkout + + + Common + + + Gift cards + + + Order totals + + + Pdf invoice + + + Common + + + Checkout + + + Common + + + Notifications + + + Common + + + Mini shopping cart + + + Wishlist + + + Common + + + Payment + + + Shipping + + + Tax dispaying + + + VAT + + + Catalog + + + Common + + + In order to use this functionality you have to disable the following setting: Configuration > Catalog settings > Ignore discounts (sitewide). + + +
    • At least two unique product identifiers are required. So each of your product shouldhave manufacturer (brand) and MPN (manufacturer part number) specified
    • Specify default tax values in your Google Merchant Center account settings
    • Specify default shipping values in your Google Merchant Center account settings
    • In order to get more info about required fields look at the following article http://www.google.com/support/merchants/bin/answer.py?answer=188494

    ]]>
    +
    + + You can download the list of allowed Google product category attributes here

    ]]>
    +
    + + If you''re using this gateway ensure that your primary store currency is supported by Paypal.

    To configure plugin follow these steps:
    1. Log into your Developer PayPal account (click here to create your account).
    2. Click on My Apps & Credentials from the Dashboard.
    3. Create new REST API app.
    4. Copy your Client ID and Secret key below.
    5. To be able to use recurring payments you need to set the webhook ID. You can get it manually in your PayPal account (enter the URL https://www.yourStore.com/Plugins/PaymentPayPalDirect/Webhook below REST API application credentials), or automatically by pressing "@T("Plugins.Payments.PayPalDirect.WebhookCreate")" button (not visible when running the site locally).

    ]]>
    +
    + + If you''re using this gateway ensure that your primary store currency is supported by Paypal.

    To use PDT, you must activate PDT and Auto Return in your PayPal account profile. You must also acquire a PDT identity token, which is used in all PDT communication you send to PayPal. Follow these steps to configure your account for PDT:

    1. Log in to your PayPal account (click here to create your account).
    2. Click the Profile subtab.
    3. Click Website Payment Preferences in the Seller Preferences column.
    4. Under Auto Return for Website Payments, click the On radio button.
    5. 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).
    6. Under Payment Data Transfer, click the On radio button.
    7. Click Save.
    8. Click Website Payment Preferences in the Seller Preferences column.
    9. Scroll down to the Payment Data Transfer section of the page to view your PDT identity token.

    Two ways to be able to receive IPN messages (optional):

    The first way is to check ''Enable IPN'' below. It will include in the request the url of you IPN handler

    The second way is to confugure your paypal account to activate this service; follow these steps:
    1. Log in to your Premier or Business account.
    2. Click the Profile subtab.
    3. Click Instant Payment Notification in the Selling Preferences column.
    4. Click the ''Edit IPN Settings'' button to update your settings.
    5. Select ''Receive IPN messages'' (Enabled) and enter the URL of your IPN handler (http://www.yourStore.com/Plugins/PaymentPayPalStandard/IPNHandler).
    6. Click Save, and you should get a message that you have successfully activated IPN.

    ]]>
    +
    + + To configure plugin follow one of these steps:
    1. If you are a Canada Post commercial customer, fill Customer number, Contract ID and API key below.
    2. If you are a Solutions for Small Business customer, specify your Customer number and API key below.
    3. If you are a non-contracted customer or you want to use the regular price of shipping paid by customers, fill the API key field only.

    Note: Canada Post gateway returns shipping price in the CAD currency, ensure that you have correctly configured exchange rate from PrimaryStoreCurrency to CAD.

    ]]>
    +
    + + Google Analytics is a free website stats tool from Google. It keeps track of statisticsabout the visitors and ecommerce conversion on your website.

    Follow the next steps to enable Google Analytics integration:
    • Create a Google Analyticsaccount and follow the wizard to add your website
    • Copy the Tracking ID into the ''ID'' box below
    • Click the ''Save'' button below and Google Analytics will be integrated into your store

    If you would like to switch between Google Analytics (used by default) and Universal Analytics, then please use the buttons below:

    ]]>
    +
    + + Please note that {ECOMMERCE} line works only when you have "Disable order completed page" order setting unticked.

    ]]>
    +
    + + Here you can find third-party extensions and themes which are developed by our community and partners.They are also available in our marketplace

    ]]>
    +
    + + A CAPTCHA is a program that can tell whether its user is a human or a computer.You''ve probably seen them — colorful images with distorted text at the bottom ofWeb registration forms. CAPTCHAs are used by many websites to prevent abuse from"bots," or automated programs usually written to generate spam. No computer programcan read distorted text as well as humans can, so bots cannot navigate sites protectedby CAPTCHAs. nopCommerce uses reCAPTCHA.

    ]]>
    +
    + + You can download more nopCommerce plugins in our marketplace

    ]]>
    +
    + + Edit values + + + Edit condition + + + Edit rules + + + {0} categories were imported + + + {0} manufacturers were imported + + + {0} products were imported + + + {0} states and provinces were imported + + + Add to wishlist + + + Update + + + + + + Store + + + Search by a specific store. + + + Rounding type + + + The rounding type. + + + Default rounding + + + Rounding up with 0.05 intervals (0.06 round to 0.10) + + + Rounding down with 0.05 intervals (0.06 round to 0.05) + + + Rounding up with 0.10 intervals (1.05 round to 1.10) + + + Rounding down with 0.10 intervals (1.05 round to 1.00) + + + Rounding with 0.50 intervals + + + Rounding with 1.00 intervals (1.01-1.49 round to 1.00, 1.50-1.99 round to 2.00) + + + Rounding up with 1.00 intervals (1.01–1.99 round to 2.00) + + + Picture zoom + + + Check to enable picture zoom on product details page. + + + Display "Blog" + + + Check if "Blog" menu item should be displayed in the top menu. + + + Display "Forums" + + + Check if "Forums" menu item should be displayed in the top menu. + + + Display "Contact us" + + + Check if "Contact us" menu item should be displayed in the top menu. + + + Display "My account" + + + Check if "My account" menu item should be displayed in the top menu. + + + Display "Home page" + + + Check if "Home page" menu item should be displayed in the top menu. + + + Display "New products" + + + Check if "New products" menu item should be displayed in the top menu. + + + Display "Search" + + + Check if "Search" menu item should be displayed in the top menu. + + + Top menu items + +
    +' + +CREATE TABLE #LocaleStringResourceTmp + ( + [ResourceName] [nvarchar](200) NOT NULL, + [ResourceValue] [nvarchar](max) NOT NULL + ) + +INSERT INTO #LocaleStringResourceTmp (ResourceName, ResourceValue) +SELECT nref.value('@Name', 'nvarchar(200)'), nref.value('Value[1]', 'nvarchar(MAX)') +FROM @resources.nodes('//Language/LocaleResource') AS R(nref) + +--do it for each existing language +DECLARE @ExistingLanguageId int +DECLARE cur_existinglanguage CURSOR FOR +SELECT [Id] +FROM [Language] +OPEN cur_existinglanguage +FETCH NEXT FROM cur_existinglanguage INTO @ExistingLanguageId +WHILE @@FETCH_STATUS = 0 +BEGIN + DECLARE @ResourceName nvarchar(200) + DECLARE @ResourceValue nvarchar(MAX) + DECLARE cur_localeresource CURSOR FOR + SELECT ResourceName, ResourceValue + FROM #LocaleStringResourceTmp + OPEN cur_localeresource + FETCH NEXT FROM cur_localeresource INTO @ResourceName, @ResourceValue + WHILE @@FETCH_STATUS = 0 + BEGIN + IF (EXISTS (SELECT 1 FROM [LocaleStringResource] WHERE LanguageId=@ExistingLanguageId AND ResourceName=@ResourceName)) + BEGIN + UPDATE [LocaleStringResource] + SET [ResourceValue]=@ResourceValue + WHERE LanguageId=@ExistingLanguageId AND ResourceName=@ResourceName + END + ELSE + BEGIN + INSERT INTO [LocaleStringResource] + ( + [LanguageId], + [ResourceName], + [ResourceValue] + ) + VALUES + ( + @ExistingLanguageId, + @ResourceName, + @ResourceValue + ) + END + + IF (@ResourceValue is null or @ResourceValue = '') + BEGIN + DELETE [LocaleStringResource] + WHERE LanguageId=@ExistingLanguageId AND ResourceName=@ResourceName + END + + FETCH NEXT FROM cur_localeresource INTO @ResourceName, @ResourceValue + END + CLOSE cur_localeresource + DEALLOCATE cur_localeresource + + --fetch next language identifier + FETCH NEXT FROM cur_existinglanguage INTO @ExistingLanguageId +END +CLOSE cur_existinglanguage +DEALLOCATE cur_existinglanguage + +DROP TABLE #LocaleStringResourceTmp +GO + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'ordersettings.invoiceident') + BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'ordersettings.invoiceident', 0, 0) + END +GO + +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'ordersettings.invoiceyear') + BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'ordersettings.invoiceyear', 2016, 0) + END +GO +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'shippingsettings.hideshippingtotal') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'shippingsettings.hideshippingtotal', N'False', 0) +END +GO + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'taxsettings.defaulttaxcategoryid') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'taxsettings.defaulttaxcategoryid', N'0', 0) +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewAddressAttribute') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'AddNewAddressAttribute', N'Add a new address attribute', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewAffiliate') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'AddNewAffiliate', N'Add a new affiliate', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewBlogPost') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'AddNewBlogPost', N'Add a new blog post', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewCampaign') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'AddNewCampaign', N'Add a new campaign', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewCountry') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'AddNewCountry', N'Add a new country', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewCurrency') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'AddNewCurrency', N'Add a new currency', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewCustomerAttribute') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'AddNewCustomerAttribute', N'Add a new customer attribute', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewCustomerAttributeValue') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'AddNewCustomerAttributeValue', N'Add a new customer attribute value', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewEmailAccount') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'AddNewEmailAccount', N'Add a new email account', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewLanguage') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'AddNewLanguage', N'Add a new language', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewMeasureDimension') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'AddNewMeasureDimension', N'Add a new measure dimension', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewMeasureWeight') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'AddNewMeasureWeight', N'Add a new measure weight', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewNews') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'AddNewNews', N'Add a new news', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'InstallNewPlugin') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'InstallNewPlugin', N'Install a new plugin', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewStateProvince') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'AddNewStateProvince', N'Add a new state or province', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewStore') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'AddNewStore', N'Add a new store', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewVendor') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'AddNewVendor', N'Add a new vendor', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewWarehouse') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'AddNewWarehouse', N'Add a new warehouse', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteAddressAttribute') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'DeleteAddressAttribute', N'Delete an address attribute', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteAffiliate') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'DeleteAffiliate', N'Delete an affiliate', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteBlogPost') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'DeleteBlogPost', N'Delete a blog post', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteCampaign') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'DeleteCampaign', N'Delete a campaign', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteCountry') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'DeleteCountry', N'Delete a country', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteCurrency') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'DeleteCurrency', N'Delete a currency', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteCustomerAttribute') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'DeleteCustomerAttribute', N'Delete a customer attribute', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteCustomerAttributeValue') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'DeleteCustomerAttributeValue', N'Delete a customer attribute value', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteEmailAccount') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'DeleteEmailAccount', N'Delete an email account', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteLanguage') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'DeleteLanguage', N'Delete a language', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteMeasureDimension') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'DeleteMeasureDimension', N'Delete a measure dimension', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteMeasureWeight') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'DeleteMeasureWeight', N'Delete a measure weight', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteMessageTemplate') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'DeleteMessageTemplate', N'Delete a message template', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteNews') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'DeleteNews', N'Delete a news', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'UninstallPlugin') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'UninstallPlugin', N'Uninstall a plugin', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteProductReview') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'DeleteProductReview', N'Delete a product review', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteStateProvince') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'DeleteStateProvince', N'Delete a state or province', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteStore') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'DeleteStore', N'Delete a store', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteVendor') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'DeleteVendor', N'Delete a vendor', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteWarehouse') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'DeleteWarehouse', N'Delete a warehouse', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditAddressAttribute') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'EditAddressAttribute', N'Edit an address attribute', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditAffiliate') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'EditAffiliate', N'Edit an affiliate', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditBlogPost') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'EditBlogPost', N'Edit a blog post', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditCampaign') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'EditCampaign', N'Edit a campaign', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditCountry') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'EditCountry', N'Edit a country', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditCurrency') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'EditCurrency', N'Edit a currency', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditCustomerAttribute') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'EditCustomerAttribute', N'Edit a customer attribute', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditCustomerAttributeValue') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'EditCustomerAttributeValue', N'Edit a customer attribute value', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditEmailAccount') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'EditEmailAccount', N'Edit an email account', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditLanguage') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'EditLanguage', N'Edit a language', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditMeasureDimension') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'EditMeasureDimension', N'Edit a measure dimension', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditMeasureWeight') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'EditMeasureWeight', N'Edit a measure weight', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditMessageTemplate') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'EditMessageTemplate', N'Edit a message template', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditNews') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'EditNews', N'Edit a news', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditPlugin') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'EditPlugin', N'Edit a plugin', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditProductReview') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'EditProductReview', N'Edit a product review', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditStateProvince') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'EditStateProvince', N'Edit a state or province', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditStore') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'EditStore', N'Edit a store', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditTask') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'EditTask', N'Edit a task', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditVendor') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'EditVendor', N'Edit a vendor', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditWarehouse') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'EditWarehouse', N'Edit a warehouse', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteBlogPostComment') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'DeleteBlogPostComment', N'Delete a blog post comment', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteNewsComment') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'DeleteNewsComment', N'Delete a news comment', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddNewAddressAttributeValue') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'AddNewAddressAttributeValue', N'Add a new address attribute value', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditAddressAttributeValue') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'EditAddressAttributeValue', N'Edit an address attribute value', N'true') +END +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'DeleteAddressAttributeValue') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'DeleteAddressAttributeValue', N'Delete an address attribute value', N'true') +END +GO + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'catalogsettings.productreviewpossibleonlyafterpurchasing') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'catalogsettings.productreviewpossibleonlyafterpurchasing', N'False', 0) +END +GO + + --new setting + IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'catalogsettings.exportimportusedropdownlistsforassociatedentities') + BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'catalogsettings.exportimportusedropdownlistsforassociatedentities', N'True', 0) + END + GO + + --new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'catalogsettings.showskuoncatalogpages') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'catalogsettings.showskuoncatalogpages', N'False', 0) +END +GO + +--rename settings +UPDATE [Setting] +SET [Name] = N'catalogsettings.showskuonproductdetailspage' +WHERE [Name] = N'catalogsettings.showproductsku' +GO + +--new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[ProductAttributeValue]') and NAME='CustomerEntersQty') +BEGIN + ALTER TABLE [ProductAttributeValue] + ADD [CustomerEntersQty] bit NULL +END +GO + +UPDATE [ProductAttributeValue] +SET [CustomerEntersQty] = 0 +WHERE [CustomerEntersQty] IS NULL +GO + +ALTER TABLE [ProductAttributeValue] ALTER COLUMN [CustomerEntersQty] bit NOT NULL +GO + +--new or update setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'shoppingcartsettings.renderassociatedattributevaluequantity') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'shoppingcartsettings.renderassociatedattributevaluequantity', N'True', 0); +END +ELSE +BEGIN + UPDATE [Setting] + SET [Value] = N'True' + WHERE [Name] = N'shoppingcartsettings.renderassociatedattributevaluequantity' +END +GO + +--update column +ALTER TABLE [RewardPointsHistory] ALTER COLUMN [PointsBalance] int NULL +GO + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'rewardpointssettings.activationdelay') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'rewardpointssettings.activationdelay', N'0', 0) +END +GO + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'rewardpointssettings.activationdelayperiodid') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'rewardpointssettings.activationdelayperiodid', N'0', 0) +END +GO + + +--new discount coupon code logic +DELETE FROM [GenericAttribute] +WHERE [KeyGroup] = 'Customer' and [Key] = 'DiscountCouponCode' +GO + +--new table +IF NOT EXISTS (SELECT 1 FROM sys.objects WHERE object_id = OBJECT_ID(N'[ProductAvailabilityRange]') and OBJECTPROPERTY(object_id, N'IsUserTable') = 1) +BEGIN + CREATE TABLE [dbo].[ProductAvailabilityRange]( + [Id] [int] IDENTITY(1,1) NOT NULL, + [Name] nvarchar(400) NOT NULL, + [DisplayOrder] int NOT NULL, + PRIMARY KEY CLUSTERED + ( + [Id] ASC + )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) + ) +END +GO + +--add a new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Product]') and NAME='ProductAvailabilityRangeId') +BEGIN + ALTER TABLE [Product] + ADD [ProductAvailabilityRangeId] int NULL +END +GO + +UPDATE [Product] +SET [ProductAvailabilityRangeId] = 0 +WHERE [ProductAvailabilityRangeId] IS NULL +GO + +ALTER TABLE [Product] ALTER COLUMN [ProductAvailabilityRangeId] int NOT NULL +GO + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'paymentsettings.showpaymentmethoddescriptions') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'paymentsettings.showpaymentmethoddescriptions', N'True', 0) +END +GO + + +--ensure that dbo is added to existing stored procedures +IF EXISTS ( + SELECT * + FROM sys.objects + WHERE object_id = OBJECT_ID(N'[FullText_IsSupported]') AND OBJECTPROPERTY(object_id,N'IsProcedure') = 1) +DROP PROCEDURE [FullText_IsSupported] +GO +CREATE PROCEDURE [dbo].[FullText_IsSupported] +AS +BEGIN + EXEC(' + SELECT CASE SERVERPROPERTY(''IsFullTextInstalled'') + WHEN 1 THEN + CASE DatabaseProperty (DB_NAME(DB_ID()), ''IsFulltextEnabled'') + WHEN 1 THEN 1 + ELSE 0 + END + ELSE 0 + END') +END +GO + + +IF EXISTS ( + SELECT * + FROM sys.objects + WHERE object_id = OBJECT_ID(N'[FullText_Enable]') AND OBJECTPROPERTY(object_id,N'IsProcedure') = 1) +DROP PROCEDURE [FullText_Enable] +GO +CREATE PROCEDURE [dbo].[FullText_Enable] +AS +BEGIN + --create catalog + EXEC(' + IF NOT EXISTS (SELECT 1 FROM sys.fulltext_catalogs WHERE [name] = ''nopCommerceFullTextCatalog'') + CREATE FULLTEXT CATALOG [nopCommerceFullTextCatalog] AS DEFAULT') + + --create indexes + DECLARE @create_index_text nvarchar(4000) + SET @create_index_text = ' + IF NOT EXISTS (SELECT 1 FROM sys.fulltext_indexes WHERE object_id = object_id(''[Product]'')) + CREATE FULLTEXT INDEX ON [Product]([Name], [ShortDescription], [FullDescription]) + KEY INDEX [' + dbo.[nop_getprimarykey_indexname] ('Product') + '] ON [nopCommerceFullTextCatalog] WITH CHANGE_TRACKING AUTO' + EXEC(@create_index_text) + + SET @create_index_text = ' + IF NOT EXISTS (SELECT 1 FROM sys.fulltext_indexes WHERE object_id = object_id(''[LocalizedProperty]'')) + CREATE FULLTEXT INDEX ON [LocalizedProperty]([LocaleValue]) + KEY INDEX [' + dbo.[nop_getprimarykey_indexname] ('LocalizedProperty') + '] ON [nopCommerceFullTextCatalog] WITH CHANGE_TRACKING AUTO' + EXEC(@create_index_text) + + SET @create_index_text = ' + IF NOT EXISTS (SELECT 1 FROM sys.fulltext_indexes WHERE object_id = object_id(''[ProductTag]'')) + CREATE FULLTEXT INDEX ON [ProductTag]([Name]) + KEY INDEX [' + dbo.[nop_getprimarykey_indexname] ('ProductTag') + '] ON [nopCommerceFullTextCatalog] WITH CHANGE_TRACKING AUTO' + EXEC(@create_index_text) +END +GO + + + +IF EXISTS ( + SELECT * + FROM sys.objects + WHERE object_id = OBJECT_ID(N'[FullText_Disable]') AND OBJECTPROPERTY(object_id,N'IsProcedure') = 1) +DROP PROCEDURE [FullText_Disable] +GO +CREATE PROCEDURE [dbo].[FullText_Disable] +AS +BEGIN + EXEC(' + --drop indexes + IF EXISTS (SELECT 1 FROM sys.fulltext_indexes WHERE object_id = object_id(''[Product]'')) + DROP FULLTEXT INDEX ON [Product] + ') + + EXEC(' + IF EXISTS (SELECT 1 FROM sys.fulltext_indexes WHERE object_id = object_id(''[LocalizedProperty]'')) + DROP FULLTEXT INDEX ON [LocalizedProperty] + ') + + EXEC(' + IF EXISTS (SELECT 1 FROM sys.fulltext_indexes WHERE object_id = object_id(''[ProductTag]'')) + DROP FULLTEXT INDEX ON [ProductTag] + ') + + --drop catalog + EXEC(' + IF EXISTS (SELECT 1 FROM sys.fulltext_catalogs WHERE [name] = ''nopCommerceFullTextCatalog'') + DROP FULLTEXT CATALOG [nopCommerceFullTextCatalog] + ') +END +GO + + + + +IF EXISTS ( + SELECT * + FROM sys.objects + WHERE object_id = OBJECT_ID(N'[LanguagePackImport]') AND OBJECTPROPERTY(object_id,N'IsProcedure') = 1) +DROP PROCEDURE [LanguagePackImport] +GO +CREATE PROCEDURE [dbo].[LanguagePackImport] +( + @LanguageId int, + @XmlPackage xml +) +AS +BEGIN + IF EXISTS(SELECT * FROM [Language] WHERE [Id] = @LanguageId) + BEGIN + CREATE TABLE #LocaleStringResourceTmp + ( + [LanguageId] [int] NOT NULL, + [ResourceName] [nvarchar](200) NOT NULL, + [ResourceValue] [nvarchar](MAX) NOT NULL + ) + + INSERT INTO #LocaleStringResourceTmp (LanguageID, ResourceName, ResourceValue) + SELECT @LanguageId, nref.value('@Name', 'nvarchar(200)'), nref.value('Value[1]', 'nvarchar(MAX)') + FROM @XmlPackage.nodes('//Language/LocaleResource') AS R(nref) + + DECLARE @ResourceName nvarchar(200) + DECLARE @ResourceValue nvarchar(MAX) + DECLARE cur_localeresource CURSOR FOR + SELECT LanguageID, ResourceName, ResourceValue + FROM #LocaleStringResourceTmp + OPEN cur_localeresource + FETCH NEXT FROM cur_localeresource INTO @LanguageId, @ResourceName, @ResourceValue + WHILE @@FETCH_STATUS = 0 + BEGIN + IF (EXISTS (SELECT 1 FROM [LocaleStringResource] WHERE LanguageID=@LanguageId AND ResourceName=@ResourceName)) + BEGIN + UPDATE [LocaleStringResource] + SET [ResourceValue]=@ResourceValue + WHERE LanguageID=@LanguageId AND ResourceName=@ResourceName + END + ELSE + BEGIN + INSERT INTO [LocaleStringResource] + ( + [LanguageId], + [ResourceName], + [ResourceValue] + ) + VALUES + ( + @LanguageId, + @ResourceName, + @ResourceValue + ) + END + + + FETCH NEXT FROM cur_localeresource INTO @LanguageId, @ResourceName, @ResourceValue + END + CLOSE cur_localeresource + DEALLOCATE cur_localeresource + + DROP TABLE #LocaleStringResourceTmp + END +END + + --new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'fixedorbyweightsettings.shippingbyweightenabled') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'fixedorbyweightsettings.shippingbyweightenabled', N'False', 0) +END +GO + +--rename settings +UPDATE [Setting] +SET [Name] = N'fixedorbyweightsettings.limitmethodstocreated' +WHERE [Name] = N'shippingbyweightsettings.limitmethodstocreated' +GO + +--rename settings +UPDATE [Setting] +SET [Name] = N'shippingratecomputationmethod.fixedorbyweight.rate.shippingmethodid' + SUBSTRING(name, 62, len(name)) +WHERE [Name] like N'shippingratecomputationmethod.fixedrate.rate.shippingmethodid%' +GO + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'vendorsettings.allowvendorstoimportproducts') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'vendorsettings.allowvendorstoimportproducts', N'True', 0) +END +GO + +--add a new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[TierPrice]') and NAME='StartDateTimeUtc') +BEGIN + ALTER TABLE [TierPrice] + ADD [StartDateTimeUtc] datetime NULL +END +GO + +--add a new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[TierPrice]') and NAME='EndDateTimeUtc') +BEGIN + ALTER TABLE [TierPrice] + ADD [EndDateTimeUtc] datetime NULL +END +GO + +--add a tier prices instead of product special prices +IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Product]') and NAME='SpecialPrice') +BEGIN + EXEC(' + INSERT INTO [dbo].[TierPrice]([ProductId], [StoreId], [CustomerRoleId], [Quantity], [Price], [StartDateTimeUtc], [EndDateTimeUtc]) + SELECT [Id], 0, NULL, 1, [SpecialPrice], [SpecialPriceStartDateTimeUtc], [SpecialPriceEndDateTimeUtc] + FROM [dbo].[Product] + WHERE [SpecialPrice] <> 0') +END +GO + +UPDATE [Product] +SET [HasTierPrices] = 1 +WHERE [Id] IN (SELECT [ProductId] FROM [dbo].[TierPrice]) +GO + +--drop column +IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Product]') and NAME='SpecialPrice') +BEGIN + ALTER TABLE [Product] DROP COLUMN [SpecialPrice] +END +GO + +--drop column +IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Product]') and NAME='SpecialPriceStartDateTimeUtc') +BEGIN + ALTER TABLE [Product] DROP COLUMN [SpecialPriceStartDateTimeUtc] +END +GO + +--drop column +IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Product]') and NAME='SpecialPriceEndDateTimeUtc') +BEGIN + ALTER TABLE [Product] DROP COLUMN [SpecialPriceEndDateTimeUtc] +END +GO + +--delete setting +DELETE FROM [Setting] +WHERE [name] = N'producteditordettings.specialprice' +GO + +--delete setting +DELETE FROM [Setting] +WHERE [name] = N'producteditordettings.specialpricestartdate' +GO + +--delete setting +DELETE FROM [Setting] +WHERE [name] = N'producteditordettings.specialpriceenddate' +GO + +--add a new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[TierPrice]') and NAME='StartDateTimeUtc') +BEGIN + ALTER TABLE [TierPrice] + ADD [StartDateTimeUtc] datetime NULL +END +GO + +--add a new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[TierPrice]') and NAME='EndDateTimeUtc') +BEGIN + ALTER TABLE [TierPrice] + ADD [EndDateTimeUtc] datetime NULL +END +GO + +IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[OrderItem]') and NAME='VatRate') + EXEC sp_rename 'OrderItem.VatRate', 'TaxRate', 'COLUMN' +GO + +--add a new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[OrderItem]') and NAME='TaxRate') +BEGIN + ALTER TABLE [OrderItem] + ADD [TaxRate] decimal(18,4) NULL +END +GO + +IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[OrderItem]') and NAME='TaxRate') +BEGIN + + UPDATE OrderItem + SET [TaxRate] = 0 + WHERE ([TaxRate] IS NULL) AND (PriceInclTax = PriceExclTax) + + UPDATE OrderItem + SET [TaxRate] = round((PriceInclTax / PriceExclTax - 1) * 100, 2) + WHERE ([TaxRate] IS NULL) AND PriceExclTax <> 0 + + ALTER TABLE [OrderItem] + ALTER COLUMN [TaxRate] DECIMAL(18,4) NOT NULL +END +GO + +--add a new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='InvoiceId') +BEGIN + ALTER TABLE [dbo].[Order] + ADD InvoiceId NVARCHAR(20) NULL; +END + +GO +DROP INDEX IF EXISTS [UI_Order_InvoiceId] ON [dbo].[Order] +GO + +IF NOT EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'[dbo].[Order]') AND name = N'UI_Order_InvoiceId') +BEGIN + CREATE UNIQUE NONCLUSTERED INDEX UI_Order_InvoiceId + ON dbo.[Order](InvoiceId, StoreId) + WHERE InvoiceId IS NOT NULL; +END +GO + +--add a new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='InvoiceDateUtc') +BEGIN + ALTER TABLE [dbo].[Order] + ADD InvoiceDateUtc DATETIME NULL; +END + +GO + +--add a new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='OrderAmount') +BEGIN + ALTER TABLE [dbo].[Order] + ADD OrderAmount DECIMAL(18,4) NULL; +END + +GO + +IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='OrderAmount') +BEGIN + + UPDATE [dbo].[Order] + SET [OrderAmount] = OrderSubtotalExclTax - ISNULL(OrderSubTotalDiscountExclTax, 0) + ISNULL(OrderShippingExclTax, 0) + ISNULL(PaymentMethodAdditionalFeeExclTax, 0) - ISNULL(OrderDiscount, 0) + WHERE CustomerTaxDisplayTypeId = 10 AND OrderSubtotalExclTax IS NOT NULL + + UPDATE [dbo].[Order] + SET [OrderAmount] = OrderSubtotalInclTax - ISNULL(OrderSubTotalDiscountInclTax, 0) + ISNULL(OrderShippingInclTax, 0) + ISNULL(PaymentMethodAdditionalFeeInclTax, 0) - ISNULL(OrderDiscount, 0) - ISNULL(OrderTax, 0) + WHERE CustomerTaxDisplayTypeId = 0 AND OrderSubtotalInclTax IS NOT NULL +END + +GO + +--add a new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='OrderAmountIncl') +BEGIN + ALTER TABLE [dbo].[Order] + ADD OrderAmountIncl DECIMAL(18,4) NULL; +END + +GO + +IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='OrderAmountIncl') +BEGIN + + UPDATE [dbo].[Order] + SET [OrderAmountIncl] = OrderSubtotalExclTax - ISNULL(OrderSubTotalDiscountExclTax, 0) + ISNULL(OrderShippingExclTax, 0) + ISNULL(PaymentMethodAdditionalFeeExclTax, 0) - ISNULL(OrderDiscount, 0) + ISNULL(OrderTax, 0) + WHERE CustomerTaxDisplayTypeId = 10 AND OrderSubtotalExclTax IS NOT NULL + + UPDATE [dbo].[Order] + SET [OrderAmountIncl] = OrderSubtotalInclTax - ISNULL(OrderSubTotalDiscountInclTax, 0) + ISNULL(OrderShippingInclTax, 0) + ISNULL(PaymentMethodAdditionalFeeInclTax, 0) - ISNULL(OrderDiscount, 0) + WHERE CustomerTaxDisplayTypeId = 0 AND OrderSubtotalExclTax IS NOT NULL +END + +GO + +--add a new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='OrderDiscountIncl') +BEGIN + ALTER TABLE [dbo].[Order] + ADD OrderDiscountIncl DECIMAL(18,4) NULL; +END +GO +IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='OrderDiscountIncl') + UPDATE [dbo].[Order] + SET [OrderDiscount] = OrderSubtotalExclTax - ISNULL(OrderSubTotalDiscountExclTax, 0) + ISNULL(OrderShippingExclTax, 0) + ISNULL(PaymentMethodAdditionalFeeExclTax, 0) + ISNULL(OrderTax, 0), + [OrderDiscountIncl] = [OrderDiscount] + WHERE CustomerTaxDisplayTypeId = 10 + + UPDATE [dbo].[Order] + SET + [OrderDiscountIncl] = OrderSubtotalInclTax - ISNULL(OrderSubTotalDiscountInclTax, 0) + ISNULL(OrderShippingInclTax, 0) + ISNULL(PaymentMethodAdditionalFeeInclTax, 0) + WHERE CustomerTaxDisplayTypeId = 0 +GO + +IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='OrderDiscountIncl') +ALTER TABLE [Order] ALTER COLUMN OrderDiscountIncl DECIMAL(18,4) NOT NULL +GO + +--add a new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='OrderShippingNonTaxable') +BEGIN + ALTER TABLE [dbo].[Order] + ADD OrderShippingNonTaxable DECIMAL(18,4) NULL; +END +GO + +--init +IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='OrderShippingNonTaxable') + UPDATE [dbo].[Order] + SET OrderShippingNonTaxable = 0 + where OrderShippingNonTaxable is Null + /*update old orders? like. Will not work for taxempted customers + [OrderShippingNonTax] = OrderShippingExclTax, + OrderShippingExclTax = 0, + OrderShippingInclTax = 0 + WHERE OrderShippingExclTax = OrderShippingInclTax */ +GO + +IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='OrderShippingNonTaxable') +ALTER TABLE [Order] ALTER COLUMN OrderShippingNonTaxable DECIMAL(18,4) NOT NULL +GO + +--add a new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='PaymentMethodAdditionalFeeNonTaxable') +BEGIN + ALTER TABLE [dbo].[Order] + ADD PaymentMethodAdditionalFeeNonTaxable DECIMAL(18,4) NULL; +END +GO + +--init +IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='PaymentMethodAdditionalFeeNonTaxable') + UPDATE [dbo].[Order] + SET [PaymentMethodAdditionalFeeNonTaxable] = 0 + where [PaymentMethodAdditionalFeeNonTaxable] is Null + /*update old orders? like. Will not work for taxempted customers + [PaymentMethodAdditionalFeeNonTaxable] = PaymentMethodAdditionalFeeInclTax, + PaymentMethodAdditionalFeeExclTax = 0, + PaymentMethodAdditionalFeeInclTax = 0 + WHERE PaymentMethodAdditionalFeeInclTax = PaymentMethodAdditionalFeeExclTax */ +GO + +IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='PaymentMethodAdditionalFeeNonTaxable') +ALTER TABLE [Order] ALTER COLUMN PaymentMethodAdditionalFeeNonTaxable DECIMAL(18,4) NOT NULL +GO + +--add a new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='EarnedRewardPointsBaseAmountIncl') +BEGIN + ALTER TABLE [dbo].[Order] + ADD EarnedRewardPointsBaseAmountIncl DECIMAL(18,4) NULL; +END +GO +IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='EarnedRewardPointsBaseAmountIncl') +UPDATE [dbo].[Order] + SET EarnedRewardPointsBaseAmountIncl = 0 + WHERE EarnedRewardPointsBaseAmountIncl IS NULL +GO +IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='EarnedRewardPointsBaseAmountIncl') +ALTER TABLE [Order] ALTER COLUMN EarnedRewardPointsBaseAmountIncl DECIMAL(18,4) NOT NULL +GO + +--add a new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='EarnedRewardPointsBaseAmountExcl') +BEGIN + ALTER TABLE [dbo].[Order] + ADD EarnedRewardPointsBaseAmountExcl DECIMAL(18,4) NULL; +END +GO +IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='EarnedRewardPointsBaseAmountExcl') +UPDATE [dbo].[Order] + SET EarnedRewardPointsBaseAmountExcl = 0 + WHERE EarnedRewardPointsBaseAmountExcl IS NULL +GO +IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='EarnedRewardPointsBaseAmountExcl') +ALTER TABLE [Order] ALTER COLUMN EarnedRewardPointsBaseAmountExcl DECIMAL(18,4) NOT NULL +GO + +--a stored procedure update +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[ProductLoadAllPaged]') AND OBJECTPROPERTY(object_id,N'IsProcedure') = 1) +DROP PROCEDURE [ProductLoadAllPaged] +GO +CREATE PROCEDURE [dbo].[ProductLoadAllPaged] +( + @CategoryIds nvarchar(MAX) = null, --a list of category IDs (comma-separated list). e.g. 1,2,3 + @ManufacturerId int = 0, + @StoreId int = 0, + @VendorId int = 0, + @WarehouseId int = 0, + @ProductTypeId int = null, --product type identifier, null - load all products + @VisibleIndividuallyOnly bit = 0, --0 - load all products , 1 - "visible indivially" only + @MarkedAsNewOnly bit = 0, --0 - load all products , 1 - "marked as new" only + @ProductTagId int = 0, + @FeaturedProducts bit = null, --0 featured only , 1 not featured only, null - load all products + @PriceMin decimal(18, 4) = null, + @PriceMax decimal(18, 4) = null, + @Keywords nvarchar(4000) = null, + @SearchDescriptions bit = 0, --a value indicating whether to search by a specified "keyword" in product descriptions + @SearchManufacturerPartNumber bit = 0, -- a value indicating whether to search by a specified "keyword" in manufacturer part number + @SearchSku bit = 0, --a value indicating whether to search by a specified "keyword" in product SKU + @SearchProductTags bit = 0, --a value indicating whether to search by a specified "keyword" in product tags + @UseFullTextSearch bit = 0, + @FullTextMode int = 0, --0 - using CONTAINS with , 5 - using CONTAINS and OR with , 10 - using CONTAINS and AND with + @FilteredSpecs nvarchar(MAX) = null, --filter by specification attribute options (comma-separated list of IDs). e.g. 14,15,16 + @LanguageId int = 0, + @OrderBy int = 0, --0 - position, 5 - Name: A to Z, 6 - Name: Z to A, 10 - Price: Low to High, 11 - Price: High to Low, 15 - creation date + @AllowedCustomerRoleIds nvarchar(MAX) = null, --a list of customer role IDs (comma-separated list) for which a product should be shown (if a subjet to ACL) + @PageIndex int = 0, + @PageSize int = 2147483644, + @ShowHidden bit = 0, + @OverridePublished bit = null, --null - process "Published" property according to "showHidden" parameter, true - load only "Published" products, false - load only "Unpublished" products + @LoadFilterableSpecificationAttributeOptionIds bit = 0, --a value indicating whether we should load the specification attribute option identifiers applied to loaded products (all pages) + @FilterableSpecificationAttributeOptionIds nvarchar(MAX) = null OUTPUT, --the specification attribute option identifiers applied to loaded products (all pages). returned as a comma separated list of identifiers + @TotalRecords int = null OUTPUT +) +AS +BEGIN + + /* Products that filtered by keywords */ + CREATE TABLE #KeywordProducts + ( + [ProductId] int NOT NULL + ) + + DECLARE + @SearchKeywords bit, + @OriginalKeywords nvarchar(4000), + @sql nvarchar(max), + @sql_orderby nvarchar(max) + + SET NOCOUNT ON + + --filter by keywords + SET @Keywords = isnull(@Keywords, '') + SET @Keywords = rtrim(ltrim(@Keywords)) + SET @OriginalKeywords = @Keywords + IF ISNULL(@Keywords, '') != '' + BEGIN + SET @SearchKeywords = 1 + + IF @UseFullTextSearch = 1 + BEGIN + --remove wrong chars (' ") + SET @Keywords = REPLACE(@Keywords, '''', '') + SET @Keywords = REPLACE(@Keywords, '"', '') + + --full-text search + IF @FullTextMode = 0 + BEGIN + --0 - using CONTAINS with + SET @Keywords = ' "' + @Keywords + '*" ' + END + ELSE + BEGIN + --5 - using CONTAINS and OR with + --10 - using CONTAINS and AND with + + --clean multiple spaces + WHILE CHARINDEX(' ', @Keywords) > 0 + SET @Keywords = REPLACE(@Keywords, ' ', ' ') + + DECLARE @concat_term nvarchar(100) + IF @FullTextMode = 5 --5 - using CONTAINS and OR with + BEGIN + SET @concat_term = 'OR' + END + IF @FullTextMode = 10 --10 - using CONTAINS and AND with + BEGIN + SET @concat_term = 'AND' + END + + --now let's build search string + declare @fulltext_keywords nvarchar(4000) + set @fulltext_keywords = N'' + declare @index int + + set @index = CHARINDEX(' ', @Keywords, 0) + + -- if index = 0, then only one field was passed + IF(@index = 0) + set @fulltext_keywords = ' "' + @Keywords + '*" ' + ELSE + BEGIN + DECLARE @first BIT + SET @first = 1 + WHILE @index > 0 + BEGIN + IF (@first = 0) + SET @fulltext_keywords = @fulltext_keywords + ' ' + @concat_term + ' ' + ELSE + SET @first = 0 + + SET @fulltext_keywords = @fulltext_keywords + '"' + SUBSTRING(@Keywords, 1, @index - 1) + '*"' + SET @Keywords = SUBSTRING(@Keywords, @index + 1, LEN(@Keywords) - @index) + SET @index = CHARINDEX(' ', @Keywords, 0) + end + + -- add the last field + IF LEN(@fulltext_keywords) > 0 + SET @fulltext_keywords = @fulltext_keywords + ' ' + @concat_term + ' ' + '"' + SUBSTRING(@Keywords, 1, LEN(@Keywords)) + '*"' + END + SET @Keywords = @fulltext_keywords + END + END + ELSE + BEGIN + --usual search by PATINDEX + SET @Keywords = '%' + @Keywords + '%' + END + --PRINT @Keywords + + --product name + SET @sql = ' + INSERT INTO #KeywordProducts ([ProductId]) + SELECT p.Id + FROM Product p with (NOLOCK) + WHERE ' + IF @UseFullTextSearch = 1 + SET @sql = @sql + 'CONTAINS(p.[Name], @Keywords) ' + ELSE + SET @sql = @sql + 'PATINDEX(@Keywords, p.[Name]) > 0 ' + + + --localized product name + SET @sql = @sql + ' + UNION + SELECT lp.EntityId + FROM LocalizedProperty lp with (NOLOCK) + WHERE + lp.LocaleKeyGroup = N''Product'' + AND lp.LanguageId = ' + ISNULL(CAST(@LanguageId AS nvarchar(max)), '0') + ' + AND lp.LocaleKey = N''Name''' + IF @UseFullTextSearch = 1 + SET @sql = @sql + ' AND CONTAINS(lp.[LocaleValue], @Keywords) ' + ELSE + SET @sql = @sql + ' AND PATINDEX(@Keywords, lp.[LocaleValue]) > 0 ' + + + IF @SearchDescriptions = 1 + BEGIN + --product short description + SET @sql = @sql + ' + UNION + SELECT p.Id + FROM Product p with (NOLOCK) + WHERE ' + IF @UseFullTextSearch = 1 + SET @sql = @sql + 'CONTAINS(p.[ShortDescription], @Keywords) ' + ELSE + SET @sql = @sql + 'PATINDEX(@Keywords, p.[ShortDescription]) > 0 ' + + + --product full description + SET @sql = @sql + ' + UNION + SELECT p.Id + FROM Product p with (NOLOCK) + WHERE ' + IF @UseFullTextSearch = 1 + SET @sql = @sql + 'CONTAINS(p.[FullDescription], @Keywords) ' + ELSE + SET @sql = @sql + 'PATINDEX(@Keywords, p.[FullDescription]) > 0 ' + + + + --localized product short description + SET @sql = @sql + ' + UNION + SELECT lp.EntityId + FROM LocalizedProperty lp with (NOLOCK) + WHERE + lp.LocaleKeyGroup = N''Product'' + AND lp.LanguageId = ' + ISNULL(CAST(@LanguageId AS nvarchar(max)), '0') + ' + AND lp.LocaleKey = N''ShortDescription''' + IF @UseFullTextSearch = 1 + SET @sql = @sql + ' AND CONTAINS(lp.[LocaleValue], @Keywords) ' + ELSE + SET @sql = @sql + ' AND PATINDEX(@Keywords, lp.[LocaleValue]) > 0 ' + + + --localized product full description + SET @sql = @sql + ' + UNION + SELECT lp.EntityId + FROM LocalizedProperty lp with (NOLOCK) + WHERE + lp.LocaleKeyGroup = N''Product'' + AND lp.LanguageId = ' + ISNULL(CAST(@LanguageId AS nvarchar(max)), '0') + ' + AND lp.LocaleKey = N''FullDescription''' + IF @UseFullTextSearch = 1 + SET @sql = @sql + ' AND CONTAINS(lp.[LocaleValue], @Keywords) ' + ELSE + SET @sql = @sql + ' AND PATINDEX(@Keywords, lp.[LocaleValue]) > 0 ' + END + + --manufacturer part number (exact match) + IF @SearchManufacturerPartNumber = 1 + BEGIN + SET @sql = @sql + ' + UNION + SELECT p.Id + FROM Product p with (NOLOCK) + WHERE p.[ManufacturerPartNumber] = @OriginalKeywords ' + END + + --SKU (exact match) + IF @SearchSku = 1 + BEGIN + SET @sql = @sql + ' + UNION + SELECT p.Id + FROM Product p with (NOLOCK) + WHERE p.[Sku] = @OriginalKeywords ' + END + + IF @SearchProductTags = 1 + BEGIN + --product tags (exact match) + SET @sql = @sql + ' + UNION + SELECT pptm.Product_Id + FROM Product_ProductTag_Mapping pptm with(NOLOCK) INNER JOIN ProductTag pt with(NOLOCK) ON pt.Id = pptm.ProductTag_Id + WHERE pt.[Name] = @OriginalKeywords ' + + --localized product tags + SET @sql = @sql + ' + UNION + SELECT pptm.Product_Id + FROM LocalizedProperty lp with (NOLOCK) INNER JOIN Product_ProductTag_Mapping pptm with(NOLOCK) ON lp.EntityId = pptm.ProductTag_Id + WHERE + lp.LocaleKeyGroup = N''ProductTag'' + AND lp.LanguageId = ' + ISNULL(CAST(@LanguageId AS nvarchar(max)), '0') + ' + AND lp.LocaleKey = N''Name'' + AND lp.[LocaleValue] = @OriginalKeywords ' + END + + --PRINT (@sql) + EXEC sp_executesql @sql, N'@Keywords nvarchar(4000), @OriginalKeywords nvarchar(4000)', @Keywords, @OriginalKeywords + + END + ELSE + BEGIN + SET @SearchKeywords = 0 + END + + --filter by category IDs + SET @CategoryIds = isnull(@CategoryIds, '') + CREATE TABLE #FilteredCategoryIds + ( + CategoryId int not null + ) + INSERT INTO #FilteredCategoryIds (CategoryId) + SELECT CAST(data as int) FROM [nop_splitstring_to_table](@CategoryIds, ',') + DECLARE @CategoryIdsCount int + SET @CategoryIdsCount = (SELECT COUNT(1) FROM #FilteredCategoryIds) + + --filter by customer role IDs (access control list) + SET @AllowedCustomerRoleIds = isnull(@AllowedCustomerRoleIds, '') + CREATE TABLE #FilteredCustomerRoleIds + ( + CustomerRoleId int not null + ) + INSERT INTO #FilteredCustomerRoleIds (CustomerRoleId) + SELECT CAST(data as int) FROM [nop_splitstring_to_table](@AllowedCustomerRoleIds, ',') + DECLARE @FilteredCustomerRoleIdsCount int + SET @FilteredCustomerRoleIdsCount = (SELECT COUNT(1) FROM #FilteredCustomerRoleIds) + + --paging + DECLARE @PageLowerBound int + DECLARE @PageUpperBound int + DECLARE @RowsToReturn int + SET @RowsToReturn = @PageSize * (@PageIndex + 1) + SET @PageLowerBound = @PageSize * @PageIndex + SET @PageUpperBound = @PageLowerBound + @PageSize + 1 + + CREATE TABLE #DisplayOrderTmp + ( + [Id] int IDENTITY (1, 1) NOT NULL, + [ProductId] int NOT NULL + ) + + SET @sql = ' + SELECT p.Id + FROM + Product p with (NOLOCK)' + + IF @CategoryIdsCount > 0 + BEGIN + SET @sql = @sql + ' + LEFT JOIN Product_Category_Mapping pcm with (NOLOCK) + ON p.Id = pcm.ProductId' + END + + IF @ManufacturerId > 0 + BEGIN + SET @sql = @sql + ' + LEFT JOIN Product_Manufacturer_Mapping pmm with (NOLOCK) + ON p.Id = pmm.ProductId' + END + + IF ISNULL(@ProductTagId, 0) != 0 + BEGIN + SET @sql = @sql + ' + LEFT JOIN Product_ProductTag_Mapping pptm with (NOLOCK) + ON p.Id = pptm.Product_Id' + END + + --searching by keywords + IF @SearchKeywords = 1 + BEGIN + SET @sql = @sql + ' + JOIN #KeywordProducts kp + ON p.Id = kp.ProductId' + END + + SET @sql = @sql + ' + WHERE + p.Deleted = 0' + + --filter by category + IF @CategoryIdsCount > 0 + BEGIN + SET @sql = @sql + ' + AND pcm.CategoryId IN (SELECT CategoryId FROM #FilteredCategoryIds)' + + IF @FeaturedProducts IS NOT NULL + BEGIN + SET @sql = @sql + ' + AND pcm.IsFeaturedProduct = ' + CAST(@FeaturedProducts AS nvarchar(max)) + END + END + + --filter by manufacturer + IF @ManufacturerId > 0 + BEGIN + SET @sql = @sql + ' + AND pmm.ManufacturerId = ' + CAST(@ManufacturerId AS nvarchar(max)) + + IF @FeaturedProducts IS NOT NULL + BEGIN + SET @sql = @sql + ' + AND pmm.IsFeaturedProduct = ' + CAST(@FeaturedProducts AS nvarchar(max)) + END + END + + --filter by vendor + IF @VendorId > 0 + BEGIN + SET @sql = @sql + ' + AND p.VendorId = ' + CAST(@VendorId AS nvarchar(max)) + END + + --filter by warehouse + IF @WarehouseId > 0 + BEGIN + --we should also ensure that 'ManageInventoryMethodId' is set to 'ManageStock' (1) + --but we skip it in order to prevent hard-coded values (e.g. 1) and for better performance + SET @sql = @sql + ' + AND + ( + (p.UseMultipleWarehouses = 0 AND + p.WarehouseId = ' + CAST(@WarehouseId AS nvarchar(max)) + ') + OR + (p.UseMultipleWarehouses > 0 AND + EXISTS (SELECT 1 FROM ProductWarehouseInventory [pwi] + WHERE [pwi].WarehouseId = ' + CAST(@WarehouseId AS nvarchar(max)) + ' AND [pwi].ProductId = p.Id)) + )' + END + + --filter by product type + IF @ProductTypeId is not null + BEGIN + SET @sql = @sql + ' + AND p.ProductTypeId = ' + CAST(@ProductTypeId AS nvarchar(max)) + END + + --filter by "visible individually" + IF @VisibleIndividuallyOnly = 1 + BEGIN + SET @sql = @sql + ' + AND p.VisibleIndividually = 1' + END + + --filter by "marked as new" + IF @MarkedAsNewOnly = 1 + BEGIN + SET @sql = @sql + ' + AND p.MarkAsNew = 1 + AND (getutcdate() BETWEEN ISNULL(p.MarkAsNewStartDateTimeUtc, ''1/1/1900'') and ISNULL(p.MarkAsNewEndDateTimeUtc, ''1/1/2999''))' + END + + --filter by product tag + IF ISNULL(@ProductTagId, 0) != 0 + BEGIN + SET @sql = @sql + ' + AND pptm.ProductTag_Id = ' + CAST(@ProductTagId AS nvarchar(max)) + END + + --"Published" property + IF (@OverridePublished is null) + BEGIN + --process according to "showHidden" + IF @ShowHidden = 0 + BEGIN + SET @sql = @sql + ' + AND p.Published = 1' + END + END + ELSE IF (@OverridePublished = 1) + BEGIN + --published only + SET @sql = @sql + ' + AND p.Published = 1' + END + ELSE IF (@OverridePublished = 0) + BEGIN + --unpublished only + SET @sql = @sql + ' + AND p.Published = 0' + END + + --show hidden + IF @ShowHidden = 0 + BEGIN + SET @sql = @sql + ' + AND p.Deleted = 0 + AND (getutcdate() BETWEEN ISNULL(p.AvailableStartDateTimeUtc, ''1/1/1900'') and ISNULL(p.AvailableEndDateTimeUtc, ''1/1/2999''))' + END + + --min price + IF @PriceMin is not null + BEGIN + SET @sql = @sql + ' + AND (p.Price >= ' + CAST(@PriceMin AS nvarchar(max)) + ')' + END + + --max price + IF @PriceMax is not null + BEGIN + SET @sql = @sql + ' + AND (p.Price <= ' + CAST(@PriceMax AS nvarchar(max)) + ')' + END + + --show hidden and ACL + IF @ShowHidden = 0 and @FilteredCustomerRoleIdsCount > 0 + BEGIN + SET @sql = @sql + ' + AND (p.SubjectToAcl = 0 OR EXISTS ( + SELECT 1 FROM #FilteredCustomerRoleIds [fcr] + WHERE + [fcr].CustomerRoleId IN ( + SELECT [acl].CustomerRoleId + FROM [AclRecord] acl with (NOLOCK) + WHERE [acl].EntityId = p.Id AND [acl].EntityName = ''Product'' + ) + ))' + END + + --filter by store + IF @StoreId > 0 + BEGIN + SET @sql = @sql + ' + AND (p.LimitedToStores = 0 OR EXISTS ( + SELECT 1 FROM [StoreMapping] sm with (NOLOCK) + WHERE [sm].EntityId = p.Id AND [sm].EntityName = ''Product'' and [sm].StoreId=' + CAST(@StoreId AS nvarchar(max)) + ' + ))' + END + + --prepare filterable specification attribute option identifier (if requested) + IF @LoadFilterableSpecificationAttributeOptionIds = 1 + BEGIN + CREATE TABLE #FilterableSpecs + ( + [SpecificationAttributeOptionId] int NOT NULL + ) + DECLARE @sql_filterableSpecs nvarchar(max) + SET @sql_filterableSpecs = ' + INSERT INTO #FilterableSpecs ([SpecificationAttributeOptionId]) + SELECT DISTINCT [psam].SpecificationAttributeOptionId + FROM [Product_SpecificationAttribute_Mapping] [psam] WITH (NOLOCK) + WHERE [psam].[AllowFiltering] = 1 + AND [psam].[ProductId] IN (' + @sql + ')' + + EXEC sp_executesql @sql_filterableSpecs + + --build comma separated list of filterable identifiers + SELECT @FilterableSpecificationAttributeOptionIds = COALESCE(@FilterableSpecificationAttributeOptionIds + ',' , '') + CAST(SpecificationAttributeOptionId as nvarchar(4000)) + FROM #FilterableSpecs + + DROP TABLE #FilterableSpecs + END + + --filter by specification attribution options + SET @FilteredSpecs = isnull(@FilteredSpecs, '') + CREATE TABLE #FilteredSpecs + ( + SpecificationAttributeOptionId int not null + ) + INSERT INTO #FilteredSpecs (SpecificationAttributeOptionId) + SELECT CAST(data as int) FROM [nop_splitstring_to_table](@FilteredSpecs, ',') + + CREATE TABLE #FilteredSpecsWithAttributes + ( + SpecificationAttributeId int not null, + SpecificationAttributeOptionId int not null + ) + INSERT INTO #FilteredSpecsWithAttributes (SpecificationAttributeId, SpecificationAttributeOptionId) + SELECT sao.SpecificationAttributeId, fs.SpecificationAttributeOptionId + FROM #FilteredSpecs fs INNER JOIN SpecificationAttributeOption sao ON sao.Id = fs.SpecificationAttributeOptionId + ORDER BY sao.SpecificationAttributeId + + DECLARE @SpecAttributesCount int + SET @SpecAttributesCount = (SELECT COUNT(1) FROM #FilteredSpecsWithAttributes) + IF @SpecAttributesCount > 0 + BEGIN + --do it for each specified specification option + DECLARE @SpecificationAttributeOptionId int + DECLARE @SpecificationAttributeId int + DECLARE @LastSpecificationAttributeId int + SET @LastSpecificationAttributeId = 0 + DECLARE cur_SpecificationAttributeOption CURSOR FOR + SELECT SpecificationAttributeId, SpecificationAttributeOptionId + FROM #FilteredSpecsWithAttributes + + OPEN cur_SpecificationAttributeOption + FOREACH: + FETCH NEXT FROM cur_SpecificationAttributeOption INTO @SpecificationAttributeId, @SpecificationAttributeOptionId + IF (@LastSpecificationAttributeId <> 0 AND @SpecificationAttributeId <> @LastSpecificationAttributeId OR @@FETCH_STATUS <> 0) + SET @sql = @sql + ' + AND p.Id in (select psam.ProductId from [Product_SpecificationAttribute_Mapping] psam with (NOLOCK) where psam.AllowFiltering = 1 and psam.SpecificationAttributeOptionId IN (SELECT SpecificationAttributeOptionId FROM #FilteredSpecsWithAttributes WHERE SpecificationAttributeId = ' + CAST(@LastSpecificationAttributeId AS nvarchar(max)) + '))' + SET @LastSpecificationAttributeId = @SpecificationAttributeId + IF @@FETCH_STATUS = 0 GOTO FOREACH + CLOSE cur_SpecificationAttributeOption + DEALLOCATE cur_SpecificationAttributeOption + END + + --sorting + SET @sql_orderby = '' + IF @OrderBy = 5 /* Name: A to Z */ + SET @sql_orderby = ' p.[Name] ASC' + ELSE IF @OrderBy = 6 /* Name: Z to A */ + SET @sql_orderby = ' p.[Name] DESC' + ELSE IF @OrderBy = 10 /* Price: Low to High */ + SET @sql_orderby = ' p.[Price] ASC' + ELSE IF @OrderBy = 11 /* Price: High to Low */ + SET @sql_orderby = ' p.[Price] DESC' + ELSE IF @OrderBy = 15 /* creation date */ + SET @sql_orderby = ' p.[CreatedOnUtc] DESC' + ELSE /* default sorting, 0 (position) */ + BEGIN + --category position (display order) + IF @CategoryIdsCount > 0 SET @sql_orderby = ' pcm.DisplayOrder ASC' + + --manufacturer position (display order) + IF @ManufacturerId > 0 + BEGIN + IF LEN(@sql_orderby) > 0 SET @sql_orderby = @sql_orderby + ', ' + SET @sql_orderby = @sql_orderby + ' pmm.DisplayOrder ASC' + END + + --name + IF LEN(@sql_orderby) > 0 SET @sql_orderby = @sql_orderby + ', ' + SET @sql_orderby = @sql_orderby + ' p.[Name] ASC' + END + + SET @sql = @sql + ' + ORDER BY' + @sql_orderby + + SET @sql = ' + INSERT INTO #DisplayOrderTmp ([ProductId])' + @sql + + --PRINT (@sql) + EXEC sp_executesql @sql + + DROP TABLE #FilteredCategoryIds + DROP TABLE #FilteredSpecs + DROP TABLE #FilteredSpecsWithAttributes + DROP TABLE #FilteredCustomerRoleIds + DROP TABLE #KeywordProducts + + CREATE TABLE #PageIndex + ( + [IndexId] int IDENTITY (1, 1) NOT NULL, + [ProductId] int NOT NULL + ) + INSERT INTO #PageIndex ([ProductId]) + SELECT ProductId + FROM #DisplayOrderTmp + GROUP BY ProductId + ORDER BY min([Id]) + + --total records + SET @TotalRecords = @@rowcount + + DROP TABLE #DisplayOrderTmp + + --return products + SELECT TOP (@RowsToReturn) + p.* + FROM + #PageIndex [pi] + INNER JOIN Product p with (NOLOCK) on p.Id = [pi].[ProductId] + WHERE + [pi].IndexId > @PageLowerBound AND + [pi].IndexId < @PageUpperBound + ORDER BY + [pi].IndexId + + DROP TABLE #PageIndex +END +GO + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'ordersettings.deactivategiftcardsafterdeletingorder') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'ordersettings.deactivategiftcardsafterdeletingorder', N'False', 0) +END +GO + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'ordersettings.completeorderwhendelivered') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'ordersettings.completeorderwhendelivered', N'True', 0) +END +GO + +--new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[NewsComment]') and NAME='IsApproved') +BEGIN + ALTER TABLE [NewsComment] + ADD [IsApproved] bit NULL +END +GO + +UPDATE [NewsComment] +SET [IsApproved] = 1 +WHERE [IsApproved] IS NULL +GO + +ALTER TABLE [NewsComment] ALTER COLUMN [IsApproved] bit NOT NULL +GO + +--new activity type +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditNewsComment') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'EditNewsComment', N'Edited a news comment', N'true') +END +GO + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'newssettings.newscommentsmustbeapproved') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'newssettings.newscommentsmustbeapproved', N'False', 0) +END +GO + +--new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[BlogComment]') and NAME='IsApproved') +BEGIN + ALTER TABLE [BlogComment] + ADD [IsApproved] bit NULL +END +GO + +UPDATE [BlogComment] +SET [IsApproved] = 1 +WHERE [IsApproved] IS NULL +GO + +ALTER TABLE [BlogComment] ALTER COLUMN [IsApproved] bit NOT NULL +GO + +--new activity type +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditBlogComment') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'EditBlogComment', N'Edited a blog comment', N'true') +END +GO + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'blogsettings.blogcommentsmustbeapproved') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'blogsettings.blogcommentsmustbeapproved', N'False', 0) +END +GO + +--drop column +IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[News]') and NAME='CommentCount') +BEGIN + ALTER TABLE [News] DROP COLUMN [CommentCount] +END +GO + +--drop column +IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[BlogPost]') and NAME='CommentCount') +BEGIN + ALTER TABLE [BlogPost] DROP COLUMN [CommentCount] +END +GO + +-- new message template + IF NOT EXISTS (SELECT 1 FROM [dbo].[MessageTemplate] WHERE [Name] = N'NewReturnRequest.CustomerNotification') + BEGIN + DECLARE @NewLine AS CHAR(2) = CHAR(13) + CHAR(10) + INSERT [dbo].[MessageTemplate] ([Name], [BccEmailAddresses], [Subject], [Body], [IsActive], [AttachedDownloadId], [EmailAccountId], [LimitedToStores], [DelayPeriodId]) + VALUES (N'NewReturnRequest.CustomerNotification', NULL, N'%Store.Name%. New return request.', N'

    ' + @NewLine + '%Store.Name%' + @NewLine + '
    ' + @NewLine + '
    ' + @NewLine + 'Hello %Customer.FullName%!' + @NewLine + '
    ' + @NewLine + 'You have just submitted a new return request. Details are below:' + @NewLine + '
    ' + @NewLine + 'Request ID: %ReturnRequest.CustomNumber%' + @NewLine + '
    ' + @NewLine + 'Product: %ReturnRequest.Product.Quantity% x Product: %ReturnRequest.Product.Name%' + @NewLine + '
    ' + @NewLine + 'Reason for return: %ReturnRequest.Reason%' + @NewLine + '
    ' + @NewLine + 'Requested action: %ReturnRequest.RequestedAction%' + @NewLine + '
    ' + @NewLine + 'Customer comments:' + @NewLine + '
    ' + @NewLine + '%ReturnRequest.CustomerComment%' + @NewLine + '

    ' + @NewLine, 1, 0, 0, 0, 0) + END + GO + + --new table +IF NOT EXISTS (SELECT 1 FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[StockQuantityHistory]') and OBJECTPROPERTY(object_id, N'IsUserTable') = 1) +BEGIN + CREATE TABLE [dbo].[StockQuantityHistory] + ( + [Id] int IDENTITY(1,1) NOT NULL, + [ProductId] int NOT NULL, + [CombinationId] int NULL, + [WarehouseId] int NULL, + [QuantityAdjustment] int NOT NULL, + [StockQuantity] int NOT NULL, + [Message] NVARCHAR (MAX) NULL, + [CreatedOnUtc] datetime NOT NULL + PRIMARY KEY CLUSTERED + ( + [Id] ASC + ) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) + ) +END +GO + +IF EXISTS (SELECT 1 FROM sys.objects WHERE name = 'StockQuantityHistory_Product' AND parent_object_id = Object_id('StockQuantityHistory') AND Objectproperty(object_id, N'IsForeignKey') = 1) +BEGIN + ALTER TABLE [dbo].StockQuantityHistory + DROP CONSTRAINT StockQuantityHistory_Product +END +GO + +ALTER TABLE [dbo].[StockQuantityHistory] WITH CHECK ADD CONSTRAINT [StockQuantityHistory_Product] FOREIGN KEY([ProductId]) +REFERENCES [dbo].[Product] ([Id]) +ON DELETE CASCADE +GO + +--initial stock quantity history +DECLARE cur_initialhistory CURSOR FOR +SELECT [Product].Id, NULL, [Product].WarehouseId, [Product].StockQuantity, NULL +FROM [Product] +UNION ALL +SELECT [ProductAttributeCombination].ProductId, [ProductAttributeCombination].Id, NULL, [ProductAttributeCombination].StockQuantity, NULL +FROM [ProductAttributeCombination] +UNION ALL +SELECT [ProductWarehouseInventory].ProductId, NULL, [ProductWarehouseInventory].WarehouseId, [ProductWarehouseInventory].StockQuantity, [ProductWarehouseInventory].Id +FROM [ProductWarehouseInventory] + +DECLARE @productId int +DECLARE @combinationId int +DECLARE @warehouseId int +DECLARE @quantity int +DECLARE @warehouseInventoryId int + +OPEN cur_initialhistory +FETCH NEXT FROM cur_initialhistory INTO @productId, @combinationId, @warehouseId, @quantity, @warehouseInventoryId + +WHILE @@FETCH_STATUS = 0 +BEGIN + IF @warehouseId = 0 + BEGIN + SET @warehouseId = NULL; + END + + DECLARE @message nvarchar(200) + SET @message = 'Initialization of history table (original quantity set) during upgrade from a previous version' + IF @warehouseInventoryId IS NOT NULL + BEGIN + SET @message = 'Multiple warehouses. ' + @message; + END + + IF (@quantity IS NOT NULL AND @quantity <> 0 AND + NOT EXISTS (SELECT 1 FROM [StockQuantityHistory] WHERE ProductId = @productId AND + (CombinationId = @combinationId OR (CombinationId IS NULL AND @combinationId IS NULL)) AND (WarehouseId = @warehouseId OR (WarehouseId IS NULL AND @warehouseId IS NULL)))) + BEGIN + INSERT INTO [StockQuantityHistory] + ([ProductId], [CombinationId], [WarehouseId], [QuantityAdjustment], [StockQuantity], [Message], [CreatedOnUtc]) + VALUES + (@productId, @combinationId, @warehouseId, @quantity, @quantity, @message, GETUTCDATE()) + END + + FETCH NEXT FROM cur_initialhistory INTO @productId, @combinationId, @warehouseId, @quantity, @warehouseInventoryId +END + +CLOSE cur_initialhistory +DEALLOCATE cur_initialhistory +GO + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'producteditorsettings.stockquantityhistory') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'producteditorsettings.stockquantityhistory', N'False', 0) +END +GO + + +--new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Customer]') and NAME='RequireReLogin') +BEGIN + ALTER TABLE [Customer] + ADD [RequireReLogin] bit NULL +END +GO + +UPDATE [Customer] +SET [RequireReLogin] = 0 +WHERE [RequireReLogin] IS NULL +GO + +ALTER TABLE [Customer] ALTER COLUMN [RequireReLogin] bit NOT NULL +GO + + +--delete setting +DELETE FROM [Setting] +WHERE [name] = N'rewardpointssettings.PointsForPurchases_Awarded' +GO + +--delete setting +DELETE FROM [Setting] +WHERE [name] = N'rewardpointssettings.PointsForPurchases_Canceled' +GO + +--delete setting +DELETE FROM [Setting] +WHERE [name] = N'ordersettings.GiftCards_Activated_OrderStatusId' +GO + +--delete setting +DELETE FROM [Setting] +WHERE [name] = N'ordersettings.GiftCards_Deactivated_OrderStatusId' +GO + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'ordersettings.activategiftcardsaftercompletingorder') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'ordersettings.activategiftcardsaftercompletingorder', N'False', 0) +END +GO + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'ordersettings.deactivategiftcardsaftercancellingorder') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'ordersettings.deactivategiftcardsaftercancellingorder', N'False', 0) +END +GO + + +--update a stored procedure +IF EXISTS ( + SELECT * + FROM sys.objects + WHERE object_id = OBJECT_ID(N'[LanguagePackImport]') AND OBJECTPROPERTY(object_id,N'IsProcedure') = 1) +DROP PROCEDURE [LanguagePackImport] +GO +CREATE PROCEDURE [dbo].[LanguagePackImport] +( + @LanguageId int, + @XmlPackage xml, + @UpdateExistingResources bit +) +AS +BEGIN + IF EXISTS(SELECT * FROM [Language] WHERE [Id] = @LanguageId) + BEGIN + CREATE TABLE #LocaleStringResourceTmp + ( + [LanguageId] [int] NOT NULL, + [ResourceName] [nvarchar](200) NOT NULL, + [ResourceValue] [nvarchar](MAX) NOT NULL + ) + + INSERT INTO #LocaleStringResourceTmp (LanguageId, ResourceName, ResourceValue) + SELECT @LanguageId, nref.value('@Name', 'nvarchar(200)'), nref.value('Value[1]', 'nvarchar(MAX)') + FROM @XmlPackage.nodes('//Language/LocaleResource') AS R(nref) + + DECLARE @ResourceName nvarchar(200) + DECLARE @ResourceValue nvarchar(MAX) + DECLARE cur_localeresource CURSOR FOR + SELECT LanguageId, ResourceName, ResourceValue + FROM #LocaleStringResourceTmp + OPEN cur_localeresource + FETCH NEXT FROM cur_localeresource INTO @LanguageId, @ResourceName, @ResourceValue + WHILE @@FETCH_STATUS = 0 + BEGIN + IF (EXISTS (SELECT 1 FROM [LocaleStringResource] WHERE LanguageId=@LanguageId AND ResourceName=@ResourceName)) + BEGIN + IF (@UpdateExistingResources = 1) + BEGIN + UPDATE [LocaleStringResource] + SET [ResourceValue]=@ResourceValue + WHERE LanguageId=@LanguageId AND ResourceName=@ResourceName + END + END + ELSE + BEGIN + INSERT INTO [LocaleStringResource] + ( + [LanguageId], + [ResourceName], + [ResourceValue] + ) + VALUES + ( + @LanguageId, + @ResourceName, + @ResourceValue + ) + END + + + FETCH NEXT FROM cur_localeresource INTO @LanguageId, @ResourceName, @ResourceValue + END + CLOSE cur_localeresource + DEALLOCATE cur_localeresource + + DROP TABLE #LocaleStringResourceTmp + END +END +GO + + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'paymentsettings.skippaymentInfostepforredirectionpaymentmethods') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'paymentsettings.skippaymentInfostepforredirectionpaymentmethods', N'False', 0) +END +GO + + +--updated some indexes (required for case sensitive SQL Server collations) +IF EXISTS (SELECT 1 from sys.indexes WHERE [NAME]=N'IX_NewsletterSubscription_Email_StoreId' and object_id=object_id(N'[dbo].[NewsLetterSubscription]')) +BEGIN + DROP INDEX [IX_NewsletterSubscription_Email_StoreId] ON [NewsLetterSubscription] +END +GO +CREATE NONCLUSTERED INDEX [IX_NewsletterSubscription_Email_StoreId] ON [NewsLetterSubscription] ([Email] ASC, [StoreId] ASC) +GO + +IF EXISTS (SELECT 1 from sys.indexes WHERE [NAME]=N'IX_Product_ShowOnHomepage' and object_id=object_id(N'[dbo].[Product]')) +BEGIN + DROP INDEX [IX_Product_ShowOnHomepage] ON [Product] +END +GO +CREATE NONCLUSTERED INDEX [IX_Product_ShowOnHomepage] ON [Product] ([ShowOnHomePage] ASC) +GO + +--update a stored procedure +IF EXISTS ( + SELECT * + FROM sys.objects + WHERE object_id = OBJECT_ID(N'[DeleteGuests]') AND OBJECTPROPERTY(object_id,N'IsProcedure') = 1) +DROP PROCEDURE [DeleteGuests] +GO +CREATE PROCEDURE [dbo].[DeleteGuests] +( + @OnlyWithoutShoppingCart bit = 1, + @CreatedFromUtc datetime, + @CreatedToUtc datetime, + @TotalRecordsDeleted int = null OUTPUT +) +AS +BEGIN + CREATE TABLE #tmp_guests (CustomerId int) + + INSERT #tmp_guests (CustomerId) + SELECT [Id] FROM [Customer] c with (NOLOCK) + WHERE + --created from + ((@CreatedFromUtc is null) OR (c.[CreatedOnUtc] > @CreatedFromUtc)) + AND + --created to + ((@CreatedToUtc is null) OR (c.[CreatedOnUtc] < @CreatedToUtc)) + AND + --shopping cart items + ((@OnlyWithoutShoppingCart=0) OR (NOT EXISTS(SELECT 1 FROM [ShoppingCartItem] sci with (NOLOCK) inner join [Customer] with (NOLOCK) on sci.[CustomerId]=c.[Id]))) + AND + --guests only + (EXISTS(SELECT 1 FROM [Customer_CustomerRole_Mapping] ccrm with (NOLOCK) inner join [Customer] with (NOLOCK) on ccrm.[Customer_Id]=c.[Id] inner join [CustomerRole] cr with (NOLOCK) on cr.[Id]=ccrm.[CustomerRole_Id] WHERE cr.[SystemName] = N'Guests')) + AND + --no orders + (NOT EXISTS(SELECT 1 FROM [Order] o with (NOLOCK) inner join [Customer] with (NOLOCK) on o.[CustomerId]=c.[Id])) + AND + --no blog comments + (NOT EXISTS(SELECT 1 FROM [BlogComment] bc with (NOLOCK) inner join [Customer] with (NOLOCK) on bc.[CustomerId]=c.[Id])) + AND + --no news comments + (NOT EXISTS(SELECT 1 FROM [NewsComment] nc with (NOLOCK)inner join [Customer] with (NOLOCK) on nc.[CustomerId]=c.[Id])) + AND + --no product reviews + (NOT EXISTS(SELECT 1 FROM [ProductReview] pr with (NOLOCK) inner join [Customer] with (NOLOCK) on pr.[CustomerId]=c.[Id])) + AND + --no product reviews helpfulness + (NOT EXISTS(SELECT 1 FROM [ProductReviewHelpfulness] prh with (NOLOCK) inner join [Customer] with (NOLOCK) on prh.[CustomerId]=c.[Id])) + AND + --no poll voting + (NOT EXISTS(SELECT 1 FROM [PollVotingRecord] pvr with (NOLOCK) inner join [Customer] with (NOLOCK) on pvr.[CustomerId]=c.[Id])) + AND + --no forum topics + (NOT EXISTS(SELECT 1 FROM [Forums_Topic] ft with (NOLOCK) inner join [Customer] with (NOLOCK) on ft.[CustomerId]=c.[Id])) + AND + --no forum posts + (NOT EXISTS(SELECT 1 FROM [Forums_Post] fp with (NOLOCK) inner join [Customer] with (NOLOCK) on fp.[CustomerId]=c.[Id])) + AND + --no system accounts + (c.IsSystemAccount = 0) + + --delete guests + DELETE [Customer] + WHERE [Id] IN (SELECT [CustomerId] FROM #tmp_guests) + + --delete attributes + DELETE [GenericAttribute] + WHERE ([EntityId] IN (SELECT [CustomerId] FROM #tmp_guests)) + AND + ([KeyGroup] = N'Customer') + + --total records + SELECT @TotalRecordsDeleted = COUNT(1) FROM #tmp_guests + + DROP TABLE #tmp_guests +END +GO + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'commonsettings.sitemapcustomurls') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'commonsettings.sitemapcustomurls', N'', 0) +END +GO + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'shoppingcartsettings.cartssharedbetweenstores') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'shoppingcartsettings.cartssharedbetweenstores', N'False', 0) +END +GO + +--new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Customer]') and NAME='EmailToRevalidate') +BEGIN + ALTER TABLE [Customer] + ADD [EmailToRevalidate] nvarchar(1000) NULL +END +GO + +-- new message template + IF NOT EXISTS (SELECT 1 FROM [dbo].[MessageTemplate] WHERE [Name] = N'Customer.EmailRevalidationMessage') + BEGIN + DECLARE @NewLine AS CHAR(2) = CHAR(13) + CHAR(10) + INSERT [dbo].[MessageTemplate] ([Name], [BccEmailAddresses], [Subject], [Body], [IsActive], [AttachedDownloadId], [EmailAccountId], [LimitedToStores], [DelayPeriodId]) + VALUES (N'Customer.EmailRevalidationMessage', NULL, N'%Store.Name%. Email validation.', N'

    ' + @NewLine + '%Store.Name%' + @NewLine + '
    ' + @NewLine + '
    ' + @NewLine + 'Hello %Customer.FullName%!' + @NewLine + '
    ' + @NewLine + 'To validate your new email address click here .' + @NewLine + '
    ' + @NewLine + '
    ' + @NewLine + '%Store.Name%' + @NewLine + '

    ' + @NewLine, 1, 0, 0, 0, 0) + END + GO + +--new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='RewardPointsHistoryEntryId') +BEGIN + ALTER TABLE [Order] + ADD [RewardPointsHistoryEntryId] int NULL +END +GO + +IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='RewardPointsWereAdded') +BEGIN + --column RewardPointsWereAdded was replaced with RewardPointsHistoryEntryId + --ensure that the new column value is not null to specify that reward points were added (earned) to an order + EXEC(' + UPDATE [Order] + SET [RewardPointsHistoryEntryId] = 0 + WHERE [RewardPointsWereAdded] = 1') +END +GO + +--drop column +IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='RewardPointsWereAdded') +BEGIN + ALTER TABLE [Order] DROP COLUMN [RewardPointsWereAdded] +END +GO + + +--update plugin locales (renamed) +UPDATE [LocaleStringResource] +SET [ResourceName] = REPLACE([ResourceName], 'Plugins.Feed.Froogle.','Plugins.Feed.GoogleShopping.') +WHERE [ResourceName] like 'Plugins.Feed.Froogle.%' +GO + +--update settings +UPDATE [Setting] +SET [Name] = REPLACE([Name], 'frooglesettings.','googlesShoppingsettings.') +WHERE [Name] like 'frooglesettings.%' +GO + + +--new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[ProductTemplate]') and NAME='IgnoredProductTypes') +BEGIN + ALTER TABLE [ProductTemplate] + ADD [IgnoredProductTypes] nvarchar(MAX) NULL +END +GO + +UPDATE [ProductTemplate] +SET [IgnoredProductTypes] = '10' +WHERE [ViewPath] = N'ProductTemplate.Simple' +GO + +UPDATE [ProductTemplate] +SET [IgnoredProductTypes] = '5' +WHERE [ViewPath] = N'ProductTemplate.Grouped' +GO + + +--new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[ReturnRequest]') and NAME='UploadedFileId') +BEGIN + ALTER TABLE [ReturnRequest] + ADD [UploadedFileId] int NULL +END +GO + +UPDATE [ReturnRequest] +SET [UploadedFileId] = 0 +WHERE [UploadedFileId] IS NULL +GO + +ALTER TABLE [ReturnRequest] ALTER COLUMN [UploadedFileId] int NOT NULL +GO + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'ordersettings.returnrequestsallowfiles') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'ordersettings.returnrequestsallowfiles', N'False', 0) +END +GO + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'ordersettings.returnrequestsfilemaximumsize') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'ordersettings.returnrequestsfilemaximumsize', N'2048', 0) +END +GO + +--new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[ProductReview]') and NAME='ReplyText') +BEGIN + ALTER TABLE [ProductReview] + ADD [ReplyText] nvarchar(MAX) NULL +END +GO + +--new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[BlogComment]') and NAME='StoreId') +BEGIN + ALTER TABLE [dbo].[BlogComment] + ADD [StoreId] int NULL +END +GO + +DECLARE @DefaultStoreId int +SET @DefaultStoreId = (SELECT TOP (1) Id FROM [dbo].[Store]); +--set default value to store column +UPDATE [dbo].[BlogComment] +SET StoreId = @DefaultStoreId +WHERE StoreId IS NULL +GO + +ALTER TABLE [dbo].[BlogComment] ALTER COLUMN [StoreId] int NOT NULL +GO + +IF EXISTS (SELECT 1 FROM sys.objects WHERE name = 'BlogComment_Store' AND parent_object_id = Object_id('BlogComment') AND Objectproperty(object_id, N'IsForeignKey') = 1) +ALTER TABLE [dbo].[BlogComment] +DROP CONSTRAINT BlogComment_Store +GO + +ALTER TABLE [dbo].[BlogComment] WITH CHECK ADD CONSTRAINT [BlogComment_Store] FOREIGN KEY([StoreId]) +REFERENCES [dbo].[Store] ([Id]) +ON DELETE CASCADE +GO + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'blogsettings.showblogcommentsperstore') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'blogsettings.showblogcommentsperstore', N'False', 0) +END +GO + +--new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[NewsComment]') and NAME='StoreId') +BEGIN + ALTER TABLE [dbo].[NewsComment] + ADD [StoreId] int NULL +END +GO + +DECLARE @DefaultStoreId int +SET @DefaultStoreId = (SELECT TOP (1) Id FROM [dbo].[Store]); +--set default value to store column +UPDATE [dbo].[NewsComment] +SET StoreId = @DefaultStoreId +WHERE StoreId IS NULL +GO + +ALTER TABLE [dbo].[NewsComment] ALTER COLUMN [StoreId] int NOT NULL +GO + +IF EXISTS (SELECT 1 FROM sys.objects WHERE name = 'NewsComment_Store' AND parent_object_id = Object_id('NewsComment') AND Objectproperty(object_id, N'IsForeignKey') = 1) +ALTER TABLE [dbo].[NewsComment] +DROP CONSTRAINT NewsComment_Store +GO + +ALTER TABLE [dbo].[NewsComment] WITH CHECK ADD CONSTRAINT [NewsComment_Store] FOREIGN KEY([StoreId]) +REFERENCES [dbo].[Store] ([Id]) +ON DELETE CASCADE +GO + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'newssettings.shownewscommentsperstore') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'newssettings.shownewscommentsperstore', N'False', 0) +END +GO + + +--new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Vendor]') and NAME='AddressId') +BEGIN + ALTER TABLE [Vendor] + ADD [AddressId] int NULL +END +GO + +UPDATE [Vendor] +SET [AddressId] = 0 +WHERE [AddressId] IS NULL +GO + +ALTER TABLE [Vendor] ALTER COLUMN [AddressId] int NOT NULL +GO + + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'customersettings.failedpasswordallowedattempts') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'customersettings.failedpasswordallowedattempts', N'0', 0) +END +GO + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'customersettings.failedpasswordlockoutminutes') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'customersettings.failedpasswordlockoutminutes', N'30', 0) +END +GO + + +--new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Customer]') and NAME='FailedLoginAttempts') +BEGIN + ALTER TABLE [Customer] + ADD [FailedLoginAttempts] int NULL +END +GO + +UPDATE [Customer] +SET [FailedLoginAttempts] = 0 +WHERE [FailedLoginAttempts] IS NULL +GO + +ALTER TABLE [Customer] ALTER COLUMN [FailedLoginAttempts] int NOT NULL +GO + +--new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Customer]') and NAME='CannotLoginUntilDateUtc') +BEGIN + ALTER TABLE [Customer] + ADD [CannotLoginUntilDateUtc] datetime NULL +END +GO + + --new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'fixedorbycountrystateziptaxsettings.countrystatezipenabled') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'fixedorbycountrystateziptaxsettings.countrystatezipenabled', N'False', 0) +END +GO + +--rename settings +UPDATE [Setting] +SET [Name] = N'tax.taxprovider.fixedorbycountrystatezip.taxcategoryid' + SUBSTRING(name, 40, len(name)) +WHERE [Name] like N'tax.taxprovider.fixedrate.taxcategoryid%' +GO + +--new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Customer]') and NAME='RegisteredInStoreId') +BEGIN + ALTER TABLE [dbo].[Customer] + ADD [RegisteredInStoreId] int NULL +END +GO + +declare @DefaultStoreId int; +if ((select count(id) from [dbo].[Store]) = 1) +set @DefaultStoreId = (select top(1) id from [dbo].[Store]) +else +set @DefaultStoreId = 0; +--set default value to store column +UPDATE [dbo].[Customer] set [RegisteredInStoreId] = @DefaultStoreId where [RegisteredInStoreId] is NULL + +ALTER TABLE [dbo].[Customer] ALTER COLUMN [RegisteredInStoreId] int NOT NULL +GO + + --new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'shippingsettings.considerassociatedproductsdimensions') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'shippingsettings.considerassociatedproductsdimensions', N'True', 0) +END +GO + + --new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'commonsettings.bbcodeeditoropenlinksinnewwindow') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'commonsettings.bbcodeeditoropenlinksinnewwindow', N'false', 0) +END +GO + + --new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'paymentsettings.cancelrecurringpaymentsafterfailedpayment') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'paymentsettings.cancelrecurringpaymentsafterfailedpayment', N'False', 0) +END +GO + +--new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[RecurringPayment]') and NAME='LastPaymentFailed') +BEGIN + ALTER TABLE [RecurringPayment] + ADD [LastPaymentFailed] bit NULL +END +GO + +UPDATE [RecurringPayment] +SET [LastPaymentFailed] = 0 +WHERE [LastPaymentFailed] IS NULL +GO + +ALTER TABLE [RecurringPayment] ALTER COLUMN [LastPaymentFailed] bit NOT NULL +GO + +-- new message template +IF NOT EXISTS (SELECT 1 FROM [dbo].[MessageTemplate] WHERE [Name] = N'RecurringPaymentCancelled.CustomerNotification') +BEGIN + DECLARE @NewLine AS CHAR(2) = CHAR(13) + CHAR(10) + INSERT [dbo].[MessageTemplate] ([Name], [BccEmailAddresses], [Subject], [Body], [IsActive], [AttachedDownloadId], [EmailAccountId], [LimitedToStores], [DelayPeriodId]) + VALUES (N'RecurringPaymentCancelled.CustomerNotification', NULL, N'%Store.Name%. Recurring payment cancelled', N'

    ' + @NewLine + '%Store.Name%' + @NewLine + '
    ' + @NewLine + '
    ' + @NewLine + 'Hello %Customer.FullName%,' + @NewLine + '
    ' + @NewLine + '%if (%RecurringPayment.CancelAfterFailedPayment%) It appears your credit card didn''t go through for this recurring payment (%Order.OrderURLForCustomer%)' + @NewLine + '
    ' + @NewLine + 'So your subscription has been canceled. endif% %if (!%RecurringPayment.CancelAfterFailedPayment%) The recurring payment ID=%RecurringPayment.ID% was cancelled. endif%' + @NewLine + '

    ' + @NewLine, 1, 0, 0, 0, 0) +END +GO + +-- new message template +IF NOT EXISTS (SELECT 1 FROM [dbo].[MessageTemplate] WHERE [Name] = N'RecurringPaymentFailed.CustomerNotification') +BEGIN + DECLARE @NewLine AS CHAR(2) = CHAR(13) + CHAR(10) + INSERT [dbo].[MessageTemplate] ([Name], [BccEmailAddresses], [Subject], [Body], [IsActive], [AttachedDownloadId], [EmailAccountId], [LimitedToStores], [DelayPeriodId]) + VALUES (N'RecurringPaymentFailed.CustomerNotification', NULL, N'%Store.Name%. Last recurring payment failed', N'

    ' + @NewLine + '%Store.Name%' + @NewLine + '
    ' + @NewLine + '
    ' + @NewLine + 'Hello %Customer.FullName%,' + @NewLine + '
    ' + @NewLine + 'It appears your credit card didn''t go through for this recurring payment (%Order.OrderURLForCustomer%)' + @NewLine + '
    %if (%RecurringPayment.RecurringPaymentType% == "Manual") ' + @NewLine + 'You can recharge balance and manually retry payment or cancel it on the order history page. endif% %if (%RecurringPayment.RecurringPaymentType% == "Automatic") ' + @NewLine + 'You can recharge balance and wait, we will try to make the payment again, or you can cancel it on the order history page. endif%' + @NewLine + '

    ' + @NewLine, 1, 0, 0, 0, 0) +END +GO + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'shoppingcartsettings.renderproductattributeprices') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'shoppingcartsettings.renderproductattributeprices', N'True', 0) +END +GO + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'rewardpointssettings.earnedrewardpointsaretaxable') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'rewardpointssettings.earnedrewardpointsaretaxable', N'False', 0) +END +GO +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = lower(N'rewardpointssettings.AwardPointsIncludeShipping')) +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (lower(N'rewardpointssettings.AwardPointsIncludeShipping'), N'True', 0) +END +GO +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = lower(N'rewardpointssettings.AwardPointsIncludePaymentMethodAdditionalFee')) +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (lower(N'rewardpointssettings.AwardPointsIncludePaymentMethodAdditionalFee'), N'True', 0) +END +GO +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = lower(N'rewardpointssettings.AwardPointsExcludeGiftCard')) +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (lower(N'rewardpointssettings.AwardPointsExcludeGiftCard'), N'True', 0) +END +GO +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = lower(N'rewardpointssettings.AwardPointsExcludePurchasedRewardPoints')) +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (lower(N'rewardpointssettings.AwardPointsExcludePurchasedRewardPoints'), N'True', 0) +END +GO +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = lower(N'rewardpointssettings.EarnRewardPointsOnlyWhenUsingPurchasedRewardPoints')) +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (lower(N'rewardpointssettings.EarnRewardPointsOnlyWhenUsingPurchasedRewardPoints'), N'False', 0) +END +GO +--add a new columns +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[RewardPointsHistory]') and NAME='PointsPurchased') +BEGIN + ALTER TABLE [RewardPointsHistory] + ADD PointsPurchased [int] NULL, PointsBalancePurchased [int] NULL, [PurchasedWithOrderItemId] [int] NULL, [UsedAmountPurchased] decimal(18,4) NULL +END +GO + +--create fk +IF NOT EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[RewardPoints_PurchasedWithOrderItem]') AND parent_object_id = OBJECT_ID(N'[dbo].[RewardPointsHistory]')) +ALTER TABLE [dbo].[RewardPointsHistory] WITH CHECK ADD CONSTRAINT [RewardPoints_PurchasedWithOrderItem] FOREIGN KEY([PurchasedWithOrderItemId]) +REFERENCES [dbo].[OrderItem] ([Id]) +GO + +--enable it +IF EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[RewardPoints_PurchasedWithOrderItem]') AND parent_object_id = OBJECT_ID(N'[dbo].[RewardPointsHistory]')) +ALTER TABLE [dbo].[RewardPointsHistory] CHECK CONSTRAINT [RewardPoints_PurchasedWithOrderItem] +GO + +--init new col. +UPDATE [RewardPointsHistory] +SET [PointsPurchased] = 0, [UsedAmountPurchased] = 0, [PointsBalancePurchased] = 0 +WHERE [PointsPurchased] IS NULL +GO + +ALTER TABLE [RewardPointsHistory] ALTER COLUMN [PointsPurchased] [int] NOT NULL +GO +ALTER TABLE [RewardPointsHistory] ALTER COLUMN [UsedAmountPurchased] decimal(18,4) NOT NULL +GO + +IF NOT EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'[dbo].[RewardPointsHistory]') AND name = N'IX_RewardPointsHistory_Customer') +CREATE NONCLUSTERED INDEX [IX_RewardPointsHistory_Customer] ON [dbo].[RewardPointsHistory] +( + [CustomerId] ASC +) +GO + +--add a new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Product]') and NAME='IsRewardPoints') +BEGIN + ALTER TABLE [Product] + ADD [IsRewardPoints] [bit] NULL, + OverriddenRPExchangeRate decimal(18,4) NULL +END +GO + +--init new col. +UPDATE [Product] +SET [IsRewardPoints] = 0 +WHERE [IsRewardPoints] IS NULL +GO + +ALTER TABLE [Product] ALTER COLUMN [IsRewardPoints] [bit] NOT NULL +GO + +--add a new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Product]') and NAME='ExcludeFromRewardPoints') +BEGIN + ALTER TABLE [Product] + ADD ExcludeFromRewardPoints [bit] NULL +END +GO + +--init new col. +UPDATE [Product] +SET [ExcludeFromRewardPoints] = 0 +WHERE [ExcludeFromRewardPoints] IS NULL +GO + +ALTER TABLE [Product] ALTER COLUMN [ExcludeFromRewardPoints] [bit] NOT NULL +GO + +--new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='CustomOrderNumber') +BEGIN + ALTER TABLE [Order] + ADD [CustomOrderNumber] nvarchar(MAX) NULL +END +GO + +UPDATE [Order] +SET [CustomOrderNumber] = [id] +WHERE [CustomOrderNumber] IS NULL +GO + +ALTER TABLE [Order] ALTER COLUMN [CustomOrderNumber] nvarchar(MAX) NOT NULL +GO + + --new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'ordersettings.customordernumbermask') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'ordersettings.customordernumbermask', N'{ID}', 0) +END +GO + + --new table +IF NOT EXISTS (SELECT 1 FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[CustomerPassword]') and OBJECTPROPERTY(object_id, N'IsUserTable') = 1) +BEGIN + CREATE TABLE [dbo].[CustomerPassword] + ( + [Id] int IDENTITY(1,1) NOT NULL, + [CustomerId] int NOT NULL, + [Password] NVARCHAR (MAX) NULL, + [PasswordFormatId] INT NOT NULL, + [PasswordSalt] NVARCHAR (MAX) NULL, + [CreatedOnUtc] datetime NOT NULL + PRIMARY KEY CLUSTERED + ( + [Id] ASC + ) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) + ) +END +GO + +IF EXISTS (SELECT 1 FROM sys.objects WHERE name = 'CustomerPassword_Customer' AND parent_object_id = Object_id('CustomerPassword') AND Objectproperty(object_id, N'IsForeignKey') = 1) +BEGIN + ALTER TABLE [dbo].CustomerPassword + DROP CONSTRAINT CustomerPassword_Customer +END +GO + +ALTER TABLE [dbo].[CustomerPassword] WITH CHECK ADD CONSTRAINT [CustomerPassword_Customer] FOREIGN KEY([CustomerId]) +REFERENCES [dbo].[Customer] ([Id]) +ON DELETE CASCADE +GO + +--move customer passwords into a new table +IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Customer]') and (NAME='Password' or NAME='PasswordFormatId' or NAME='PasswordSalt')) +BEGIN + EXEC(' + INSERT INTO [dbo].[CustomerPassword]([CustomerId], [Password], [PasswordFormatId], [PasswordSalt], [CreatedOnUtc]) + SELECT [Id], [Password], [PasswordFormatId], [PasswordSalt], [CreatedOnUtc] + FROM [dbo].[Customer]') +END +GO + +--drop column +IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Customer]') and NAME='Password') +BEGIN + ALTER TABLE [Customer] DROP COLUMN [Password] +END +GO + +--drop column +IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Customer]') and NAME='PasswordFormatId') +BEGIN + ALTER TABLE [Customer] DROP COLUMN [PasswordFormatId] +END +GO + +--drop column +IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Customer]') and NAME='PasswordSalt') +BEGIN + ALTER TABLE [Customer] DROP COLUMN [PasswordSalt] +END +GO + + --new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'customersettings.unduplicatedpasswordsnumber') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'customersettings.unduplicatedpasswordsnumber', N'4', 0) +END +GO + + --new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'customersettings.passwordlifetime') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'customersettings.passwordlifetime', N'90', 0) +END +GO + +--new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[CustomerRole]') and NAME='EnablePasswordLifetime') +BEGIN + ALTER TABLE [CustomerRole] + ADD [EnablePasswordLifetime] bit NULL +END +GO + +UPDATE [CustomerRole] +SET [EnablePasswordLifetime] = 0 +WHERE [EnablePasswordLifetime] IS NULL +GO + +ALTER TABLE [CustomerRole] ALTER COLUMN [EnablePasswordLifetime] bit NOT NULL +GO + +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'AddCustomerRewardPoints') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'AddCustomerRewardPoints', N'Add customer reward points', N'true') +END +GO +--new activity types +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'EditCustomerRewardPoints') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'EditCustomerRewardPoints', N'Edit customer reward points', N'true') +END +GO +GO + + +-- new message template + IF NOT EXISTS (SELECT 1 FROM [dbo].[MessageTemplate] WHERE [Name] = N'Service.ContactUs') + BEGIN + DECLARE @NewLine AS CHAR(2) = CHAR(13) + CHAR(10) + INSERT [dbo].[MessageTemplate] ([Name], [BccEmailAddresses], [Subject], [Body], [IsActive], [AttachedDownloadId], [EmailAccountId], [LimitedToStores], [DelayPeriodId]) + VALUES (N'Service.ContactUs', NULL, N'%Store.Name%. Contact us', N'

    ' + @NewLine + '%ContactUs.Body%' + @NewLine + '

    ' + @NewLine, 1, 0, 0, 0, 0) + END + GO + +-- new message template + IF NOT EXISTS (SELECT 1 FROM [dbo].[MessageTemplate] WHERE [Name] = N'Service.ContactVendor') + BEGIN + DECLARE @NewLine AS CHAR(2) = CHAR(13) + CHAR(10) + INSERT [dbo].[MessageTemplate] ([Name], [BccEmailAddresses], [Subject], [Body], [IsActive], [AttachedDownloadId], [EmailAccountId], [LimitedToStores], [DelayPeriodId]) + VALUES (N'Service.ContactVendor', NULL, N'%Store.Name%. Contact us', N'

    ' + @NewLine + '%ContactUs.Body%' + @NewLine + '

    ' + @NewLine, 1, 0, 0, 0, 0) + END + GO + + --now vendors have "Manage product reviews" permission +IF EXISTS ( + SELECT 1 + FROM [dbo].[PermissionRecord] + WHERE [SystemName] = N'ManageProductReviews') +BEGIN + DECLARE @PermissionRecordId INT + SET @PermissionRecordId = (SELECT [Id] FROM [dbo].[PermissionRecord] WHERE [SystemName] = N'ManageProductReviews') + + --add it to vendor role by default + DECLARE @VendorCustomerRoleId int + SELECT @VendorCustomerRoleId = Id + FROM [CustomerRole] + WHERE IsSystemRole=1 and [SystemName] = N'Vendors' + + IF NOT EXISTS ( + SELECT 1 + FROM [dbo].[PermissionRecord_Role_Mapping] + WHERE [PermissionRecord_Id] = @PermissionRecordId AND [CustomerRole_Id] = @VendorCustomerRoleId) + BEGIN + INSERT [dbo].[PermissionRecord_Role_Mapping] ([PermissionRecord_Id], [CustomerRole_Id]) + VALUES (@PermissionRecordId, @VendorCustomerRoleId) + END +END +GO + + --new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'taxsettings.taxbasedonpickuppointaddress') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'taxsettings.taxbasedonpickuppointaddress', N'False', 0) +END +GO + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'ordersettings.exportwithproducts') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'ordersettings.exportwithproducts', N'True', 0) +END +GO + +--new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[DiscountRequirement]') and NAME='InteractionTypeId') +BEGIN + ALTER TABLE [DiscountRequirement] + ADD [InteractionTypeId] int NULL +END +GO + +--new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[DiscountRequirement]') and NAME='ParentId') +BEGIN + ALTER TABLE [DiscountRequirement] + ADD [ParentId] int NULL +END +GO + +--new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[DiscountRequirement]') and NAME='IsGroup') +BEGIN + ALTER TABLE [DiscountRequirement] + ADD [IsGroup] bit NULL +END +GO + +UPDATE [DiscountRequirement] +SET [IsGroup] = 0 +WHERE [IsGroup] IS NULL +GO + +ALTER TABLE [DiscountRequirement] ALTER COLUMN [IsGroup] bit NOT NULL +GO + +--add default requirement group for existing requirements +DECLARE cursor_defaultGroup CURSOR FOR SELECT [DiscountId] FROM [DiscountRequirement] +DECLARE @discountId int + +OPEN cursor_defaultGroup +FETCH NEXT FROM cursor_defaultGroup INTO @discountId +WHILE @@FETCH_STATUS = 0 +BEGIN + IF NOT EXISTS (SELECT 1 FROM [DiscountRequirement] WHERE [DiscountId] = @discountId AND [ParentId] IS NULL AND [IsGroup] = 1) + BEGIN + INSERT INTO [DiscountRequirement] + ([DiscountId], [DiscountRequirementRuleSystemName], [InteractionTypeId], [ParentId], [IsGroup]) + VALUES + (@discountId, 'Default requirement group', 0, NULL, 1); + + DECLARE @requirementId int = (SELECT SCOPE_IDENTITY()); + + UPDATE [DiscountRequirement] + SET [ParentId] = @requirementId, [InteractionTypeId] = NULL + WHERE [DiscountId] = @discountId AND [Id] <> @requirementId + END + FETCH NEXT FROM cursor_defaultGroup INTO @discountId +END + +CLOSE cursor_defaultGroup +DEALLOCATE cursor_defaultGroup +GO + + +--new task +IF NOT EXISTS (SELECT 1 FROM [ScheduleTask] WHERE [Name] = N'Assign invoice ident') +INSERT INTO ScheduleTask + (Name, Seconds, Type, Enabled, StopOnError) +VALUES (N'Assign invoice ident', 60, N'Nop.Services.Orders.AssignInvoiceIdentTask, Nop.Services', 0, 0) +GO + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'ordersettings.assigninvoiceidentfromtask') + BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'ordersettings.assigninvoiceidentfromtask', 'False', 0) + END +GO +GO + + --new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'adminareasettings.useisodatetimeconverterinjson') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'adminareasettings.useisodatetimeconverterinjson', N'True', 0) +END +GO + +--new activity type +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'ImportCategories') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'ImportCategories', N'Categories were imported', N'True') +END +GO + +--new activity type +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'ImportManufacturers') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'ImportManufacturers', N'Manufacturers were imported', N'True') +END +GO + +--new activity type +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'ImportProducts') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'ImportProducts', N'Products were imported', N'True') +END +GO + +--new activity type +IF NOT EXISTS (SELECT 1 FROM [ActivityLogType] WHERE [SystemKeyword] = N'ImportStates') +BEGIN + INSERT [ActivityLogType] ([SystemKeyword], [Name], [Enabled]) + VALUES (N'ImportStates', N'States and provinces were imported', N'True') +END +GO + +--update DownloadActivationType according to the new enum value +UPDATE [Product] +SET [DownloadActivationTypeId] = 0 +WHERE [DownloadActivationTypeId] = 1 +GO + +--new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Currency]') and NAME='RoundingTypeId') +BEGIN + ALTER TABLE [Currency] + ADD [RoundingTypeId] INT NULL +END +GO + +UPDATE [Currency] +SET [RoundingTypeId] = 0 +WHERE [RoundingTypeId] IS NULL +GO + +-- Rounding with 1.00 intervals (The system used in Sweden since 30 September 2010. https://en.wikipedia.org/wiki/Cash_rounding#Rounding_with_1.00_intervals) +UPDATE [Currency] +SET [RoundingTypeId] = 60 +WHERE [CurrencyCode] = 'SEK' +GO + +ALTER TABLE [Currency] ALTER COLUMN [RoundingTypeId] INT NOT NULL +GO + + --new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'adminareasettings.usericheditorinmessagetemplates') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'adminareasettings.usericheditorinmessagetemplates', N'False', 0) +END +GO + + --new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'mediasettings.azurecachecontrolheader') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'mediasettings.azurecachecontrolheader', N'', 0) +END +GO + +--add stored procedure for getting category tree +IF EXISTS (SELECT * FROM sysobjects WHERE id = OBJECT_ID(N'[nop_padright]') AND xtype IN (N'FN', N'IF', N'TF')) +DROP FUNCTION [dbo].[nop_padright] +GO + +CREATE FUNCTION [dbo].[nop_padright] +( + @source INT, + @symbol NVARCHAR(MAX), + @length INT +) +RETURNS NVARCHAR(MAX) +AS +BEGIN + RETURN RIGHT(REPLICATE(@symbol, @length)+ RTRIM(CAST(@source AS NVARCHAR(MAX))), @length) +END +GO + +IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[CategoryLoadAllPaged]') AND OBJECTPROPERTY(object_id, N'IsProcedure') = 1) +DROP PROCEDURE [dbo].[CategoryLoadAllPaged] +GO + +CREATE PROCEDURE [dbo].[CategoryLoadAllPaged] +( + @ShowHidden BIT = 0, + @Name NVARCHAR(MAX) = NULL, + @StoreId INT = 0, + @CustomerRoleIds NVARCHAR(MAX) = NULL, + @PageIndex INT = 0, + @PageSize INT = 2147483644, + @TotalRecords INT = NULL OUTPUT +) +AS +BEGIN + SET NOCOUNT ON + + --filter by customer role IDs (access control list) + SET @CustomerRoleIds = ISNULL(@CustomerRoleIds, '') + CREATE TABLE #FilteredCustomerRoleIds + ( + CustomerRoleId INT NOT NULL + ) + INSERT INTO #FilteredCustomerRoleIds (CustomerRoleId) + SELECT CAST(data AS INT) FROM [nop_splitstring_to_table](@CustomerRoleIds, ',') + DECLARE @FilteredCustomerRoleIdsCount INT = (SELECT COUNT(1) FROM #FilteredCustomerRoleIds) + + --ordered categories + CREATE TABLE #OrderedCategoryIds + ( + [Id] int IDENTITY (1, 1) NOT NULL, + [CategoryId] int NOT NULL + ) + + --get max length of DisplayOrder and Id columns (used for padding Order column) + DECLARE @lengthId INT = (SELECT LEN(MAX(Id)) FROM [Category]) + DECLARE @lengthOrder INT = (SELECT LEN(MAX(DisplayOrder)) FROM [Category]) + + --get category tree + ;WITH [CategoryTree] + AS (SELECT [Category].[Id] AS [Id], dbo.[nop_padright] ([Category].[DisplayOrder], '0', @lengthOrder) + '-' + dbo.[nop_padright] ([Category].[Id], '0', @lengthId) AS [Order] + FROM [Category] WHERE [Category].[ParentCategoryId] = 0 + UNION ALL + SELECT [Category].[Id] AS [Id], [CategoryTree].[Order] + '|' + dbo.[nop_padright] ([Category].[DisplayOrder], '0', @lengthOrder) + '-' + dbo.[nop_padright] ([Category].[Id], '0', @lengthId) AS [Order] + FROM [Category] + INNER JOIN [CategoryTree] ON [CategoryTree].[Id] = [Category].[ParentCategoryId]) + INSERT INTO #OrderedCategoryIds ([CategoryId]) + SELECT [Category].[Id] + FROM [CategoryTree] + RIGHT JOIN [Category] ON [CategoryTree].[Id] = [Category].[Id] + + --filter results + WHERE [Category].[Deleted] = 0 + AND (@ShowHidden = 1 OR [Category].[Published] = 1) + AND (@Name IS NULL OR @Name = '' OR [Category].[Name] LIKE ('%' + @Name + '%')) + AND (@ShowHidden = 1 OR @FilteredCustomerRoleIdsCount = 0 OR [Category].[SubjectToAcl] = 0 + OR EXISTS (SELECT 1 FROM #FilteredCustomerRoleIds [roles] WHERE [roles].[CustomerRoleId] IN + (SELECT [acl].[CustomerRoleId] FROM [AclRecord] acl WITH (NOLOCK) WHERE [acl].[EntityId] = [Category].[Id] AND [acl].[EntityName] = 'Category') + ) + ) + AND (@StoreId = 0 OR [Category].[LimitedToStores] = 0 + OR EXISTS (SELECT 1 FROM [StoreMapping] sm WITH (NOLOCK) + WHERE [sm].[EntityId] = [Category].[Id] AND [sm].[EntityName] = 'Category' AND [sm].[StoreId] = @StoreId + ) + ) + ORDER BY ISNULL([CategoryTree].[Order], 1) + + --total records + SET @TotalRecords = @@ROWCOUNT + + --paging + SELECT [Category].* FROM #OrderedCategoryIds AS [Result] INNER JOIN [Category] ON [Result].[CategoryId] = [Category].[Id] + WHERE ([Result].[Id] > @PageSize * @PageIndex AND [Result].[Id] <= @PageSize * (@PageIndex + 1)) + ORDER BY [Result].[Id] + + DROP TABLE #FilteredCustomerRoleIds + DROP TABLE #OrderedCategoryIds +END +GO + + --new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'commonsettings.usestoredprocedureforloadingcategories') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'commonsettings.usestoredprocedureforloadingcategories', N'False', 0) +END +GO + + --new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'storeinformationsettings.displayminiprofilerforadminonly') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'storeinformationsettings.displayminiprofilerforadminonly', N'False', 0) +END +GO + +--indicating whether to display default menu items +DECLARE @displayMenuItems bit +IF NOT EXISTS (SELECT 1 FROM [Category] where ParentCategoryId=0 and Deleted=0 and Published=1) + set @displayMenuItems = N'True' +ELSE + set @displayMenuItems = N'False' + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'displaydefaultmenuitemsettings.displayhomepagemenuitem') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'displaydefaultmenuitemsettings.displayhomepagemenuitem', @displayMenuItems, 0) +END + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'displaydefaultmenuitemsettings.displaynewproductsmenuitem') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'displaydefaultmenuitemsettings.displaynewproductsmenuitem', @displayMenuItems, 0) +END + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'displaydefaultmenuitemsettings.displayproductsearchmenuitem') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'displaydefaultmenuitemsettings.displayproductsearchmenuitem', @displayMenuItems, 0) +END + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'displaydefaultmenuitemsettings.displaycustomerinfomenuitem') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'displaydefaultmenuitemsettings.displaycustomerinfomenuitem', @displayMenuItems, 0) +END + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'displaydefaultmenuitemsettings.displayblogmenuitem') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'displaydefaultmenuitemsettings.displayblogmenuitem', @displayMenuItems, 0) +END + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'displaydefaultmenuitemsettings.displayforumsmenuitem') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'displaydefaultmenuitemsettings.displayforumsmenuitem', @displayMenuItems, 0) +END + +--new setting +IF NOT EXISTS (SELECT 1 FROM [Setting] WHERE [name] = N'displaydefaultmenuitemsettings.displaycontactusmenuitem ') +BEGIN + INSERT [Setting] ([Name], [Value], [StoreId]) + VALUES (N'displaydefaultmenuitemsettings.displaycontactusmenuitem ', @displayMenuItems, 0) +END GO \ No newline at end of file From 2d21d84ee2841e260b9eda80315c3cdb5e8f4a98 Mon Sep 17 00:00:00 2001 From: Markus Falk Date: Thu, 23 Feb 2017 21:07:22 +0100 Subject: [PATCH 09/13] Display OrderTotal Amount, OrderTotal --- src/Libraries/Nop.Core/Domain/Orders/Order.cs | 21 +- .../Domain/Orders/OrderAverageReportLine.cs | 60 +- .../Nop.Data/Mapping/Orders/OrderMap.cs | 1 + .../CodeFirstInstallationService.cs | 7 +- .../Orders/OrderProcessingService.cs | 4 + .../Nop.Services/Orders/OrderReportService.cs | 1121 ++++++++-------- src/Libraries/Nop.Services/Tax/TaxSummary.cs | 71 +- .../Controllers/AffiliateController.cs | 7 +- .../Controllers/CustomerController.cs | 1 + .../Controllers/OrderController.cs | 5 +- .../Models/Affiliates/AffiliateModel.cs | 132 +- .../Models/Customers/CustomerModel.cs | 2 + .../Models/Orders/OrderModel.cs | 5 +- .../Administration/Views/Order/List.cshtml | 1157 +++++++++-------- .../Views/Order/_OrderDetails.Info.cshtml | 8 + .../Localization/defaultResources.nopres.xml | 52 +- .../Nop.Web/Factories/OrderModelFactory.cs | 10 +- .../Factories/ShoppingCartModelFactory.cs | 4 + .../Models/Order/CustomerOrderListModel.cs | 1 + .../Nop.Web/Models/Order/OrderDetailsModel.cs | 1 + .../Models/ShoppingCart/OrderTotalsModel.cs | 1 + .../Nop.Web/Views/Order/CustomerOrders.cshtml | 3 +- .../Nop.Web/Views/Order/Details.cshtml | 11 + .../Orders/OrderPersistenceTests.cs | 260 ++-- .../3.80-the next version/upgrade.sql | 37 +- 25 files changed, 1599 insertions(+), 1383 deletions(-) diff --git a/src/Libraries/Nop.Core/Domain/Orders/Order.cs b/src/Libraries/Nop.Core/Domain/Orders/Order.cs index 473f9d8d940..793b025e86a 100644 --- a/src/Libraries/Nop.Core/Domain/Orders/Order.cs +++ b/src/Libraries/Nop.Core/Domain/Orders/Order.cs @@ -183,18 +183,18 @@ protected virtual SortedDictionary ParseTaxRates(string tax /// /// Gets or sets the order shipping (incl tax) /// - /// + /// public decimal OrderShippingInclTax { get; set; } /// /// Gets or sets the order shipping (excl tax) /// - /// + /// public decimal OrderShippingExclTax { get; set; } /// Gets or sets the order shipping (non taxable) /// public decimal OrderShippingNonTaxable { get; set; } - + /// /// Gets or sets the payment method additional fee (incl tax) /// @@ -395,6 +395,10 @@ protected virtual SortedDictionary ParseTaxRates(string tax /// public decimal EarnedRewardPointsBaseAmountExcl { get; set; } /// + /// Gets or sets a value indicating if redeemed reward points are taxable + /// + public bool RewardPointsTaxable { get; set; } + /// /// Gets or sets the custom order number without prefix /// public string CustomOrderNumber { get; set; } @@ -550,6 +554,17 @@ public SortedDictionary TaxRatesDictionary } } + /// + /// Gets the order total amount incl. tax + /// + public decimal OrderTotalAmountIncl + { + get + { + return OrderAmountIncl + PaymentMethodAdditionalFeeNonTaxable + OrderShippingNonTaxable + - (RewardPointsTaxable ? decimal.Zero : (RedeemedRewardPointsEntry != null ? RedeemedRewardPointsEntry.UsedAmount : decimal.Zero)); + } + } #endregion } diff --git a/src/Libraries/Nop.Core/Domain/Orders/OrderAverageReportLine.cs b/src/Libraries/Nop.Core/Domain/Orders/OrderAverageReportLine.cs index a56507a3fb8..2647afe82fb 100644 --- a/src/Libraries/Nop.Core/Domain/Orders/OrderAverageReportLine.cs +++ b/src/Libraries/Nop.Core/Domain/Orders/OrderAverageReportLine.cs @@ -1,28 +1,32 @@ -namespace Nop.Core.Domain.Orders -{ - /// - /// Represents an order average report line - /// - public partial class OrderAverageReportLine - { - /// - /// Gets or sets the count - /// - public int CountOrders { get; set; } - - /// - /// Gets or sets the shipping summary (excluding tax) - /// - public decimal SumShippingExclTax { get; set; } - - /// - /// Gets or sets the tax summary - /// - public decimal SumTax { get; set; } - - /// - /// Gets or sets the order total summary - /// - public decimal SumOrders { get; set; } - } -} +namespace Nop.Core.Domain.Orders +{ + /// + /// Represents an order average report line + /// + public partial class OrderAverageReportLine + { + /// + /// Gets or sets the count + /// + public int CountOrders { get; set; } + + /// + /// Gets or sets the shipping summary (excluding tax) + /// + public decimal SumShippingExclTax { get; set; } + + /// + /// Gets or sets the tax summary + /// + public decimal SumTax { get; set; } + + /// + /// Gets or sets the order total summary + /// + public decimal SumOrders { get; set; } + /// + /// Gets or sets the order total amount summary + /// + public decimal SumOrdersTotalAmountIncl { get; set; } + } +} diff --git a/src/Libraries/Nop.Data/Mapping/Orders/OrderMap.cs b/src/Libraries/Nop.Data/Mapping/Orders/OrderMap.cs index ce70a23d292..d9221fd4b13 100644 --- a/src/Libraries/Nop.Data/Mapping/Orders/OrderMap.cs +++ b/src/Libraries/Nop.Data/Mapping/Orders/OrderMap.cs @@ -35,6 +35,7 @@ public OrderMap() this.Ignore(o => o.ShippingStatus); this.Ignore(o => o.CustomerTaxDisplayType); this.Ignore(o => o.TaxRatesDictionary); + this.Ignore(o => o.OrderTotalAmountIncl); this.HasRequired(o => o.Customer) .WithMany() diff --git a/src/Libraries/Nop.Services/Installation/CodeFirstInstallationService.cs b/src/Libraries/Nop.Services/Installation/CodeFirstInstallationService.cs index 7d6d2207490..fa2043b140a 100644 --- a/src/Libraries/Nop.Services/Installation/CodeFirstInstallationService.cs +++ b/src/Libraries/Nop.Services/Installation/CodeFirstInstallationService.cs @@ -4457,6 +4457,7 @@ protected virtual void InstallOrders() OrderDiscountIncl = decimal.Zero, EarnedRewardPointsBaseAmountIncl = 1855M, EarnedRewardPointsBaseAmountExcl = 1855M, + RewardPointsTaxable = false, RefundedAmount = decimal.Zero, OrderDiscount = decimal.Zero, CheckoutAttributeDescription = string.Empty, @@ -4634,6 +4635,7 @@ protected virtual void InstallOrders() OrderDiscountIncl = decimal.Zero, EarnedRewardPointsBaseAmountIncl = 2460M, EarnedRewardPointsBaseAmountExcl = 2460M, + RewardPointsTaxable = false, RefundedAmount = decimal.Zero, OrderDiscount = decimal.Zero, CheckoutAttributeDescription = string.Empty, @@ -4762,8 +4764,9 @@ protected virtual void InstallOrders() OrderDiscountIncl = decimal.Zero, EarnedRewardPointsBaseAmountIncl = 8.80M, EarnedRewardPointsBaseAmountExcl = 8.80M, + RewardPointsTaxable = false, RefundedAmount = decimal.Zero, - OrderDiscount = decimal.Zero, + OrderDiscount = decimal.Zero, CheckoutAttributeDescription = string.Empty, CheckoutAttributesXml = string.Empty, CustomerCurrencyCode = "USD", @@ -4916,6 +4919,7 @@ protected virtual void InstallOrders() OrderDiscountIncl = decimal.Zero, EarnedRewardPointsBaseAmountIncl = 102M, EarnedRewardPointsBaseAmountExcl = 102M, + RewardPointsTaxable = false, RefundedAmount = decimal.Zero, OrderDiscount = decimal.Zero, CheckoutAttributeDescription = string.Empty, @@ -5139,6 +5143,7 @@ protected virtual void InstallOrders() OrderDiscountIncl = decimal.Zero, EarnedRewardPointsBaseAmountIncl = 43.50M, EarnedRewardPointsBaseAmountExcl = 43.50M, + RewardPointsTaxable = false, RefundedAmount = decimal.Zero, OrderDiscount = decimal.Zero, CheckoutAttributeDescription = string.Empty, diff --git a/src/Libraries/Nop.Services/Orders/OrderProcessingService.cs b/src/Libraries/Nop.Services/Orders/OrderProcessingService.cs index 3a1ae9f7e3f..db202e09cba 100644 --- a/src/Libraries/Nop.Services/Orders/OrderProcessingService.cs +++ b/src/Libraries/Nop.Services/Orders/OrderProcessingService.cs @@ -283,6 +283,7 @@ public PlaceOrderContainter() public decimal OrderDiscountAmountIncl { get; set; } public decimal EarnedRewardPointsBaseAmountIncl { get; set; } public decimal EarnedRewardPointsBaseAmountExcl { get; set; } + public bool RewardPointsTaxable { get; set; } } @@ -508,6 +509,7 @@ protected virtual PlaceOrderContainter PreparePlaceOrderDetails(ProcessPaymentRe details.OrderAmountIncl = includingTax ? taxSummaryIncl.TotalAmountIncludingTax : taxSummaryExcl.TotalAmountIncludingTax; details.EarnedRewardPointsBaseAmountIncl = earnedRewardPointsBaseAmountIncl; details.EarnedRewardPointsBaseAmountExcl = earnedRewardPointsBaseAmountExcl; + details.RewardPointsTaxable = _rewardPointsSettings.EarnedRewardPointsAreTaxable; //discount history @@ -644,6 +646,7 @@ protected virtual PlaceOrderContainter PrepareRecurringOrderDetails(ProcessPayme details.OrderDiscountAmountIncl = details.InitialOrder.OrderDiscountIncl; details.EarnedRewardPointsBaseAmountIncl = details.InitialOrder.EarnedRewardPointsBaseAmountIncl; details.EarnedRewardPointsBaseAmountExcl = details.InitialOrder.EarnedRewardPointsBaseAmountExcl; + details.RewardPointsTaxable = _rewardPointsSettings.EarnedRewardPointsAreTaxable; processPaymentRequest.OrderTotal = details.OrderTotal; return details; @@ -685,6 +688,7 @@ protected virtual Order SaveOrderDetails(ProcessPaymentRequest processPaymentReq OrderDiscountIncl = details.OrderDiscountAmountIncl, EarnedRewardPointsBaseAmountIncl = details.EarnedRewardPointsBaseAmountIncl, EarnedRewardPointsBaseAmountExcl = details.EarnedRewardPointsBaseAmountExcl, + RewardPointsTaxable = details.RewardPointsTaxable, RefundedAmount = decimal.Zero, OrderDiscount = details.OrderDiscountAmount, CheckoutAttributeDescription = details.CheckoutAttributeDescription, diff --git a/src/Libraries/Nop.Services/Orders/OrderReportService.cs b/src/Libraries/Nop.Services/Orders/OrderReportService.cs index 4ede06f25d2..6ac7cd28263 100644 --- a/src/Libraries/Nop.Services/Orders/OrderReportService.cs +++ b/src/Libraries/Nop.Services/Orders/OrderReportService.cs @@ -1,559 +1,562 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using Nop.Core; -using Nop.Core.Data; -using Nop.Core.Domain.Catalog; -using Nop.Core.Domain.Orders; -using Nop.Core.Domain.Payments; -using Nop.Core.Domain.Shipping; -using Nop.Core.Domain.Stores; -using Nop.Services.Helpers; - -namespace Nop.Services.Orders -{ - /// - /// Order report service - /// - public partial class OrderReportService : IOrderReportService - { - #region Fields - - private readonly IRepository _orderRepository; - private readonly IRepository _orderItemRepository; - private readonly IRepository _productRepository; - private readonly IRepository _storeMappingRepository; - private readonly IDateTimeHelper _dateTimeHelper; - private readonly CatalogSettings _catalogSettings; - - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Order repository - /// Order item repository - /// Product repository - /// Store mapping repository - /// Datetime helper - /// Catalog settings - public OrderReportService(IRepository orderRepository, - IRepository orderItemRepository, - IRepository productRepository, - IRepository storeMappingRepository, - IDateTimeHelper dateTimeHelper, - CatalogSettings catalogSettings) - { - this._orderRepository = orderRepository; - this._orderItemRepository = orderItemRepository; - this._productRepository = productRepository; - this._storeMappingRepository = storeMappingRepository; - this._dateTimeHelper = dateTimeHelper; - this._catalogSettings = catalogSettings; - } - - #endregion - - #region Methods - - /// - /// Get "order by country" report - /// - /// Store identifier - /// Order status - /// Payment status - /// Shipping status - /// Start date - /// End date - /// Result - public virtual IList GetCountryReport(int storeId, OrderStatus? os, - PaymentStatus? ps, ShippingStatus? ss, DateTime? startTimeUtc, DateTime? endTimeUtc) - { - int? orderStatusId = null; - if (os.HasValue) - orderStatusId = (int)os.Value; - - int? paymentStatusId = null; - if (ps.HasValue) - paymentStatusId = (int)ps.Value; - - int? shippingStatusId = null; - if (ss.HasValue) - shippingStatusId = (int)ss.Value; - - var query = _orderRepository.Table; - query = query.Where(o => !o.Deleted); - if (storeId > 0) - query = query.Where(o => o.StoreId == storeId); - if (orderStatusId.HasValue) - query = query.Where(o => o.OrderStatusId == orderStatusId.Value); - if (paymentStatusId.HasValue) - query = query.Where(o => o.PaymentStatusId == paymentStatusId.Value); - if (shippingStatusId.HasValue) - query = query.Where(o => o.ShippingStatusId == shippingStatusId.Value); - if (startTimeUtc.HasValue) - query = query.Where(o => startTimeUtc.Value <= o.CreatedOnUtc); - if (endTimeUtc.HasValue) - query = query.Where(o => endTimeUtc.Value >= o.CreatedOnUtc); - - var report = (from oq in query - group oq by oq.BillingAddress.CountryId into result - select new - { - CountryId = result.Key, - TotalOrders = result.Count(), - SumOrders = result.Sum(o => o.OrderTotal) - } - ) - .OrderByDescending(x => x.SumOrders) - .Select(r => new OrderByCountryReportLine - { - CountryId = r.CountryId, - TotalOrders = r.TotalOrders, - SumOrders = r.SumOrders - }) - - .ToList(); - - return report; - } - - /// - /// Get order average report - /// - /// Store identifier; pass 0 to ignore this parameter - /// Vendor identifier; pass 0 to ignore this parameter - /// Billing country identifier; 0 to load all orders - /// Order identifier; pass 0 to ignore this parameter - /// Payment method system name; null to load all records - /// Order status identifiers - /// Payment status identifiers - /// Shipping status identifiers - /// Start date - /// End date - /// Billing email. Leave empty to load all records. - /// Billing last name. Leave empty to load all records. - /// Search in order notes. Leave empty to load all records. - /// Result - public virtual OrderAverageReportLine GetOrderAverageReportLine(int storeId = 0, - int vendorId = 0, int billingCountryId = 0, - int orderId = 0, string paymentMethodSystemName = null, - List osIds = null, List psIds = null, List ssIds = null, - DateTime? startTimeUtc = null, DateTime? endTimeUtc = null, - string billingEmail = null, string billingLastName = "", string orderNotes = null) - { - var query = _orderRepository.Table; - query = query.Where(o => !o.Deleted); - if (storeId > 0) - query = query.Where(o => o.StoreId == storeId); - if (orderId > 0) - query = query.Where(o => o.Id == orderId); - if (vendorId > 0) - { - query = query - .Where(o => o.OrderItems - .Any(orderItem => orderItem.Product.VendorId == vendorId)); - } - if (billingCountryId > 0) - query = query.Where(o => o.BillingAddress != null && o.BillingAddress.CountryId == billingCountryId); - if (!String.IsNullOrEmpty(paymentMethodSystemName)) - query = query.Where(o => o.PaymentMethodSystemName == paymentMethodSystemName); - if (osIds != null && osIds.Any()) - query = query.Where(o => osIds.Contains(o.OrderStatusId)); - if (psIds != null && psIds.Any()) - query = query.Where(o => psIds.Contains(o.PaymentStatusId)); - if (ssIds != null && ssIds.Any()) - query = query.Where(o => ssIds.Contains(o.ShippingStatusId)); - if (startTimeUtc.HasValue) - query = query.Where(o => startTimeUtc.Value <= o.CreatedOnUtc); - if (endTimeUtc.HasValue) - query = query.Where(o => endTimeUtc.Value >= o.CreatedOnUtc); - if (!String.IsNullOrEmpty(billingEmail)) - query = query.Where(o => o.BillingAddress != null && !String.IsNullOrEmpty(o.BillingAddress.Email) && o.BillingAddress.Email.Contains(billingEmail)); - if (!String.IsNullOrEmpty(billingLastName)) - query = query.Where(o => o.BillingAddress != null && !String.IsNullOrEmpty(o.BillingAddress.LastName) && o.BillingAddress.LastName.Contains(billingLastName)); - if (!String.IsNullOrEmpty(orderNotes)) - query = query.Where(o => o.OrderNotes.Any(on => on.Note.Contains(orderNotes))); - - var item = (from oq in query - group oq by 1 into result - select new - { - OrderCount = result.Count(), - OrderShippingExclTaxSum = result.Sum(o => o.OrderShippingExclTax), - OrderTaxSum = result.Sum(o => o.OrderTax), - OrderTotalSum = result.Sum(o => o.OrderTotal) - } - ).Select(r => new OrderAverageReportLine - { - CountOrders = r.OrderCount, - SumShippingExclTax = r.OrderShippingExclTaxSum, - SumTax = r.OrderTaxSum, - SumOrders = r.OrderTotalSum - }) - .FirstOrDefault(); - - item = item ?? new OrderAverageReportLine - { - CountOrders = 0, - SumShippingExclTax = decimal.Zero, - SumTax = decimal.Zero, - SumOrders = decimal.Zero, - }; - return item; - } - - /// - /// Get order average report - /// - /// Store identifier - /// Order status - /// Result - public virtual OrderAverageReportLineSummary OrderAverageReport(int storeId, OrderStatus os) - { - var item = new OrderAverageReportLineSummary(); - item.OrderStatus = os; - var orderStatuses = new List() { (int)os }; - - DateTime nowDt = _dateTimeHelper.ConvertToUserTime(DateTime.Now); - TimeZoneInfo timeZone = _dateTimeHelper.CurrentTimeZone; - - //today - var t1 = new DateTime(nowDt.Year, nowDt.Month, nowDt.Day); - if (!timeZone.IsInvalidTime(t1)) - { - DateTime? startTime1 = _dateTimeHelper.ConvertToUtcTime(t1, timeZone); - var todayResult = GetOrderAverageReportLine(storeId: storeId, - osIds: orderStatuses, - startTimeUtc: startTime1); - item.SumTodayOrders = todayResult.SumOrders; - item.CountTodayOrders = todayResult.CountOrders; - } - //week - DayOfWeek fdow = CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek; - var today = new DateTime(nowDt.Year, nowDt.Month, nowDt.Day); - DateTime t2 = today.AddDays(-(today.DayOfWeek - fdow)); - if (!timeZone.IsInvalidTime(t2)) - { - DateTime? startTime2 = _dateTimeHelper.ConvertToUtcTime(t2, timeZone); - var weekResult = GetOrderAverageReportLine(storeId: storeId, - osIds: orderStatuses, - startTimeUtc: startTime2); - item.SumThisWeekOrders = weekResult.SumOrders; - item.CountThisWeekOrders = weekResult.CountOrders; - } - //month - var t3 = new DateTime(nowDt.Year, nowDt.Month, 1); - if (!timeZone.IsInvalidTime(t3)) - { - DateTime? startTime3 = _dateTimeHelper.ConvertToUtcTime(t3, timeZone); - var monthResult = GetOrderAverageReportLine(storeId: storeId, - osIds: orderStatuses, - startTimeUtc: startTime3); - item.SumThisMonthOrders = monthResult.SumOrders; - item.CountThisMonthOrders = monthResult.CountOrders; - } - //year - var t4 = new DateTime(nowDt.Year, 1, 1); - if (!timeZone.IsInvalidTime(t4)) - { - DateTime? startTime4 = _dateTimeHelper.ConvertToUtcTime(t4, timeZone); - var yearResult = GetOrderAverageReportLine(storeId: storeId, - osIds: orderStatuses, - startTimeUtc: startTime4); - item.SumThisYearOrders = yearResult.SumOrders; - item.CountThisYearOrders = yearResult.CountOrders; - } - //all time - var allTimeResult = GetOrderAverageReportLine(storeId: storeId, osIds: orderStatuses); - item.SumAllTimeOrders = allTimeResult.SumOrders; - item.CountAllTimeOrders = allTimeResult.CountOrders; - - return item; - } - - /// - /// Get best sellers report - /// - /// Store identifier (orders placed in a specific store); 0 to load all records - /// Vendor identifier; 0 to load all records - /// Category identifier; 0 to load all records - /// Manufacturer identifier; 0 to load all records - /// Order created date from (UTC); null to load all records - /// Order created date to (UTC); null to load all records - /// Order status; null to load all records - /// Order payment status; null to load all records - /// Shipping status; null to load all records - /// Billing country identifier; 0 to load all records - /// 1 - order by quantity, 2 - order by total amount - /// Page index - /// Page size - /// A value indicating whether to show hidden records - /// Result - public virtual IPagedList BestSellersReport( - int categoryId = 0, int manufacturerId = 0, - int storeId = 0, int vendorId = 0, - DateTime? createdFromUtc = null, DateTime? createdToUtc = null, - OrderStatus? os = null, PaymentStatus? ps = null, ShippingStatus? ss = null, - int billingCountryId = 0, - int orderBy = 1, - int pageIndex = 0, int pageSize = int.MaxValue, - bool showHidden = false) - { - int? orderStatusId = null; - if (os.HasValue) - orderStatusId = (int)os.Value; - - int? paymentStatusId = null; - if (ps.HasValue) - paymentStatusId = (int)ps.Value; - - int? shippingStatusId = null; - if (ss.HasValue) - shippingStatusId = (int)ss.Value; - - var query1 = from orderItem in _orderItemRepository.Table - join o in _orderRepository.Table on orderItem.OrderId equals o.Id - join p in _productRepository.Table on orderItem.ProductId equals p.Id - //join pc in _productCategoryRepository.Table on p.Id equals pc.ProductId into p_pc from pc in p_pc.DefaultIfEmpty() - //join pm in _productManufacturerRepository.Table on p.Id equals pm.ProductId into p_pm from pm in p_pm.DefaultIfEmpty() - where (storeId == 0 || storeId == o.StoreId) && - (!createdFromUtc.HasValue || createdFromUtc.Value <= o.CreatedOnUtc) && - (!createdToUtc.HasValue || createdToUtc.Value >= o.CreatedOnUtc) && - (!orderStatusId.HasValue || orderStatusId == o.OrderStatusId) && - (!paymentStatusId.HasValue || paymentStatusId == o.PaymentStatusId) && - (!shippingStatusId.HasValue || shippingStatusId == o.ShippingStatusId) && - (!o.Deleted) && - (!p.Deleted) && - (vendorId == 0 || p.VendorId == vendorId) && - //(categoryId == 0 || pc.CategoryId == categoryId) && - //(manufacturerId == 0 || pm.ManufacturerId == manufacturerId) && - (categoryId == 0 || p.ProductCategories.Count(pc => pc.CategoryId == categoryId) > 0) && - (manufacturerId == 0 || p.ProductManufacturers.Count(pm => pm.ManufacturerId == manufacturerId) > 0) && - (billingCountryId == 0 || o.BillingAddress.CountryId == billingCountryId) && - (showHidden || p.Published) - select orderItem; - - IQueryable query2 = - //group by products - from orderItem in query1 - group orderItem by orderItem.ProductId into g - select new BestsellersReportLine - { - ProductId = g.Key, - TotalAmount = g.Sum(x => x.PriceExclTax), - TotalQuantity = g.Sum(x => x.Quantity), - } - ; - - switch (orderBy) - { - case 1: - { - query2 = query2.OrderByDescending(x => x.TotalQuantity); - } - break; - case 2: - { - query2 = query2.OrderByDescending(x => x.TotalAmount); - } - break; - default: - throw new ArgumentException("Wrong orderBy parameter", "orderBy"); - } - - var result = new PagedList(query2, pageIndex, pageSize); - return result; - } - - /// - /// Gets a list of products (identifiers) purchased by other customers who purchased a specified product - /// - /// Store identifier - /// Product identifier - /// Records to return - /// A values indicating whether to load only products marked as "visible individually"; "false" to load all records; "true" to load "visible individually" only - /// A value indicating whether to show hidden records - /// Products - public virtual int[] GetAlsoPurchasedProductsIds(int storeId, int productId, - int recordsToReturn = 5, bool visibleIndividuallyOnly = true, bool showHidden = false) - { - if (productId == 0) - throw new ArgumentException("Product ID is not specified"); - - //this inner query should retrieve all orders that contains a specified product ID - var query1 = from orderItem in _orderItemRepository.Table - where orderItem.ProductId == productId - select orderItem.OrderId; - - var query2 = from orderItem in _orderItemRepository.Table - join p in _productRepository.Table on orderItem.ProductId equals p.Id - where (query1.Contains(orderItem.OrderId)) && - (p.Id != productId) && - (showHidden || p.Published) && - (!orderItem.Order.Deleted) && - (storeId == 0 || orderItem.Order.StoreId == storeId) && - (!p.Deleted) && - (!visibleIndividuallyOnly || p.VisibleIndividually) - select new { orderItem, p }; - - var query3 = from orderItem_p in query2 - group orderItem_p by orderItem_p.p.Id into g - select new - { - ProductId = g.Key, - ProductsPurchased = g.Sum(x => x.orderItem.Quantity), - }; - query3 = query3.OrderByDescending(x => x.ProductsPurchased); - - if (recordsToReturn > 0) - query3 = query3.Take(recordsToReturn); - - var report = query3.ToList(); - - var ids = new List(); - foreach (var reportLine in report) - ids.Add(reportLine.ProductId); - - return ids.ToArray(); - } - - /// - /// Gets a list of products that were never sold - /// - /// Vendor identifier (filter products by a specific vendor); 0 to load all records - /// Store identifier (filter products by a specific store); 0 to load all records - /// Category identifier; 0 to load all records - /// Manufacturer identifier; 0 to load all records - /// Order created date from (UTC); null to load all records - /// Order created date to (UTC); null to load all records - /// Page index - /// Page size - /// A value indicating whether to show hidden records - /// Products - public virtual IPagedList ProductsNeverSold(int vendorId = 0, int storeId = 0, - int categoryId = 0, int manufacturerId = 0, - DateTime? createdFromUtc = null, DateTime? createdToUtc = null, - int pageIndex = 0, int pageSize = int.MaxValue, bool showHidden = false) - { - //this inner query should retrieve all purchased product identifiers - var query_tmp = (from orderItem in _orderItemRepository.Table - join o in _orderRepository.Table on orderItem.OrderId equals o.Id - where (!createdFromUtc.HasValue || createdFromUtc.Value <= o.CreatedOnUtc) && - (!createdToUtc.HasValue || createdToUtc.Value >= o.CreatedOnUtc) && - (!o.Deleted) - select orderItem.ProductId).Distinct(); - - var simpleProductTypeId = (int) ProductType.SimpleProduct; - - var query = from p in _productRepository.Table - where (!query_tmp.Contains(p.Id)) && - //include only simple products - (p.ProductTypeId == simpleProductTypeId) && - (!p.Deleted) && - (vendorId == 0 || p.VendorId == vendorId) && - (categoryId == 0 || p.ProductCategories.Count(pc => pc.CategoryId == categoryId) > 0) && - (manufacturerId == 0 || p.ProductManufacturers.Count(pm => pm.ManufacturerId == manufacturerId) > 0) && - (showHidden || p.Published) - select p; - - - if (storeId > 0 && !_catalogSettings.IgnoreStoreLimitations) - { - query = from p in query - join sm in _storeMappingRepository.Table - on new { c1 = p.Id, c2 = "Product" } equals new { c1 = sm.EntityId, c2 = sm.EntityName } into p_sm - from sm in p_sm.DefaultIfEmpty() - where !p.LimitedToStores || storeId == sm.StoreId - select p; - } - - query = query.OrderBy(p => p.Name); - - var products = new PagedList(query, pageIndex, pageSize); - return products; - } - - /// - /// Get profit report - /// - /// Store identifier; pass 0 to ignore this parameter - /// Vendor identifier; pass 0 to ignore this parameter - /// Order identifier; pass 0 to ignore this parameter - /// Billing country identifier; 0 to load all orders - /// Payment method system name; null to load all records - /// Start date - /// End date - /// Order status identifiers; null to load all records - /// Payment status identifiers; null to load all records - /// Shipping status identifiers; null to load all records - /// Billing email. Leave empty to load all records. - /// Billing last name. Leave empty to load all records. - /// Search in order notes. Leave empty to load all records. - /// Result - public virtual decimal ProfitReport(int storeId = 0, int vendorId = 0, - int billingCountryId = 0, int orderId = 0, string paymentMethodSystemName = null, - List osIds = null, List psIds = null, List ssIds = null, - DateTime? startTimeUtc = null, DateTime? endTimeUtc = null, - string billingEmail = null, string billingLastName = "", string orderNotes = null) - { - //We cannot use String.IsNullOrEmpty() in SQL Compact - bool dontSearchEmail = String.IsNullOrEmpty(billingEmail); - //We cannot use String.IsNullOrEmpty() in SQL Compact - bool dontSearchLastName = String.IsNullOrEmpty(billingLastName); - //We cannot use String.IsNullOrEmpty() in SQL Compact - bool dontSearchOrderNotes = String.IsNullOrEmpty(orderNotes); - //We cannot use String.IsNullOrEmpty() in SQL Compact - bool dontSearchPaymentMethods = String.IsNullOrEmpty(paymentMethodSystemName); - - var orders = _orderRepository.Table; - if (osIds != null && osIds.Any()) - orders = orders.Where(o => osIds.Contains(o.OrderStatusId)); - if (psIds != null && psIds.Any()) - orders = orders.Where(o => psIds.Contains(o.PaymentStatusId)); - if (ssIds != null && ssIds.Any()) - orders = orders.Where(o => ssIds.Contains(o.ShippingStatusId)); - - var query = from orderItem in _orderItemRepository.Table - join o in orders on orderItem.OrderId equals o.Id - where (storeId == 0 || storeId == o.StoreId) && - (orderId == 0 || orderId == o.Id) && - (billingCountryId ==0 || (o.BillingAddress != null && o.BillingAddress.CountryId == billingCountryId)) && - (dontSearchPaymentMethods || paymentMethodSystemName == o.PaymentMethodSystemName) && - (!startTimeUtc.HasValue || startTimeUtc.Value <= o.CreatedOnUtc) && - (!endTimeUtc.HasValue || endTimeUtc.Value >= o.CreatedOnUtc) && - (!o.Deleted) && - (vendorId == 0 || orderItem.Product.VendorId == vendorId) && - //we do not ignore deleted products when calculating order reports - //(!p.Deleted) - (dontSearchEmail || (o.BillingAddress != null && !String.IsNullOrEmpty(o.BillingAddress.Email) && o.BillingAddress.Email.Contains(billingEmail))) && - (dontSearchLastName || (o.BillingAddress != null && !String.IsNullOrEmpty(o.BillingAddress.LastName) && o.BillingAddress.LastName.Contains(billingLastName))) && - (dontSearchOrderNotes || o.OrderNotes.Any(oNote => oNote.Note.Contains(orderNotes))) - select orderItem; - - var productCost = Convert.ToDecimal(query.Sum(orderItem => (decimal?)orderItem.OriginalProductCost * orderItem.Quantity)); - - var reportSummary = GetOrderAverageReportLine( - storeId: storeId, - vendorId: vendorId, - billingCountryId: billingCountryId, - orderId: orderId, - paymentMethodSystemName: paymentMethodSystemName, - osIds: osIds, - psIds: psIds, - ssIds: ssIds, - startTimeUtc: startTimeUtc, - endTimeUtc: endTimeUtc, - billingEmail: billingEmail, - billingLastName: billingLastName, - orderNotes: orderNotes); - var profit = reportSummary.SumOrders - reportSummary.SumShippingExclTax - reportSummary.SumTax - productCost; - return profit; - } - - #endregion - } -} +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Nop.Core; +using Nop.Core.Data; +using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Orders; +using Nop.Core.Domain.Payments; +using Nop.Core.Domain.Shipping; +using Nop.Core.Domain.Stores; +using Nop.Services.Helpers; + +namespace Nop.Services.Orders +{ + /// + /// Order report service + /// + public partial class OrderReportService : IOrderReportService + { + #region Fields + + private readonly IRepository _orderRepository; + private readonly IRepository _orderItemRepository; + private readonly IRepository _productRepository; + private readonly IRepository _storeMappingRepository; + private readonly IDateTimeHelper _dateTimeHelper; + private readonly CatalogSettings _catalogSettings; + + #endregion + + #region Ctor + + /// + /// Ctor + /// + /// Order repository + /// Order item repository + /// Product repository + /// Store mapping repository + /// Datetime helper + /// Catalog settings + public OrderReportService(IRepository orderRepository, + IRepository orderItemRepository, + IRepository productRepository, + IRepository storeMappingRepository, + IDateTimeHelper dateTimeHelper, + CatalogSettings catalogSettings) + { + this._orderRepository = orderRepository; + this._orderItemRepository = orderItemRepository; + this._productRepository = productRepository; + this._storeMappingRepository = storeMappingRepository; + this._dateTimeHelper = dateTimeHelper; + this._catalogSettings = catalogSettings; + } + + #endregion + + #region Methods + + /// + /// Get "order by country" report + /// + /// Store identifier + /// Order status + /// Payment status + /// Shipping status + /// Start date + /// End date + /// Result + public virtual IList GetCountryReport(int storeId, OrderStatus? os, + PaymentStatus? ps, ShippingStatus? ss, DateTime? startTimeUtc, DateTime? endTimeUtc) + { + int? orderStatusId = null; + if (os.HasValue) + orderStatusId = (int)os.Value; + + int? paymentStatusId = null; + if (ps.HasValue) + paymentStatusId = (int)ps.Value; + + int? shippingStatusId = null; + if (ss.HasValue) + shippingStatusId = (int)ss.Value; + + var query = _orderRepository.Table; + query = query.Where(o => !o.Deleted); + if (storeId > 0) + query = query.Where(o => o.StoreId == storeId); + if (orderStatusId.HasValue) + query = query.Where(o => o.OrderStatusId == orderStatusId.Value); + if (paymentStatusId.HasValue) + query = query.Where(o => o.PaymentStatusId == paymentStatusId.Value); + if (shippingStatusId.HasValue) + query = query.Where(o => o.ShippingStatusId == shippingStatusId.Value); + if (startTimeUtc.HasValue) + query = query.Where(o => startTimeUtc.Value <= o.CreatedOnUtc); + if (endTimeUtc.HasValue) + query = query.Where(o => endTimeUtc.Value >= o.CreatedOnUtc); + + var report = (from oq in query + group oq by oq.BillingAddress.CountryId into result + select new + { + CountryId = result.Key, + TotalOrders = result.Count(), + SumOrders = result.Sum(o => o.OrderTotal) + } + ) + .OrderByDescending(x => x.SumOrders) + .Select(r => new OrderByCountryReportLine + { + CountryId = r.CountryId, + TotalOrders = r.TotalOrders, + SumOrders = r.SumOrders + }) + + .ToList(); + + return report; + } + + /// + /// Get order average report + /// + /// Store identifier; pass 0 to ignore this parameter + /// Vendor identifier; pass 0 to ignore this parameter + /// Billing country identifier; 0 to load all orders + /// Order identifier; pass 0 to ignore this parameter + /// Payment method system name; null to load all records + /// Order status identifiers + /// Payment status identifiers + /// Shipping status identifiers + /// Start date + /// End date + /// Billing email. Leave empty to load all records. + /// Billing last name. Leave empty to load all records. + /// Search in order notes. Leave empty to load all records. + /// Result + public virtual OrderAverageReportLine GetOrderAverageReportLine(int storeId = 0, + int vendorId = 0, int billingCountryId = 0, + int orderId = 0, string paymentMethodSystemName = null, + List osIds = null, List psIds = null, List ssIds = null, + DateTime? startTimeUtc = null, DateTime? endTimeUtc = null, + string billingEmail = null, string billingLastName = "", string orderNotes = null) + { + var query = _orderRepository.Table; + query = query.Where(o => !o.Deleted); + if (storeId > 0) + query = query.Where(o => o.StoreId == storeId); + if (orderId > 0) + query = query.Where(o => o.Id == orderId); + if (vendorId > 0) + { + query = query + .Where(o => o.OrderItems + .Any(orderItem => orderItem.Product.VendorId == vendorId)); + } + if (billingCountryId > 0) + query = query.Where(o => o.BillingAddress != null && o.BillingAddress.CountryId == billingCountryId); + if (!String.IsNullOrEmpty(paymentMethodSystemName)) + query = query.Where(o => o.PaymentMethodSystemName == paymentMethodSystemName); + if (osIds != null && osIds.Any()) + query = query.Where(o => osIds.Contains(o.OrderStatusId)); + if (psIds != null && psIds.Any()) + query = query.Where(o => psIds.Contains(o.PaymentStatusId)); + if (ssIds != null && ssIds.Any()) + query = query.Where(o => ssIds.Contains(o.ShippingStatusId)); + if (startTimeUtc.HasValue) + query = query.Where(o => startTimeUtc.Value <= o.CreatedOnUtc); + if (endTimeUtc.HasValue) + query = query.Where(o => endTimeUtc.Value >= o.CreatedOnUtc); + if (!String.IsNullOrEmpty(billingEmail)) + query = query.Where(o => o.BillingAddress != null && !String.IsNullOrEmpty(o.BillingAddress.Email) && o.BillingAddress.Email.Contains(billingEmail)); + if (!String.IsNullOrEmpty(billingLastName)) + query = query.Where(o => o.BillingAddress != null && !String.IsNullOrEmpty(o.BillingAddress.LastName) && o.BillingAddress.LastName.Contains(billingLastName)); + if (!String.IsNullOrEmpty(orderNotes)) + query = query.Where(o => o.OrderNotes.Any(on => on.Note.Contains(orderNotes))); + + var item = (from oq in query + group oq by 1 into result + select new + { + OrderCount = result.Count(), + OrderShippingExclTaxSum = result.Sum(o => o.OrderShippingExclTax), + OrderTaxSum = result.Sum(o => o.OrderTax), + OrderTotalAmountInclSum = result.Sum(o => o.OrderAmountIncl + o.OrderShippingNonTaxable + o.PaymentMethodAdditionalFeeNonTaxable - (o.RedeemedRewardPointsEntry != null ? o.RedeemedRewardPointsEntry.UsedAmount : decimal.Zero)), + OrderTotalSum = result.Sum(o => o.OrderTotal), + } + ).Select(r => new OrderAverageReportLine + { + CountOrders = r.OrderCount, + SumShippingExclTax = r.OrderShippingExclTaxSum, + SumTax = r.OrderTaxSum, + SumOrdersTotalAmountIncl = r.OrderTotalAmountInclSum, + SumOrders = r.OrderTotalSum, + }) + .FirstOrDefault(); + + item = item ?? new OrderAverageReportLine + { + CountOrders = 0, + SumShippingExclTax = decimal.Zero, + SumTax = decimal.Zero, + SumOrdersTotalAmountIncl = decimal.Zero, + SumOrders = decimal.Zero, + }; + return item; + } + + /// + /// Get order average report + /// + /// Store identifier + /// Order status + /// Result + public virtual OrderAverageReportLineSummary OrderAverageReport(int storeId, OrderStatus os) + { + var item = new OrderAverageReportLineSummary(); + item.OrderStatus = os; + var orderStatuses = new List() { (int)os }; + + DateTime nowDt = _dateTimeHelper.ConvertToUserTime(DateTime.Now); + TimeZoneInfo timeZone = _dateTimeHelper.CurrentTimeZone; + + //today + var t1 = new DateTime(nowDt.Year, nowDt.Month, nowDt.Day); + if (!timeZone.IsInvalidTime(t1)) + { + DateTime? startTime1 = _dateTimeHelper.ConvertToUtcTime(t1, timeZone); + var todayResult = GetOrderAverageReportLine(storeId: storeId, + osIds: orderStatuses, + startTimeUtc: startTime1); + item.SumTodayOrders = todayResult.SumOrders; + item.CountTodayOrders = todayResult.CountOrders; + } + //week + DayOfWeek fdow = CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek; + var today = new DateTime(nowDt.Year, nowDt.Month, nowDt.Day); + DateTime t2 = today.AddDays(-(today.DayOfWeek - fdow)); + if (!timeZone.IsInvalidTime(t2)) + { + DateTime? startTime2 = _dateTimeHelper.ConvertToUtcTime(t2, timeZone); + var weekResult = GetOrderAverageReportLine(storeId: storeId, + osIds: orderStatuses, + startTimeUtc: startTime2); + item.SumThisWeekOrders = weekResult.SumOrders; + item.CountThisWeekOrders = weekResult.CountOrders; + } + //month + var t3 = new DateTime(nowDt.Year, nowDt.Month, 1); + if (!timeZone.IsInvalidTime(t3)) + { + DateTime? startTime3 = _dateTimeHelper.ConvertToUtcTime(t3, timeZone); + var monthResult = GetOrderAverageReportLine(storeId: storeId, + osIds: orderStatuses, + startTimeUtc: startTime3); + item.SumThisMonthOrders = monthResult.SumOrders; + item.CountThisMonthOrders = monthResult.CountOrders; + } + //year + var t4 = new DateTime(nowDt.Year, 1, 1); + if (!timeZone.IsInvalidTime(t4)) + { + DateTime? startTime4 = _dateTimeHelper.ConvertToUtcTime(t4, timeZone); + var yearResult = GetOrderAverageReportLine(storeId: storeId, + osIds: orderStatuses, + startTimeUtc: startTime4); + item.SumThisYearOrders = yearResult.SumOrders; + item.CountThisYearOrders = yearResult.CountOrders; + } + //all time + var allTimeResult = GetOrderAverageReportLine(storeId: storeId, osIds: orderStatuses); + item.SumAllTimeOrders = allTimeResult.SumOrders; + item.CountAllTimeOrders = allTimeResult.CountOrders; + + return item; + } + + /// + /// Get best sellers report + /// + /// Store identifier (orders placed in a specific store); 0 to load all records + /// Vendor identifier; 0 to load all records + /// Category identifier; 0 to load all records + /// Manufacturer identifier; 0 to load all records + /// Order created date from (UTC); null to load all records + /// Order created date to (UTC); null to load all records + /// Order status; null to load all records + /// Order payment status; null to load all records + /// Shipping status; null to load all records + /// Billing country identifier; 0 to load all records + /// 1 - order by quantity, 2 - order by total amount + /// Page index + /// Page size + /// A value indicating whether to show hidden records + /// Result + public virtual IPagedList BestSellersReport( + int categoryId = 0, int manufacturerId = 0, + int storeId = 0, int vendorId = 0, + DateTime? createdFromUtc = null, DateTime? createdToUtc = null, + OrderStatus? os = null, PaymentStatus? ps = null, ShippingStatus? ss = null, + int billingCountryId = 0, + int orderBy = 1, + int pageIndex = 0, int pageSize = int.MaxValue, + bool showHidden = false) + { + int? orderStatusId = null; + if (os.HasValue) + orderStatusId = (int)os.Value; + + int? paymentStatusId = null; + if (ps.HasValue) + paymentStatusId = (int)ps.Value; + + int? shippingStatusId = null; + if (ss.HasValue) + shippingStatusId = (int)ss.Value; + + var query1 = from orderItem in _orderItemRepository.Table + join o in _orderRepository.Table on orderItem.OrderId equals o.Id + join p in _productRepository.Table on orderItem.ProductId equals p.Id + //join pc in _productCategoryRepository.Table on p.Id equals pc.ProductId into p_pc from pc in p_pc.DefaultIfEmpty() + //join pm in _productManufacturerRepository.Table on p.Id equals pm.ProductId into p_pm from pm in p_pm.DefaultIfEmpty() + where (storeId == 0 || storeId == o.StoreId) && + (!createdFromUtc.HasValue || createdFromUtc.Value <= o.CreatedOnUtc) && + (!createdToUtc.HasValue || createdToUtc.Value >= o.CreatedOnUtc) && + (!orderStatusId.HasValue || orderStatusId == o.OrderStatusId) && + (!paymentStatusId.HasValue || paymentStatusId == o.PaymentStatusId) && + (!shippingStatusId.HasValue || shippingStatusId == o.ShippingStatusId) && + (!o.Deleted) && + (!p.Deleted) && + (vendorId == 0 || p.VendorId == vendorId) && + //(categoryId == 0 || pc.CategoryId == categoryId) && + //(manufacturerId == 0 || pm.ManufacturerId == manufacturerId) && + (categoryId == 0 || p.ProductCategories.Count(pc => pc.CategoryId == categoryId) > 0) && + (manufacturerId == 0 || p.ProductManufacturers.Count(pm => pm.ManufacturerId == manufacturerId) > 0) && + (billingCountryId == 0 || o.BillingAddress.CountryId == billingCountryId) && + (showHidden || p.Published) + select orderItem; + + IQueryable query2 = + //group by products + from orderItem in query1 + group orderItem by orderItem.ProductId into g + select new BestsellersReportLine + { + ProductId = g.Key, + TotalAmount = g.Sum(x => x.PriceExclTax), + TotalQuantity = g.Sum(x => x.Quantity), + } + ; + + switch (orderBy) + { + case 1: + { + query2 = query2.OrderByDescending(x => x.TotalQuantity); + } + break; + case 2: + { + query2 = query2.OrderByDescending(x => x.TotalAmount); + } + break; + default: + throw new ArgumentException("Wrong orderBy parameter", "orderBy"); + } + + var result = new PagedList(query2, pageIndex, pageSize); + return result; + } + + /// + /// Gets a list of products (identifiers) purchased by other customers who purchased a specified product + /// + /// Store identifier + /// Product identifier + /// Records to return + /// A values indicating whether to load only products marked as "visible individually"; "false" to load all records; "true" to load "visible individually" only + /// A value indicating whether to show hidden records + /// Products + public virtual int[] GetAlsoPurchasedProductsIds(int storeId, int productId, + int recordsToReturn = 5, bool visibleIndividuallyOnly = true, bool showHidden = false) + { + if (productId == 0) + throw new ArgumentException("Product ID is not specified"); + + //this inner query should retrieve all orders that contains a specified product ID + var query1 = from orderItem in _orderItemRepository.Table + where orderItem.ProductId == productId + select orderItem.OrderId; + + var query2 = from orderItem in _orderItemRepository.Table + join p in _productRepository.Table on orderItem.ProductId equals p.Id + where (query1.Contains(orderItem.OrderId)) && + (p.Id != productId) && + (showHidden || p.Published) && + (!orderItem.Order.Deleted) && + (storeId == 0 || orderItem.Order.StoreId == storeId) && + (!p.Deleted) && + (!visibleIndividuallyOnly || p.VisibleIndividually) + select new { orderItem, p }; + + var query3 = from orderItem_p in query2 + group orderItem_p by orderItem_p.p.Id into g + select new + { + ProductId = g.Key, + ProductsPurchased = g.Sum(x => x.orderItem.Quantity), + }; + query3 = query3.OrderByDescending(x => x.ProductsPurchased); + + if (recordsToReturn > 0) + query3 = query3.Take(recordsToReturn); + + var report = query3.ToList(); + + var ids = new List(); + foreach (var reportLine in report) + ids.Add(reportLine.ProductId); + + return ids.ToArray(); + } + + /// + /// Gets a list of products that were never sold + /// + /// Vendor identifier (filter products by a specific vendor); 0 to load all records + /// Store identifier (filter products by a specific store); 0 to load all records + /// Category identifier; 0 to load all records + /// Manufacturer identifier; 0 to load all records + /// Order created date from (UTC); null to load all records + /// Order created date to (UTC); null to load all records + /// Page index + /// Page size + /// A value indicating whether to show hidden records + /// Products + public virtual IPagedList ProductsNeverSold(int vendorId = 0, int storeId = 0, + int categoryId = 0, int manufacturerId = 0, + DateTime? createdFromUtc = null, DateTime? createdToUtc = null, + int pageIndex = 0, int pageSize = int.MaxValue, bool showHidden = false) + { + //this inner query should retrieve all purchased product identifiers + var query_tmp = (from orderItem in _orderItemRepository.Table + join o in _orderRepository.Table on orderItem.OrderId equals o.Id + where (!createdFromUtc.HasValue || createdFromUtc.Value <= o.CreatedOnUtc) && + (!createdToUtc.HasValue || createdToUtc.Value >= o.CreatedOnUtc) && + (!o.Deleted) + select orderItem.ProductId).Distinct(); + + var simpleProductTypeId = (int) ProductType.SimpleProduct; + + var query = from p in _productRepository.Table + where (!query_tmp.Contains(p.Id)) && + //include only simple products + (p.ProductTypeId == simpleProductTypeId) && + (!p.Deleted) && + (vendorId == 0 || p.VendorId == vendorId) && + (categoryId == 0 || p.ProductCategories.Count(pc => pc.CategoryId == categoryId) > 0) && + (manufacturerId == 0 || p.ProductManufacturers.Count(pm => pm.ManufacturerId == manufacturerId) > 0) && + (showHidden || p.Published) + select p; + + + if (storeId > 0 && !_catalogSettings.IgnoreStoreLimitations) + { + query = from p in query + join sm in _storeMappingRepository.Table + on new { c1 = p.Id, c2 = "Product" } equals new { c1 = sm.EntityId, c2 = sm.EntityName } into p_sm + from sm in p_sm.DefaultIfEmpty() + where !p.LimitedToStores || storeId == sm.StoreId + select p; + } + + query = query.OrderBy(p => p.Name); + + var products = new PagedList(query, pageIndex, pageSize); + return products; + } + + /// + /// Get profit report + /// + /// Store identifier; pass 0 to ignore this parameter + /// Vendor identifier; pass 0 to ignore this parameter + /// Order identifier; pass 0 to ignore this parameter + /// Billing country identifier; 0 to load all orders + /// Payment method system name; null to load all records + /// Start date + /// End date + /// Order status identifiers; null to load all records + /// Payment status identifiers; null to load all records + /// Shipping status identifiers; null to load all records + /// Billing email. Leave empty to load all records. + /// Billing last name. Leave empty to load all records. + /// Search in order notes. Leave empty to load all records. + /// Result + public virtual decimal ProfitReport(int storeId = 0, int vendorId = 0, + int billingCountryId = 0, int orderId = 0, string paymentMethodSystemName = null, + List osIds = null, List psIds = null, List ssIds = null, + DateTime? startTimeUtc = null, DateTime? endTimeUtc = null, + string billingEmail = null, string billingLastName = "", string orderNotes = null) + { + //We cannot use String.IsNullOrEmpty() in SQL Compact + bool dontSearchEmail = String.IsNullOrEmpty(billingEmail); + //We cannot use String.IsNullOrEmpty() in SQL Compact + bool dontSearchLastName = String.IsNullOrEmpty(billingLastName); + //We cannot use String.IsNullOrEmpty() in SQL Compact + bool dontSearchOrderNotes = String.IsNullOrEmpty(orderNotes); + //We cannot use String.IsNullOrEmpty() in SQL Compact + bool dontSearchPaymentMethods = String.IsNullOrEmpty(paymentMethodSystemName); + + var orders = _orderRepository.Table; + if (osIds != null && osIds.Any()) + orders = orders.Where(o => osIds.Contains(o.OrderStatusId)); + if (psIds != null && psIds.Any()) + orders = orders.Where(o => psIds.Contains(o.PaymentStatusId)); + if (ssIds != null && ssIds.Any()) + orders = orders.Where(o => ssIds.Contains(o.ShippingStatusId)); + + var query = from orderItem in _orderItemRepository.Table + join o in orders on orderItem.OrderId equals o.Id + where (storeId == 0 || storeId == o.StoreId) && + (orderId == 0 || orderId == o.Id) && + (billingCountryId ==0 || (o.BillingAddress != null && o.BillingAddress.CountryId == billingCountryId)) && + (dontSearchPaymentMethods || paymentMethodSystemName == o.PaymentMethodSystemName) && + (!startTimeUtc.HasValue || startTimeUtc.Value <= o.CreatedOnUtc) && + (!endTimeUtc.HasValue || endTimeUtc.Value >= o.CreatedOnUtc) && + (!o.Deleted) && + (vendorId == 0 || orderItem.Product.VendorId == vendorId) && + //we do not ignore deleted products when calculating order reports + //(!p.Deleted) + (dontSearchEmail || (o.BillingAddress != null && !String.IsNullOrEmpty(o.BillingAddress.Email) && o.BillingAddress.Email.Contains(billingEmail))) && + (dontSearchLastName || (o.BillingAddress != null && !String.IsNullOrEmpty(o.BillingAddress.LastName) && o.BillingAddress.LastName.Contains(billingLastName))) && + (dontSearchOrderNotes || o.OrderNotes.Any(oNote => oNote.Note.Contains(orderNotes))) + select orderItem; + + var productCost = Convert.ToDecimal(query.Sum(orderItem => (decimal?)orderItem.OriginalProductCost * orderItem.Quantity)); + + var reportSummary = GetOrderAverageReportLine( + storeId: storeId, + vendorId: vendorId, + billingCountryId: billingCountryId, + orderId: orderId, + paymentMethodSystemName: paymentMethodSystemName, + osIds: osIds, + psIds: psIds, + ssIds: ssIds, + startTimeUtc: startTimeUtc, + endTimeUtc: endTimeUtc, + billingEmail: billingEmail, + billingLastName: billingLastName, + orderNotes: orderNotes); + var profit = reportSummary.SumOrders - reportSummary.SumShippingExclTax - reportSummary.SumTax - productCost; + return profit; + } + + #endregion + } +} diff --git a/src/Libraries/Nop.Services/Tax/TaxSummary.cs b/src/Libraries/Nop.Services/Tax/TaxSummary.cs index 55820318f36..cddddb8a3c8 100644 --- a/src/Libraries/Nop.Services/Tax/TaxSummary.cs +++ b/src/Libraries/Nop.Services/Tax/TaxSummary.cs @@ -77,13 +77,25 @@ public TaxSummary(bool pricesIncludingTax) #endregion public SortedDictionary TaxRates { get; private set; } + /// + /// Indicates if supplied prices and amounts do include tax + /// public bool PricesIncludeTax { get; } public decimal PercentageInvDiscount { get; private set; } = decimal.Zero; //set via sub public decimal PercentageSubTotalDiscount { get; private set; } = decimal.Zero; //set via sub public decimal PercentageRewardPointsDiscount { get; private set; } = decimal.Zero; //earned points, set via sub public decimal PercentagePaymentFeeOrDiscount { get; private set; } = decimal.Zero; //set via sub + /// + /// Total subtotal of taxrates + /// public decimal TotalSubTotalAmount { get { if (HasChanges) CalculateAmounts(); return _totalSubTotalAmount; } } + /// + /// Total shipping of taxrates + /// public decimal? TotalShippingAmountTaxable { get { if (HasChanges) CalculateAmounts(); return _totalShippingAmountTaxable; } } + /// + /// Total shipping not taxable + /// public decimal? TotalShippingAmountNonTaxable { get { return _totalShippingAmountNonTaxable; } @@ -97,40 +109,93 @@ public decimal? TotalShippingAmountNonTaxable } } } + /// + /// Total shipping amount (taxable + nontaxable) + /// public decimal? TotalShippingAmount { get { return GetSum(SumType.TotalShippingAmount); } } //sum of shipping amounts + /// + /// Total reward points amount (taxable) = earned points + /// public decimal TotalRewardPointsAmountTaxable { get { if (HasChanges) CalculateAmounts(); return _totalRewardPointsAmountTaxable; } } //set via percentage + /// + /// Total reward points amount (nontaxable) = earned points set as nontaxable + /// public decimal? TotalRewardPointsAmountNonTaxable { get { return _totalRewardPointsAmountNonTaxable; } set { _totalRewardPointsAmountNonTaxable = value.HasValue ? RoundingHelper.RoundAmount(value.Value) : value; } } + /// + /// Total payment fee amount (taxable) + /// public decimal TotalPaymentFeeAmount { get { if (HasChanges) CalculateAmounts(); return _totalPaymentFeeAmount; } } //set via percentage + /// + /// Total payment fee amount (nontaxable) + /// public decimal? TotalPaymentFeeAmountNonTaxable { get { return _totalPaymentFeeAmountNonTaxable; } set { _totalPaymentFeeAmountNonTaxable = value.HasValue ? RoundingHelper.RoundAmount(value.Value) : value; } } - + /// + /// Total payment fee discount amount (taxable) + /// public decimal TotalPaymentFeeDiscAmount { get { if (HasChanges) CalculateAmounts(); return _totalPaymentFeeDiscAmount; } } //set via percentage + /// + /// Total payment fee + discount amount (taxable) + /// public decimal TotalPaymentFeeAmountTaxable { get { return GetSum(SumType.TotalPaymentFeeAmountTaxable) ?? decimal.Zero; } } //sum of payment fees + /// + /// Total subtotal discount of taxrates + /// public decimal TotalSubTotalDiscAmount { get { if (HasChanges) CalculateAmounts(); return _totalSubTotalDiscAmount; } } //set via percentage + /// + /// Total invoice discount of taxrates + /// public decimal TotalInvDiscAmount { get { if (HasChanges) CalculateAmounts(); return _totalInvDiscAmount; } } //set via percentage + /// + /// Total base amount (excl. tax= + /// public decimal TotalAmount { get { if (HasChanges) CalculateAmounts(); return _totalAmount; } } + /// + /// Total tax amount + /// public decimal TotalAmountTax { get { if (HasChanges) CalculateAmounts(); return _totalAmountTax; } } + /// + /// Total amount incl. tax (base + tax) + /// public decimal TotalAmountIncludingTax { get { if (HasChanges) CalculateAmounts(); return _totalAmountIncludingTax; } } + /// + /// Total purchased reward points amount (nontaxable) + /// public decimal? TotalRewardPointsAmountPurchased { get { return _totalRewardPointsAmountPurchased; } set { _totalRewardPointsAmountPurchased = value.HasValue ? RoundingHelper.RoundAmount(value.Value) : value; } } + /// + /// Total gift cards amount (nontaxable) + /// public decimal? TotalGiftcardsAmount { get { return _totalGiftcardsAmount; } set { _totalGiftcardsAmount = value.HasValue ? RoundingHelper.RoundAmount(value.Value) : value; } } + /// + /// Total to pay (Amount incl. vat + non tax payment fee + non tax shipp - rewardpoints - gift cards) + /// public decimal TotalPaymentAmount { get { return GetSum(SumType.TotalPaymentAmount) ?? decimal.Zero; } } + /// + /// Total base amount for payment fee calculation (total amount or total incl.) + /// public decimal TotalBaseAmountForPaymentFeeCalculation { get { return GetSum(SumType.TotalBaseAmountForPaymentFeeCalculation) ?? decimal.Zero; } } - public decimal TotalOrderAmount { get { return GetSum(SumType.TotalOrderAmount) ?? decimal.Zero; } } + /// + /// Total order amount (base + fee (nontax) + shipp (nontax) - reward (nontax) + /// + public decimal TotalOrderAmountExcl { get { return GetSum(SumType.TotalOrderAmount) ?? decimal.Zero; } } + /// + /// Total order amount (base + tax + fee (nontax) + shipp (nontax) - reward (nontax) + /// public decimal TotalOrderAmountIncl { get { return GetSum(SumType.TotalOrderAmountIncl) ?? decimal.Zero; } } @@ -185,7 +250,7 @@ private enum SumType { TotalShippingAmount, TotalPaymentFeeAmountTaxable, TotalP return amountPay; case SumType.TotalBaseAmountForPaymentFeeCalculation: - return PricesIncludeTax ? TotalAmountIncludingTax : TotalAmount; + return PricesIncludeTax ? TotalOrderAmountIncl : TotalOrderAmountExcl; case SumType.TotalOrderAmount: decimal amountOrder = TotalAmount diff --git a/src/Presentation/Nop.Web/Administration/Controllers/AffiliateController.cs b/src/Presentation/Nop.Web/Administration/Controllers/AffiliateController.cs index c587cf1d1b4..b4077003dff 100644 --- a/src/Presentation/Nop.Web/Administration/Controllers/AffiliateController.cs +++ b/src/Presentation/Nop.Web/Administration/Controllers/AffiliateController.cs @@ -322,11 +322,11 @@ public virtual ActionResult AffiliatedOrderList(int affiliateId) //order statuses model.AvailableOrderStatuses = OrderStatus.Pending.ToSelectList(false).ToList(); model.AvailableOrderStatuses.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - + //payment statuses model.AvailablePaymentStatuses = PaymentStatus.Pending.ToSelectList(false).ToList(); model.AvailablePaymentStatuses.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); - + //shipping statuses model.AvailableShippingStatuses = ShippingStatus.NotYetShipped.ToSelectList(false).ToList(); model.AvailableShippingStatuses.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); @@ -372,6 +372,7 @@ public virtual ActionResult AffiliatedOrderListGrid(DataSourceRequest command, A orderModel.OrderStatusId = order.OrderStatusId; orderModel.PaymentStatus = order.PaymentStatus.GetLocalizedEnum(_localizationService, _workContext); orderModel.ShippingStatus = order.ShippingStatus.GetLocalizedEnum(_localizationService, _workContext); + orderModel.OrderTotalAmountIncl = _priceFormatter.FormatPrice(order.OrderTotalAmountIncl, true, false); orderModel.OrderTotal = _priceFormatter.FormatPrice(order.OrderTotal, true, false); orderModel.CreatedOn = _dateTimeHelper.ConvertToUserTime(order.CreatedOnUtc, DateTimeKind.Utc); orderModel.CustomOrderNumber = order.CustomOrderNumber; @@ -394,7 +395,7 @@ public virtual ActionResult AffiliatedCustomerList(int affiliateId, DataSourceRe var affiliate = _affiliateService.GetAffiliateById(affiliateId); if (affiliate == null) throw new ArgumentException("No affiliate found with the specified id"); - + var customers = _customerService.GetAllCustomers( affiliateId: affiliate.Id, pageIndex: command.Page - 1, diff --git a/src/Presentation/Nop.Web/Administration/Controllers/CustomerController.cs b/src/Presentation/Nop.Web/Administration/Controllers/CustomerController.cs index c5452d4b84d..025eb4e14a3 100644 --- a/src/Presentation/Nop.Web/Administration/Controllers/CustomerController.cs +++ b/src/Presentation/Nop.Web/Administration/Controllers/CustomerController.cs @@ -1935,6 +1935,7 @@ public virtual ActionResult OrderList(int customerId, DataSourceRequest command) PaymentStatus = order.PaymentStatus.GetLocalizedEnum(_localizationService, _workContext), ShippingStatus = order.ShippingStatus.GetLocalizedEnum(_localizationService, _workContext), OrderTotal = _priceFormatter.FormatPrice(order.OrderTotal, true, false), + OrderTotalAmountIncl = _priceFormatter.FormatPrice(order.OrderTotalAmountIncl, true, false), StoreName = store != null ? store.Name : "Unknown", CreatedOn = _dateTimeHelper.ConvertToUserTime(order.CreatedOnUtc, DateTimeKind.Utc), CustomOrderNumber = order.CustomOrderNumber diff --git a/src/Presentation/Nop.Web/Administration/Controllers/OrderController.cs b/src/Presentation/Nop.Web/Administration/Controllers/OrderController.cs index e005a4c565e..e675be92548 100644 --- a/src/Presentation/Nop.Web/Administration/Controllers/OrderController.cs +++ b/src/Presentation/Nop.Web/Administration/Controllers/OrderController.cs @@ -577,6 +577,7 @@ protected virtual void PrepareOrderDetailsModel(OrderModel model, Order order) //total model.OrderTotal = _priceFormatter.FormatPrice(order.OrderTotal, true, false); model.OrderTotalValue = order.OrderTotal; + model.OrderTotalAmountIncl = _priceFormatter.FormatPrice(order.OrderTotalAmountIncl, true, false); model.OrderAmount = _priceFormatter.FormatPrice(order.OrderAmount, true, false); model.OrderAmountValue = order.OrderAmount; model.OrderAmountIncl = _priceFormatter.FormatPrice(order.OrderAmountIncl, true, false); @@ -1206,6 +1207,7 @@ public virtual ActionResult OrderList(DataSourceRequest command, OrderListModel Id = x.Id, StoreName = store != null ? store.Name : "Unknown", OrderTotal = _priceFormatter.FormatPrice(x.OrderTotal, true, false), + OrderTotalAmountIncl = _priceFormatter.FormatPrice(x.OrderTotalAmountIncl, true, false), OrderStatus = x.OrderStatus.GetLocalizedEnum(_localizationService, _workContext), OrderStatusId = x.OrderStatusId, PaymentStatus = x.PaymentStatus.GetLocalizedEnum(_localizationService, _workContext), @@ -1258,8 +1260,9 @@ public virtual ActionResult OrderList(DataSourceRequest command, OrderListModel gridModel.ExtraData = new OrderAggreratorModel { aggregatorprofit = _priceFormatter.FormatPrice(profit, true, false), - aggregatorshipping = _priceFormatter.FormatShippingPrice(reportSummary.SumShippingExclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, false), + aggregatorshipping = _priceFormatter.FormatShippingPrice(reportSummary.SumShippingExclTax, true, primaryStoreCurrency, _workContext.WorkingLanguage, false, false), aggregatortax = _priceFormatter.FormatPrice(reportSummary.SumTax, true, false), + aggregatortotalamountincl = _priceFormatter.FormatPrice(reportSummary.SumOrdersTotalAmountIncl, true, false), aggregatortotal = _priceFormatter.FormatPrice(reportSummary.SumOrders, true, false) }; diff --git a/src/Presentation/Nop.Web/Administration/Models/Affiliates/AffiliateModel.cs b/src/Presentation/Nop.Web/Administration/Models/Affiliates/AffiliateModel.cs index b0192b5817f..5f258cb76b5 100644 --- a/src/Presentation/Nop.Web/Administration/Models/Affiliates/AffiliateModel.cs +++ b/src/Presentation/Nop.Web/Administration/Models/Affiliates/AffiliateModel.cs @@ -1,66 +1,68 @@ -using System; -using System.Web.Mvc; -using Nop.Admin.Models.Common; -using Nop.Web.Framework; -using Nop.Web.Framework.Mvc; - -namespace Nop.Admin.Models.Affiliates -{ - public partial class AffiliateModel : BaseNopEntityModel - { - public AffiliateModel() - { - Address = new AddressModel(); - } - - [NopResourceDisplayName("Admin.Affiliates.Fields.URL")] - public string Url { get; set; } - - [NopResourceDisplayName("Admin.Affiliates.Fields.AdminComment")] - [AllowHtml] - public string AdminComment { get; set; } - - [NopResourceDisplayName("Admin.Affiliates.Fields.FriendlyUrlName")] - [AllowHtml] - public string FriendlyUrlName { get; set; } - - [NopResourceDisplayName("Admin.Affiliates.Fields.Active")] - public bool Active { get; set; } - - public AddressModel Address { get; set; } - - #region Nested classes - - public partial class AffiliatedOrderModel : BaseNopEntityModel - { - public override int Id { get; set; } - [NopResourceDisplayName("Admin.Affiliates.Orders.CustomOrderNumber")] - public string CustomOrderNumber { get; set; } - - [NopResourceDisplayName("Admin.Affiliates.Orders.OrderStatus")] - public string OrderStatus { get; set; } - [NopResourceDisplayName("Admin.Affiliates.Orders.OrderStatus")] - public int OrderStatusId { get; set; } - - [NopResourceDisplayName("Admin.Affiliates.Orders.PaymentStatus")] - public string PaymentStatus { get; set; } - - [NopResourceDisplayName("Admin.Affiliates.Orders.ShippingStatus")] - public string ShippingStatus { get; set; } - - [NopResourceDisplayName("Admin.Affiliates.Orders.OrderTotal")] - public string OrderTotal { get; set; } - - [NopResourceDisplayName("Admin.Affiliates.Orders.CreatedOn")] - public DateTime CreatedOn { get; set; } - } - - public partial class AffiliatedCustomerModel : BaseNopEntityModel - { - [NopResourceDisplayName("Admin.Affiliates.Customers.Name")] - public string Name { get; set; } - } - - #endregion - } +using System; +using System.Web.Mvc; +using Nop.Admin.Models.Common; +using Nop.Web.Framework; +using Nop.Web.Framework.Mvc; + +namespace Nop.Admin.Models.Affiliates +{ + public partial class AffiliateModel : BaseNopEntityModel + { + public AffiliateModel() + { + Address = new AddressModel(); + } + + [NopResourceDisplayName("Admin.Affiliates.Fields.URL")] + public string Url { get; set; } + + [NopResourceDisplayName("Admin.Affiliates.Fields.AdminComment")] + [AllowHtml] + public string AdminComment { get; set; } + + [NopResourceDisplayName("Admin.Affiliates.Fields.FriendlyUrlName")] + [AllowHtml] + public string FriendlyUrlName { get; set; } + + [NopResourceDisplayName("Admin.Affiliates.Fields.Active")] + public bool Active { get; set; } + + public AddressModel Address { get; set; } + + #region Nested classes + + public partial class AffiliatedOrderModel : BaseNopEntityModel + { + public override int Id { get; set; } + [NopResourceDisplayName("Admin.Affiliates.Orders.CustomOrderNumber")] + public string CustomOrderNumber { get; set; } + + [NopResourceDisplayName("Admin.Affiliates.Orders.OrderStatus")] + public string OrderStatus { get; set; } + [NopResourceDisplayName("Admin.Affiliates.Orders.OrderStatus")] + public int OrderStatusId { get; set; } + + [NopResourceDisplayName("Admin.Affiliates.Orders.PaymentStatus")] + public string PaymentStatus { get; set; } + + [NopResourceDisplayName("Admin.Affiliates.Orders.ShippingStatus")] + public string ShippingStatus { get; set; } + + [NopResourceDisplayName("Admin.Affiliates.Orders.OrderTotal")] + public string OrderTotal { get; set; } + [NopResourceDisplayName("Admin.Affiliates.Orders.OrderTotalAmount")] + public string OrderTotalAmountIncl { get; set; } + + [NopResourceDisplayName("Admin.Affiliates.Orders.CreatedOn")] + public DateTime CreatedOn { get; set; } + } + + public partial class AffiliatedCustomerModel : BaseNopEntityModel + { + [NopResourceDisplayName("Admin.Affiliates.Customers.Name")] + public string Name { get; set; } + } + + #endregion + } } \ No newline at end of file diff --git a/src/Presentation/Nop.Web/Administration/Models/Customers/CustomerModel.cs b/src/Presentation/Nop.Web/Administration/Models/Customers/CustomerModel.cs index fdb42c79b74..0e2aded94c1 100644 --- a/src/Presentation/Nop.Web/Administration/Models/Customers/CustomerModel.cs +++ b/src/Presentation/Nop.Web/Administration/Models/Customers/CustomerModel.cs @@ -316,6 +316,8 @@ public partial class OrderModel : BaseNopEntityModel [NopResourceDisplayName("Admin.Customers.Customers.Orders.OrderTotal")] public string OrderTotal { get; set; } + [NopResourceDisplayName("Admin.Customers.Customers.Orders.OrderTotalAmountIncl")] + public string OrderTotalAmountIncl { get; set; } [NopResourceDisplayName("Admin.Customers.Customers.Orders.Store")] public string StoreName { get; set; } diff --git a/src/Presentation/Nop.Web/Administration/Models/Orders/OrderModel.cs b/src/Presentation/Nop.Web/Administration/Models/Orders/OrderModel.cs index c31ed99abd7..210ec1823e9 100644 --- a/src/Presentation/Nop.Web/Administration/Models/Orders/OrderModel.cs +++ b/src/Presentation/Nop.Web/Administration/Models/Orders/OrderModel.cs @@ -36,7 +36,7 @@ public OrderModel() public DateTime? InvoiceDateUtc { get; set; } [NopResourceDisplayName("Admin.Orders.Fields.CustomOrderNumber")] public string CustomOrderNumber { get; set; } - + //store [NopResourceDisplayName("Admin.Orders.Fields.Store")] public string StoreName { get; set; } @@ -114,6 +114,8 @@ public OrderModel() public string OrderAmountIncl { get; set; } //MF 09.12.16 [NopResourceDisplayName("Admin.Orders.Fields.OrderTotalDiscountIncl")] public string OrderTotalDiscountIncl { get; set; } + [NopResourceDisplayName("Admin.Orders.Fields.OrderTotalAmountIncl")] + public string OrderTotalAmountIncl { get; set; } [NopResourceDisplayName("Admin.Orders.Fields.EarnedRewardPointsBaseAmountIncl")] public string EarnedRewardPointsBaseAmountIncl { get; set; } [NopResourceDisplayName("Admin.Orders.Fields.EarnedRewardPointsBaseAmountExcl")] @@ -549,5 +551,6 @@ public partial class OrderAggreratorModel : BaseNopModel public string aggregatorshipping { get; set; } public string aggregatortax { get; set; } public string aggregatortotal { get; set; } + public string aggregatortotalamountincl { get; set; } } } \ No newline at end of file diff --git a/src/Presentation/Nop.Web/Administration/Views/Order/List.cshtml b/src/Presentation/Nop.Web/Administration/Views/Order/List.cshtml index 5fc1aa6bf12..663cb6c1fdc 100644 --- a/src/Presentation/Nop.Web/Administration/Views/Order/List.cshtml +++ b/src/Presentation/Nop.Web/Administration/Views/Order/List.cshtml @@ -1,571 +1,588 @@ -@using Nop.Services.Stores -@model OrderListModel - -@{ - var defaultGridPageSize = EngineContext.Current.Resolve().DefaultGridPageSize; - var gridPageSizes = EngineContext.Current.Resolve().GridPageSizes; - var stores = EngineContext.Current.Resolve().GetAllStores(); - - //page title - ViewBag.Title = T("Admin.Orders").Text; - //active menu item (system name) - Html.SetActiveMenuItemSystemName("Orders"); -} - -@using (Html.BeginForm()) -{ - @Html.AntiForgeryToken() -
    -

    - @T("Admin.Orders") -

    -
    -
    - - - -
    -
    - - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    - - - - -
    -
    -
    -
    -
    -} - -@*export selected (XML). We don't use GET approach because it's limited to 2K-4K chars and won't work for large number of entities*@ -@using (Html.BeginForm("ExportXmlSelected", "Order", FormMethod.Post, new { id = "export-xml-selected-form" })) -{ - @Html.AntiForgeryToken() - -} - - - -@*export selected (Excel). We don't use GET approach because it's limited to 2K-4K chars and won't work for large number of entities*@ -@using (Html.BeginForm("ExportExcelSelected", "Order", FormMethod.Post, new { id = "export-excel-selected-form" })) -{ - @Html.AntiForgeryToken() - -} - - - -@*Print packaging slips selected (XML). We don't use GET approach because it's limited to 2K-4K chars and won't work for large number of entities*@ -@using (Html.BeginForm("PdfInvoiceSelected", "Order", FormMethod.Post, new { id = "pdf-invoice-selected-form" })) -{ - @Html.AntiForgeryToken() - -} - - +
    +
    +
    +
    + @Html.NopLabelFor(model => model.OrderStatusIds) +
    +
    + @Html.EditorFor(model => model.OrderStatusIds, new { SelectList = Model.AvailableOrderStatuses }) +
    +
    +
    +
    + @Html.NopLabelFor(model => model.PaymentStatusIds) +
    +
    + @Html.EditorFor(model => model.PaymentStatusIds, new { SelectList = Model.AvailablePaymentStatuses }) +
    +
    +
    +
    + @Html.NopLabelFor(model => model.ShippingStatusIds) +
    +
    + @Html.EditorFor(model => model.ShippingStatusIds, new { SelectList = Model.AvailableShippingStatuses }) +
    +
    + +
    +
    +
    + @Html.NopLabelFor(model => model.StoreId) +
    +
    + @Html.NopDropDownList("StoreId", Model.AvailableStores) +
    +
    +
    +
    + @Html.NopLabelFor(model => model.VendorId) +
    +
    + @Html.NopDropDownList("VendorId", Model.AvailableVendors) +
    +
    +
    +
    + @Html.NopLabelFor(model => model.BillingEmail) +
    +
    + @Html.NopEditorFor(model => model.BillingEmail) +
    +
    +
    +
    + @Html.NopLabelFor(model => model.BillingLastName) +
    +
    + @Html.NopEditorFor(model => model.BillingLastName) +
    +
    +
    +
    + @Html.NopLabelFor(model => model.BillingCountryId) +
    +
    + @Html.NopDropDownList("BillingCountryId", Model.AvailableCountries) +
    +
    +
    +
    + @Html.NopLabelFor(model => model.PaymentMethodSystemName) +
    +
    + @Html.NopDropDownList("PaymentMethodSystemName", Model.AvailablePaymentMethods) +
    +
    +
    +
    + @Html.NopLabelFor(model => model.OrderNotes) +
    +
    + @Html.NopEditorFor(model => model.OrderNotes) +
    +
    +
    +
    + @Html.NopLabelFor(model => model.GoDirectlyToCustomOrderNumber) +
    +
    +
    + @Html.NopEditorFor(model => model.GoDirectlyToCustomOrderNumber) + + + +
    +
    +
    +
    + +
    +
    + +
    +
    + + + +
    +
    +
    + + + + +
    +
    + + + +} + +@*export selected (XML). We don't use GET approach because it's limited to 2K-4K chars and won't work for large number of entities*@ +@using (Html.BeginForm("ExportXmlSelected", "Order", FormMethod.Post, new { id = "export-xml-selected-form" })) +{ + @Html.AntiForgeryToken() + +} + + + +@*export selected (Excel). We don't use GET approach because it's limited to 2K-4K chars and won't work for large number of entities*@ +@using (Html.BeginForm("ExportExcelSelected", "Order", FormMethod.Post, new { id = "export-excel-selected-form" })) +{ + @Html.AntiForgeryToken() + +} + + + +@*Print packaging slips selected (XML). We don't use GET approach because it's limited to 2K-4K chars and won't work for large number of entities*@ +@using (Html.BeginForm("PdfInvoiceSelected", "Order", FormMethod.Post, new { id = "pdf-invoice-selected-form" })) +{ + @Html.AntiForgeryToken() + +} + + \ No newline at end of file diff --git a/src/Presentation/Nop.Web/Administration/Views/Order/_OrderDetails.Info.cshtml b/src/Presentation/Nop.Web/Administration/Views/Order/_OrderDetails.Info.cshtml index 2a38becc4f7..88d1f2e5a7a 100644 --- a/src/Presentation/Nop.Web/Administration/Views/Order/_OrderDetails.Info.cshtml +++ b/src/Presentation/Nop.Web/Administration/Views/Order/_OrderDetails.Info.cshtml @@ -565,6 +565,14 @@ } +
    +
    + @Html.NopLabelFor(model => model.OrderTotalAmountIncl) +
    +
    + @Html.NopDisplayFor(model => model.OrderTotalAmountIncl) +
    +
    @Html.NopLabelFor(model => model.OrderTotal) diff --git a/src/Presentation/Nop.Web/App_Data/Localization/defaultResources.nopres.xml b/src/Presentation/Nop.Web/App_Data/Localization/defaultResources.nopres.xml index 3d91d965813..db7239a2441 100644 --- a/src/Presentation/Nop.Web/App_Data/Localization/defaultResources.nopres.xml +++ b/src/Presentation/Nop.Web/App_Data/Localization/defaultResources.nopres.xml @@ -169,7 +169,10 @@ Order status - Order Total + Order Total to pay + + + Order Total Amount Recurring payments @@ -1495,7 +1498,10 @@ Search by a specific order status e.g. Complete. - Order total + Order total to pay + + + Order total amount (incl.) Payment status @@ -4802,7 +4808,7 @@ You can download more nopCommerce plugins in our marketplace

    ]]>
    -
    + Upload the plugin to the /plugins folder in your nopCommerce directory. @@ -4931,7 +4937,7 @@ Here you can find third-party extensions and themes which are developed by our community and partners.They are also available in our marketplace

    ]]>
    -
    + Name @@ -6131,7 +6137,7 @@ A CAPTCHA is a program that can tell whether its user is a human or a computer.You've probably seen them — colorful images with distorted text at the bottom ofWeb registration forms. CAPTCHAs are used by many websites to prevent abuse from"bots," or automated programs usually written to generate spam. No computer programcan read distorted text as well as humans can, so bots cannot navigate sites protectedby CAPTCHAs. nopCommerce uses reCAPTCHA.

    ]]>
    -
    + Captcha is enabled but the appropriate keys are not entered @@ -9991,7 +9997,10 @@ Order status - Order total + Order total to pay + + + Order total amount Payment status @@ -10675,10 +10684,10 @@ Edit the subtotal discount of this order. - Order total + Order total to pay - Edit the total cost of this order. + Edit the total amount to pay for this order. Order discount @@ -10819,10 +10828,16 @@ incl tax - Order total + Order total to pay - The total cost of this order (includes discounts, shipping and tax). + The total amount to pay for this order (includes discounts, shipping and tax, reward points, gift cards). + + + Order total amount + + + The total amount for this order (includes discounts, shipping and tax). Order discount @@ -11314,6 +11329,9 @@ Tax + Total to pay + + Total @@ -11875,7 +11893,7 @@ Order # - Order total + Order total to pay Discount info @@ -14858,7 +14876,10 @@ Gift card ({0}): - Order Total: + Order Total to pay: + + + Order Total Amount: Payment method additional fee: @@ -15050,7 +15071,10 @@ Order Status - Order Total + Order Total to pay + + + Order Total Amount Payment @@ -15503,7 +15527,7 @@ Note - Order total: + Order total to pay: Payment method: {0} diff --git a/src/Presentation/Nop.Web/Factories/OrderModelFactory.cs b/src/Presentation/Nop.Web/Factories/OrderModelFactory.cs index 16a2f55544e..faddee1d7a0 100644 --- a/src/Presentation/Nop.Web/Factories/OrderModelFactory.cs +++ b/src/Presentation/Nop.Web/Factories/OrderModelFactory.cs @@ -130,6 +130,9 @@ public virtual CustomerOrderListModel PrepareCustomerOrderListModel() var orderTotalInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderTotal, order.CurrencyRate); orderModel.OrderTotal = _priceFormatter.FormatPrice(orderTotalInCustomerCurrency, true, order.CustomerCurrencyCode, false, _workContext.WorkingLanguage); + var orderTotalAmountInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderTotalAmountIncl, order.CurrencyRate); + orderModel.OrderTotalAmountIncl = _priceFormatter.FormatPrice(orderTotalAmountInCustomerCurrency, true, order.CustomerCurrencyCode, false, _workContext.WorkingLanguage); + model.Orders.Add(orderModel); } @@ -289,7 +292,7 @@ public virtual OrderDetailsModel PrepareOrderDetailsModel(Order order) model.PaymentMethodAdditionalFee = _priceFormatter.FormatPaymentMethodAdditionalFee(paymentMethodAdditionalFeeExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _workContext.WorkingLanguage, false); } - //non taxable + //non taxable if (order.OrderShippingNonTaxable != decimal.Zero) { var orderShippingNonTaxableInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderShippingNonTaxable, order.CurrencyRate); @@ -377,6 +380,9 @@ public virtual OrderDetailsModel PrepareOrderDetailsModel(Order order) var orderTotalInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderTotal, order.CurrencyRate); model.OrderTotal = _priceFormatter.FormatPrice(orderTotalInCustomerCurrency, true, order.CustomerCurrencyCode, false, _workContext.WorkingLanguage); + var orderTotalAmountInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderTotalAmountIncl, order.CurrencyRate); + model.OrderTotalAmountIncl = _priceFormatter.FormatPrice(orderTotalAmountInCustomerCurrency, true, order.CustomerCurrencyCode, false, _workContext.WorkingLanguage); + //checkout attributes model.CheckoutAttributeInfo = order.CheckoutAttributeDescription; @@ -572,7 +578,7 @@ public virtual CustomerRewardPointsModel PrepareCustomerRewardPoints(int? page) //current amount/balance RewardPoints rewardPointsBalance = _rewardPointService.GetRewardPointsBalance(customer.Id, _storeContext.CurrentStore.Id); - decimal rewardPointsAmount = _currencyService.ConvertFromPrimaryStoreCurrency(rewardPointsBalance.Points, _workContext.WorkingCurrency); + decimal rewardPointsAmount = _currencyService.ConvertFromPrimaryStoreCurrency(rewardPointsBalance.Points, _workContext.WorkingCurrency); decimal rewardPointsAmountPurchased = _currencyService.ConvertFromPrimaryStoreCurrency(rewardPointsBalance.PointsPurchased, _workContext.WorkingCurrency); model.RewardPointsBalance = rewardPointsBalance.Points; model.RewardPointsAmount = _priceFormatter.FormatPrice(rewardPointsAmount, true, false); diff --git a/src/Presentation/Nop.Web/Factories/ShoppingCartModelFactory.cs b/src/Presentation/Nop.Web/Factories/ShoppingCartModelFactory.cs index 97dd3c8fa9b..cb85148b905 100644 --- a/src/Presentation/Nop.Web/Factories/ShoppingCartModelFactory.cs +++ b/src/Presentation/Nop.Web/Factories/ShoppingCartModelFactory.cs @@ -1057,6 +1057,10 @@ public virtual OrderTotalsModel PrepareOrderTotalsModel(IList model.OrderAmount = _priceFormatter.FormatPrice(orderAmount, true, true); decimal orderAmountIncl = _currencyService.ConvertFromPrimaryStoreCurrency(taxSummary.TotalAmountIncludingTax, _workContext.WorkingCurrency); model.OrderAmountIncl = _priceFormatter.FormatPrice(orderAmountIncl, true, includingTax); + + decimal orderTotalAmountInCustomerCurrency = _currencyService.ConvertFromPrimaryStoreCurrency(taxSummary.TotalAmountIncludingTax + (taxSummary.TotalPaymentFeeAmountNonTaxable ?? decimal.Zero) + (taxSummary.TotalShippingAmountNonTaxable ?? decimal.Zero), _workContext.WorkingCurrency); + model.OrderTotalAmountIncl = _priceFormatter.FormatPrice(orderTotalAmountInCustomerCurrency, true, includingTax); + } //subtotal diff --git a/src/Presentation/Nop.Web/Models/Order/CustomerOrderListModel.cs b/src/Presentation/Nop.Web/Models/Order/CustomerOrderListModel.cs index 246d08f41da..66bec1e1d71 100644 --- a/src/Presentation/Nop.Web/Models/Order/CustomerOrderListModel.cs +++ b/src/Presentation/Nop.Web/Models/Order/CustomerOrderListModel.cs @@ -25,6 +25,7 @@ public partial class OrderDetailsModel : BaseNopEntityModel { public string CustomOrderNumber { get; set; } public string OrderTotal { get; set; } + public string OrderTotalAmountIncl { get; set; } public bool IsReturnRequestAllowed { get; set; } public OrderStatus OrderStatusEnum { get; set; } public string OrderStatus { get; set; } diff --git a/src/Presentation/Nop.Web/Models/Order/OrderDetailsModel.cs b/src/Presentation/Nop.Web/Models/Order/OrderDetailsModel.cs index 41ddaeff0bb..28082d7e89b 100644 --- a/src/Presentation/Nop.Web/Models/Order/OrderDetailsModel.cs +++ b/src/Presentation/Nop.Web/Models/Order/OrderDetailsModel.cs @@ -88,6 +88,7 @@ public OrderDetailsModel() public string OrderAmount { get; set; } //MF 08.12.16 public string OrderAmountIncl { get; set; } //MF 08.12.16 public string OrderTotalDiscountIncl { get; set; } + public string OrderTotalAmountIncl { get; set; } public string EarnedRewardPointsBaseAmountIncl { get; set; } public string EarnedRewardPointsBaseAmountExcl { get; set; } diff --git a/src/Presentation/Nop.Web/Models/ShoppingCart/OrderTotalsModel.cs b/src/Presentation/Nop.Web/Models/ShoppingCart/OrderTotalsModel.cs index 64913d0ca52..e1d95cb3cb5 100644 --- a/src/Presentation/Nop.Web/Models/ShoppingCart/OrderTotalsModel.cs +++ b/src/Presentation/Nop.Web/Models/ShoppingCart/OrderTotalsModel.cs @@ -49,6 +49,7 @@ public OrderTotalsModel() public string OrderAmount { get; set; } //MF 09.12.16 public string OrderAmountIncl { get; set; } //MF 09.12.16 public string OrderDiscountIncl { get; set; } + public string OrderTotalAmountIncl { get; set; } public string EarnedRewardPointsBaseAmountIncl { get; set; } public string EarnedRewardPointsBaseAmountExcl { get; set; } public bool HasRewardPointsProduct { get; set; } diff --git a/src/Presentation/Nop.Web/Views/Order/CustomerOrders.cshtml b/src/Presentation/Nop.Web/Views/Order/CustomerOrders.cshtml index 09e43128250..dd025e009ff 100644 --- a/src/Presentation/Nop.Web/Views/Order/CustomerOrders.cshtml +++ b/src/Presentation/Nop.Web/Views/Order/CustomerOrders.cshtml @@ -129,7 +129,7 @@
    } } - + @if (Model.Orders.Count > 0) {
    @@ -142,6 +142,7 @@
    • @T("Account.CustomerOrders.OrderStatus"): @order.OrderStatus
    • @T("Account.CustomerOrders.OrderDate"): @order.CreatedOn.ToString()
    • +
    • @T("Account.CustomerOrders.OrderTotalAmountIncl"): @order.OrderTotalAmountIncl
    • @T("Account.CustomerOrders.OrderTotal"): @order.OrderTotal
    diff --git a/src/Presentation/Nop.Web/Views/Order/Details.cshtml b/src/Presentation/Nop.Web/Views/Order/Details.cshtml index 6cb2acabded..ce9d973a56c 100644 --- a/src/Presentation/Nop.Web/Views/Order/Details.cshtml +++ b/src/Presentation/Nop.Web/Views/Order/Details.cshtml @@ -47,6 +47,9 @@
  • @T("Order.OrderStatus"): @Model.OrderStatus
  • +
  • + @T("Order.OrderTotalAmountIncl"): @Model.OrderTotalAmountIncl +
  • @T("Order.OrderTotal"): @Model.OrderTotal
  • @@ -791,6 +794,14 @@
    - /// replace anchor text (remove a tag from the following url Name and output only the string "Name") - ///
    + + + @Model.OrderTotalAmountIncl +
    diff --git a/src/Tests/Nop.Data.Tests/Orders/OrderPersistenceTests.cs b/src/Tests/Nop.Data.Tests/Orders/OrderPersistenceTests.cs index 354f07fc78a..08c74a7c7b7 100644 --- a/src/Tests/Nop.Data.Tests/Orders/OrderPersistenceTests.cs +++ b/src/Tests/Nop.Data.Tests/Orders/OrderPersistenceTests.cs @@ -1,130 +1,130 @@ -using System.Linq; -using Nop.Tests; -using NUnit.Framework; - -namespace Nop.Data.Tests.Orders -{ - [TestFixture] - public class OrderPersistenceTests : PersistenceTest - { - [Test] - public void Can_save_and_load_order() - { - var order = this.GetTestOrder(); - - order.Customer = this.GetTestCustomer(); - order.PickupAddress = this.GetTestAddress(); - - var fromDb = SaveAndLoadEntity(order); - fromDb.ShouldNotBeNull(); - fromDb.PropertiesShouldEqual(this.GetTestOrder()); - fromDb.Customer.PropertiesShouldEqual(this.GetTestCustomer()); - fromDb.PickupAddress.PropertiesShouldEqual(this.GetTestAddress()); - fromDb.BillingAddress.PropertiesShouldEqual(this.GetTestAddress()); - } - - [Test] - public void Can_save_and_load_order_with_shipping_address() - { - var order = this.GetTestOrder(); - - order.Customer = this.GetTestCustomer(); - order.PickupAddress = this.GetTestAddress(); - order.ShippingAddress = this.GetTestAddress(); - - var fromDb = SaveAndLoadEntity(order); - fromDb.ShouldNotBeNull(); - fromDb.ShippingAddress.ShouldNotBeNull(); - fromDb.ShippingAddress.PropertiesShouldEqual(this.GetTestAddress()); - } - - [Test] - public void Can_save_and_load_order_with_usedRewardPoints() - { - var order = this.GetTestOrder(); - order.RedeemedRewardPointsEntry = this.GetTestRewardPointsHistory(); - order.RedeemedRewardPointsEntry.UsedWithOrder = order; - - var fromDb = SaveAndLoadEntity(order); - fromDb.ShouldNotBeNull(); - fromDb.PropertiesShouldEqual(this.GetTestOrder()); - - fromDb.RedeemedRewardPointsEntry.ShouldNotBeNull(); - fromDb.RedeemedRewardPointsEntry.PropertiesShouldEqual(this.GetTestRewardPointsHistory()); - } - - [Test] - public void Can_save_and_load_order_with_discountUsageHistory() - { - var order = this.GetTestOrder(); - var discountUsageHistory = this.GetTestDiscountUsageHistory(); - order.Customer = this.GetTestCustomer(); - discountUsageHistory.Order = order; - order.DiscountUsageHistory.Add(discountUsageHistory); - - var fromDb = SaveAndLoadEntity(order); - fromDb.ShouldNotBeNull(); - fromDb.PropertiesShouldEqual(this.GetTestOrder()); - - fromDb.DiscountUsageHistory.ShouldNotBeNull(); - fromDb.DiscountUsageHistory.First().PropertiesShouldEqual(this.GetTestDiscountUsageHistory()); - } - - [Test] - public void Can_save_and_load_order_with_giftCardUsageHistory() - { - var order = this.GetTestOrder(); - order.GiftCardUsageHistory.Add(this.GetTestGiftCardUsageHistory()); - order.Customer = this.GetTestCustomer(); - var fromDb = SaveAndLoadEntity(order); - fromDb.ShouldNotBeNull(); - - fromDb.GiftCardUsageHistory.ShouldNotBeNull(); - fromDb.GiftCardUsageHistory.Count.ShouldEqual(1); - fromDb.GiftCardUsageHistory.First().PropertiesShouldEqual(this.GetTestGiftCardUsageHistory()); - } - - [Test] - public void Can_save_and_load_order_with_orderNotes() - { - var order = this.GetTestOrder(); - order.OrderNotes.Add(this.GetTestOrderNote()); - order.Customer = this.GetTestCustomer(); - var fromDb = SaveAndLoadEntity(order); - fromDb.ShouldNotBeNull(); - - fromDb.OrderNotes.ShouldNotBeNull(); - fromDb.OrderNotes.Count.ShouldEqual(1); - fromDb.OrderNotes.First().PropertiesShouldEqual(this.GetTestOrderNote()); - } - - [Test] - public void Can_save_and_load_order_with_orderItems() - { - var order = this.GetTestOrder(); - order.OrderItems.Add(this.GetTestOrderItem()); - order.Customer = this.GetTestCustomer(); - var fromDb = SaveAndLoadEntity(order); - fromDb.ShouldNotBeNull(); - - fromDb.OrderItems.ShouldNotBeNull(); - fromDb.OrderItems.Count.ShouldEqual(1); - fromDb.OrderItems.First().PropertiesShouldEqual(this.GetTestOrderItem()); - } - - [Test] - public void Can_save_and_load_order_with_shipments() - { - var order = this.GetTestOrder(); - order.Shipments.Add(this.GetTestShipment()); - order.Shipments.First().Order = order; - order.Customer = this.GetTestCustomer(); - var fromDb = SaveAndLoadEntity(order); - fromDb.ShouldNotBeNull(); - - fromDb.Shipments.ShouldNotBeNull(); - fromDb.Shipments.Count.ShouldEqual(1); - fromDb.Shipments.First().PropertiesShouldEqual(this.GetTestShipment()); - } - } -} +using System.Linq; +using Nop.Tests; +using NUnit.Framework; + +namespace Nop.Data.Tests.Orders +{ + [TestFixture] + public class OrderPersistenceTests : PersistenceTest + { + [Test] + public void Can_save_and_load_order() + { + var order = this.GetTestOrder(); + + order.Customer = this.GetTestCustomer(); + order.PickupAddress = this.GetTestAddress(); + + var fromDb = SaveAndLoadEntity(order); + fromDb.ShouldNotBeNull(); + fromDb.PropertiesShouldEqual(this.GetTestOrder()); + fromDb.Customer.PropertiesShouldEqual(this.GetTestCustomer()); + fromDb.PickupAddress.PropertiesShouldEqual(this.GetTestAddress()); + fromDb.BillingAddress.PropertiesShouldEqual(this.GetTestAddress()); + } + + [Test] + public void Can_save_and_load_order_with_shipping_address() + { + var order = this.GetTestOrder(); + + order.Customer = this.GetTestCustomer(); + order.PickupAddress = this.GetTestAddress(); + order.ShippingAddress = this.GetTestAddress(); + + var fromDb = SaveAndLoadEntity(order); + fromDb.ShouldNotBeNull(); + fromDb.ShippingAddress.ShouldNotBeNull(); + fromDb.ShippingAddress.PropertiesShouldEqual(this.GetTestAddress()); + } + + [Test] + public void Can_save_and_load_order_with_usedRewardPoints() + { + var order = this.GetTestOrder(); + order.RedeemedRewardPointsEntry = this.GetTestRewardPointsHistory(); + order.RedeemedRewardPointsEntry.UsedWithOrder = order; + + var fromDb = SaveAndLoadEntity(order); + fromDb.ShouldNotBeNull(); + fromDb.PropertiesShouldEqual(this.GetTestOrder()); + + fromDb.RedeemedRewardPointsEntry.ShouldNotBeNull(); + fromDb.RedeemedRewardPointsEntry.PropertiesShouldEqual(this.GetTestRewardPointsHistory()); + } + + [Test] + public void Can_save_and_load_order_with_discountUsageHistory() + { + var order = this.GetTestOrder(); + var discountUsageHistory = this.GetTestDiscountUsageHistory(); + order.Customer = this.GetTestCustomer(); + discountUsageHistory.Order = order; + order.DiscountUsageHistory.Add(discountUsageHistory); + + var fromDb = SaveAndLoadEntity(order); + fromDb.ShouldNotBeNull(); + fromDb.PropertiesShouldEqual(this.GetTestOrder()); + + fromDb.DiscountUsageHistory.ShouldNotBeNull(); + fromDb.DiscountUsageHistory.First().PropertiesShouldEqual(this.GetTestDiscountUsageHistory()); + } + + [Test] + public void Can_save_and_load_order_with_giftCardUsageHistory() + { + var order = this.GetTestOrder(); + order.GiftCardUsageHistory.Add(this.GetTestGiftCardUsageHistory()); + order.Customer = this.GetTestCustomer(); + var fromDb = SaveAndLoadEntity(order); + fromDb.ShouldNotBeNull(); + + fromDb.GiftCardUsageHistory.ShouldNotBeNull(); + fromDb.GiftCardUsageHistory.Count.ShouldEqual(1); + fromDb.GiftCardUsageHistory.First().PropertiesShouldEqual(this.GetTestGiftCardUsageHistory()); + } + + [Test] + public void Can_save_and_load_order_with_orderNotes() + { + var order = this.GetTestOrder(); + order.OrderNotes.Add(this.GetTestOrderNote()); + order.Customer = this.GetTestCustomer(); + var fromDb = SaveAndLoadEntity(order); + fromDb.ShouldNotBeNull(); + + fromDb.OrderNotes.ShouldNotBeNull(); + fromDb.OrderNotes.Count.ShouldEqual(1); + fromDb.OrderNotes.First().PropertiesShouldEqual(this.GetTestOrderNote()); + } + + [Test] + public void Can_save_and_load_order_with_orderItems() + { + var order = this.GetTestOrder(); + order.OrderItems.Add(this.GetTestOrderItem()); + order.Customer = this.GetTestCustomer(); + var fromDb = SaveAndLoadEntity(order); + fromDb.ShouldNotBeNull(); + + fromDb.OrderItems.ShouldNotBeNull(); + fromDb.OrderItems.Count.ShouldEqual(1); + fromDb.OrderItems.First().PropertiesShouldEqual(this.GetTestOrderItem()); + } + + [Test] + public void Can_save_and_load_order_with_shipments() + { + var order = this.GetTestOrder(); + order.Shipments.Add(this.GetTestShipment()); + order.Shipments.First().Order = order; + order.Customer = this.GetTestCustomer(); + var fromDb = SaveAndLoadEntity(order); + fromDb.ShouldNotBeNull(); + + fromDb.Shipments.ShouldNotBeNull(); + fromDb.Shipments.Count.ShouldEqual(1); + fromDb.Shipments.First().PropertiesShouldEqual(this.GetTestShipment()); + } + } +} diff --git a/upgradescripts/3.80-the next version/upgrade.sql b/upgradescripts/3.80-the next version/upgrade.sql index c22005b560e..1ff943de9cb 100644 --- a/upgradescripts/3.80-the next version/upgrade.sql +++ b/upgradescripts/3.80-the next version/upgrade.sql @@ -152,6 +152,18 @@ set @resources=' Invoice Date + + Order total to pay + + + The total amount to pay for this order (includes discounts, shipping and tax, reward points, gift cards). + + + Order total amount + + + The total amount for this order (includes discounts, shipping and tax). + Amount @@ -205,8 +217,14 @@ set @resources=' Amount incl. Tax - - + + + Order total to pay + + + Edit the total amount to pay for this order. + + Earned reward points base amount incl. Tax @@ -3831,6 +3849,21 @@ IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and N ALTER TABLE [Order] ALTER COLUMN EarnedRewardPointsBaseAmountExcl DECIMAL(18,4) NOT NULL GO +--add a new column +IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='RewardPointsTaxable') +BEGIN + ALTER TABLE [dbo].[Order] + ADD RewardPointsTaxable bit NULL; +END +GO +IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='RewardPointsTaxable') +UPDATE [dbo].[Order] + SET RewardPointsTaxable = 0 + WHERE RewardPointsTaxable IS NULL +GO +IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id=object_id('[Order]') and NAME='RewardPointsTaxable') +ALTER TABLE [Order] ALTER COLUMN RewardPointsTaxable bit NOT NULL +GO --a stored procedure update IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[ProductLoadAllPaged]') AND OBJECTPROPERTY(object_id,N'IsProcedure') = 1) DROP PROCEDURE [ProductLoadAllPaged] From 20a97dbee72ce23a07b7b2609d95259ae9847029 Mon Sep 17 00:00:00 2001 From: Markus Falk Date: Fri, 24 Feb 2017 08:09:16 +0100 Subject: [PATCH 10/13] PfdService 7 and OrderProcService AssignRewardPoints fix, Ressource string update --- src/Libraries/Nop.Services/Common/PdfService7.cs | 9 +++++---- .../Nop.Services/Orders/OrderProcessingService.cs | 2 +- .../App_Data/Localization/defaultResources.nopres.xml | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Libraries/Nop.Services/Common/PdfService7.cs b/src/Libraries/Nop.Services/Common/PdfService7.cs index ed1d4adf3d2..8a0f5b5e482 100644 --- a/src/Libraries/Nop.Services/Common/PdfService7.cs +++ b/src/Libraries/Nop.Services/Common/PdfService7.cs @@ -566,9 +566,10 @@ public virtual void PrintOrdersToPdf(Stream stream, IList orders, int lan //empty Line tabPage.AddHeaderCell(new Cell(1, col).Add("\n").AddStyle(styleCell)); //Invoice - paraPdf = new Paragraph(new Text(_localizationService.GetResource(order.InvoiceId != null? "PDFInvoice.Invoice" :"PDFInvoice.Order", lang.Id)).AddStyle(styleTitle)); - if (order.InvoiceId != null) - paraPdf.Add(new Text(" " + order.InvoiceId).AddStyle(styleTitle)); + var invoiceID = (order.InvoiceId != null && order.InvoiceId != "ToBeAssigned") ? order.InvoiceId : null; + paraPdf = new Paragraph(new Text(_localizationService.GetResource(invoiceID != null? "PDFInvoice.Invoice" :"PDFInvoice.Order", lang.Id)).AddStyle(styleTitle)); + if (invoiceID != null) + paraPdf.Add(new Text(" " + invoiceID).AddStyle(styleTitle)); tabPage.AddHeaderCell(new Cell(1, col - 3).Add(paraPdf).AddStyle(styleCell)); paraPdf = new Paragraph(); if (order.InvoiceDateUtc.HasValue) @@ -852,7 +853,7 @@ public virtual void PrintOrdersToPdf(Stream stream, IList orders, int lan } //order total incl. lstSummary.Add(new Tuple("PDFInvoice.OrderAmountIncl", order.OrderAmountIncl, true, true, false, false)); - + //shipping non taxable var shippTuple = Tuple.Create("PDFInvoice.Shipping", order.OrderShippingNonTaxable, false, false, order.ShippingStatus != ShippingStatus.ShippingNotRequired, false); lstSummary.Add(shippTuple); diff --git a/src/Libraries/Nop.Services/Orders/OrderProcessingService.cs b/src/Libraries/Nop.Services/Orders/OrderProcessingService.cs index db202e09cba..bb9eea1ee6e 100644 --- a/src/Libraries/Nop.Services/Orders/OrderProcessingService.cs +++ b/src/Libraries/Nop.Services/Orders/OrderProcessingService.cs @@ -835,7 +835,7 @@ protected virtual void AwardRewardPoints(Order order) var points = new RewardPoints(_rewardPointService); //earned points - decimal usedPurchasedRewardPointsAmount = order.RedeemedRewardPointsEntry.UsedAmountPurchased; + decimal usedPurchasedRewardPointsAmount = order.RedeemedRewardPointsEntry != null ? order.RedeemedRewardPointsEntry.UsedAmountPurchased : decimal.Zero; bool includingTax = order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax; decimal amount = _rewardPointService.GetRewardPointsBaseAmount(includingTax ? order.EarnedRewardPointsBaseAmountIncl : order.EarnedRewardPointsBaseAmountExcl, usedPurchasedRewardPointsAmount); diff --git a/src/Presentation/Nop.Web/App_Data/Localization/defaultResources.nopres.xml b/src/Presentation/Nop.Web/App_Data/Localization/defaultResources.nopres.xml index db7239a2441..77a5f82b0c6 100644 --- a/src/Presentation/Nop.Web/App_Data/Localization/defaultResources.nopres.xml +++ b/src/Presentation/Nop.Web/App_Data/Localization/defaultResources.nopres.xml @@ -6787,7 +6787,7 @@ Assign Invoice ID from Task - Assign invoice id using background task. This assures that the same invoice id isn't used more than ones and that there are no wholes in the number series. + Assign invoice id using background task. This assures that the same invoice id isn't used more than ones and that there are no wholes in the number series. Please note: Activate the task manually in System/Schedule tasks. Auto update order totals From b42efc19b8290ded777650df1e09ab802d687385 Mon Sep 17 00:00:00 2001 From: Markus Falk Date: Sat, 25 Feb 2017 00:51:38 +0100 Subject: [PATCH 11/13] Fixed Ressource string --- upgradescripts/3.80-the next version/upgrade.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/upgradescripts/3.80-the next version/upgrade.sql b/upgradescripts/3.80-the next version/upgrade.sql index 1ff943de9cb..33b04cc831a 100644 --- a/upgradescripts/3.80-the next version/upgrade.sql +++ b/upgradescripts/3.80-the next version/upgrade.sql @@ -318,7 +318,7 @@ set @resources=' Last used Invoice No. - Invoice ID counter. This is useful if you want your invoices to start from certain number. This only affects invoices henceforward. The value must be greater than the current maximum invoice ID. + Invoice ID counter. This is useful if you want your invoices to start from certain number. This only affects invoices henceforward. The value must be greater than the current maximum invoice ID. Please note: Activate the task manually in System/Schedule tasks. Year of last invoice From a1fd7d5be267ae7891c3221c775b6b762b84a270 Mon Sep 17 00:00:00 2001 From: Markus Falk Date: Sat, 11 Mar 2017 23:09:23 +0100 Subject: [PATCH 12/13] git-setting --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6e7d108a868..b711fdcaea2 100644 --- a/.gitignore +++ b/.gitignore @@ -99,7 +99,7 @@ publish/ # NuGet Packages Directory ## TODO: If you have NuGet Package Restore enabled, uncomment the next line -#packages/ +packages/ # Windows Azure Build Output csx From 97197a2617e3761a9d6146715dfd158059b57b54 Mon Sep 17 00:00:00 2001 From: Markus Falk Date: Wed, 1 Mar 2017 18:44:22 +0100 Subject: [PATCH 13/13] Vat Rounding: GetUnitPrice fix for attribute combinations --- .../Catalog/PriceCalculationService.cs | 44 +++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/src/Libraries/Nop.Services/Catalog/PriceCalculationService.cs b/src/Libraries/Nop.Services/Catalog/PriceCalculationService.cs index 87dc2e54fa5..d2aa285daa9 100644 --- a/src/Libraries/Nop.Services/Catalog/PriceCalculationService.cs +++ b/src/Libraries/Nop.Services/Catalog/PriceCalculationService.cs @@ -537,33 +537,31 @@ public virtual decimal GetUnitPrice(Product product, //needed for VAT calculation of product bundles //taxrate comes from associated product and not from product decimal attributesTotalPrice = decimal.Zero; - if (combination != null) + var attributeValues = _productAttributeParser.ParseProductAttributeValues(attributesXml); + if (attributeValues != null) { - var attributeValues = _productAttributeParser.ParseProductAttributeValues(attributesXml); - if (attributeValues != null) + decimal taxRate; + foreach (var attributeValue in attributeValues) { - decimal taxRate; - foreach (var attributeValue in attributeValues) - { - var attributePrice = GetProductAttributeValuePriceAdjustment(attributeValue); - attributesTotalPrice += attributePrice; + var attributePrice = GetProductAttributeValuePriceAdjustment(attributeValue); + attributesTotalPrice += attributePrice; - if (attributeValue.AttributeValueType == AttributeValueType.AssociatedToProduct) + if (attributeValue.AttributeValueType == AttributeValueType.AssociatedToProduct) + { + //bundled product + var associatedProduct = _productService.GetProductById(attributeValue.AssociatedProductId); + if (associatedProduct != null) { - //bundled product - var associatedProduct = _productService.GetProductById(attributeValue.AssociatedProductId); - if (associatedProduct != null) - { - //get only the taxrate of associate product, price is not needed - var attributePriceTax = _taxService.GetProductPrice(associatedProduct, 0, customer, out taxRate); - //build taxSummary for tax subdivision - taxWeightSummary.AddRate(taxRate, attributePrice); - } + //get only the taxrate of associate product, price is not needed + var attributePriceTax = _taxService.GetProductPrice(associatedProduct, 0, customer, out taxRate); + //build taxSummary for tax subdivision + taxWeightSummary.AddRate(taxRate, attributePrice); } } } } + if (combination != null && combination.OverriddenPrice.HasValue) { finalPrice = GetFinalPrice(product, @@ -618,17 +616,17 @@ public virtual decimal GetUnitPrice(Product product, if (_shoppingCartSettings.RoundPricesDuringCalculation) finalPrice = RoundingHelper.RoundPrice(finalPrice); - //add tax attributes + //add tax attributes if needed if (taxWeightSummary != null && taxWeightSummary.TaxRates.Any()) { decimal prodcutTaxRate; - //get taxrate, no price needed - var itemAmount = _taxService.GetProductPrice(product, 0, customer, out prodcutTaxRate); + //get taxrate and product price + var productPrice = _taxService.GetProductPrice(product, product.Price, customer, out prodcutTaxRate); //price is overridden or it is product price + attributes price - if (combination != null && !combination.OverriddenPrice.HasValue) + if (combination == null || combination != null && !combination.OverriddenPrice.HasValue) { - var baseProductPrice = finalPrice - attributesTotalPrice; + var baseProductPrice = productPrice; // finalPrice - attributesTotalPrice; //add product price if exists if (baseProductPrice > decimal.Zero) taxWeightSummary.AddRate(prodcutTaxRate, baseProductPrice);