Try your search with a different keyword or use * as a wildcard.
using System.Globalization;
using System.Xml;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using Nop.Core;
using Nop.Core.Domain.Catalog;
using Nop.Data;
using Nop.Services.Directory;
using Nop.Services.Localization;
using Nop.Services.Media;
namespace Nop.Services.Catalog;
///
/// Product attribute parser
///
public partial class ProductAttributeParser : IProductAttributeParser
{
#region Fields
protected readonly ICurrencyService _currencyService;
protected readonly IDownloadService _downloadService;
protected readonly ILocalizationService _localizationService;
protected readonly IProductAttributeService _productAttributeService;
protected readonly IRepository _productAttributeValueRepository;
protected readonly IWorkContext _workContext;
private static readonly char[] _separator = [','];
#endregion
#region Ctor
public ProductAttributeParser(ICurrencyService currencyService,
IDownloadService downloadService,
ILocalizationService localizationService,
IProductAttributeService productAttributeService,
IRepository productAttributeValueRepository,
IWorkContext workContext)
{
_currencyService = currencyService;
_downloadService = downloadService;
_productAttributeService = productAttributeService;
_productAttributeValueRepository = productAttributeValueRepository;
_workContext = workContext;
_localizationService = localizationService;
}
#endregion
#region Utilities
///
/// Returns a list which contains all possible combinations of elements
///
/// Type of element
/// Elements to make combinations
/// All possible combinations of elements
protected virtual IList> CreateCombination(IList elements)
{
var rez = new List>();
for (var i = 1; i < Math.Pow(2, elements.Count); i++)
{
var current = new List();
var index = -1;
//transform int to binary string
var binaryMask = Convert.ToString(i, 2).PadLeft(elements.Count, '0');
foreach (var flag in binaryMask)
{
index++;
if (flag == '0')
continue;
//add element if binary mask in the position of element has 1
current.Add(elements[index]);
}
rez.Add(current);
}
return rez;
}
///
/// 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?["ID"] == null)
continue;
if (!int.TryParse(attributeNode.Attributes["ID"].InnerText.Trim(), out var attributeId) ||
attributeId != productAttributeMappingId)
continue;
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
{
// ignored
}
return selectedValues;
}
///
/// Adds gift cards attributes in XML format
///
/// Product
/// Form
/// Attributes in XML format
protected virtual void AddGiftCardsAttributesXml(Product product, IFormCollection form, ref string attributesXml)
{
if (!product.IsGiftCard)
return;
var recipientName = "";
var recipientEmail = "";
var senderName = "";
var senderEmail = "";
var giftCardMessage = "";
foreach (var formKey in form.Keys)
{
if (formKey.Equals($"giftcard_{product.Id}.RecipientName", StringComparison.InvariantCultureIgnoreCase))
{
recipientName = form[formKey];
continue;
}
if (formKey.Equals($"giftcard_{product.Id}.RecipientEmail", StringComparison.InvariantCultureIgnoreCase))
{
recipientEmail = form[formKey];
continue;
}
if (formKey.Equals($"giftcard_{product.Id}.SenderName", StringComparison.InvariantCultureIgnoreCase))
{
senderName = form[formKey];
continue;
}
if (formKey.Equals($"giftcard_{product.Id}.SenderEmail", StringComparison.InvariantCultureIgnoreCase))
{
senderEmail = form[formKey];
continue;
}
if (formKey.Equals($"giftcard_{product.Id}.Message", StringComparison.InvariantCultureIgnoreCase))
{
giftCardMessage = form[formKey];
}
}
attributesXml = AddGiftCardAttribute(attributesXml, recipientName, recipientEmail, senderName, senderEmail, giftCardMessage);
}
///
/// Gets product attributes in XML format
///
/// Product
/// Form
/// Errors
///
/// A task that represents the asynchronous operation
/// The task result contains the attributes in XML format
///
protected virtual async Task GetProductAttributesXmlAsync(Product product, IFormCollection form, List errors)
{
var attributesXml = string.Empty;
var productAttributes = await _productAttributeService.GetProductAttributeMappingsByProductIdAsync(product.Id);
foreach (var attribute in productAttributes)
{
var controlId = $"{NopCatalogDefaults.ProductAttributePrefix}{attribute.Id}";
switch (attribute.AttributeControlType)
{
case AttributeControlType.DropdownList:
case AttributeControlType.RadioList:
case AttributeControlType.ColorSquares:
case AttributeControlType.ImageSquares:
{
var ctrlAttributes = form[controlId];
if (!StringValues.IsNullOrEmpty(ctrlAttributes))
{
var selectedAttributeId = int.Parse(ctrlAttributes);
if (selectedAttributeId > 0)
{
//get quantity entered by customer
var quantity = 1;
var quantityStr = form[$"{NopCatalogDefaults.ProductAttributePrefix}{attribute.Id}_{selectedAttributeId}_qty"];
if (!StringValues.IsNullOrEmpty(quantityStr) &&
(!int.TryParse(quantityStr, out quantity) || quantity < 1))
errors.Add(await _localizationService.GetResourceAsync("Products.QuantityShouldBePositive"));
attributesXml = AddProductAttribute(attributesXml,
attribute, selectedAttributeId.ToString(), quantity > 1 ? (int?)quantity : null);
}
}
}
break;
case AttributeControlType.Checkboxes:
{
var ctrlAttributes = form[controlId];
if (!StringValues.IsNullOrEmpty(ctrlAttributes))
{
foreach (var item in ctrlAttributes.ToString()
.Split(_separator, StringSplitOptions.RemoveEmptyEntries))
{
var selectedAttributeId = int.Parse(item);
if (selectedAttributeId > 0)
{
//get quantity entered by customer
var quantity = 1;
var quantityStr = form[$"{NopCatalogDefaults.ProductAttributePrefix}{attribute.Id}_{item}_qty"];
if (!StringValues.IsNullOrEmpty(quantityStr) &&
(!int.TryParse(quantityStr, out quantity) || quantity < 1))
errors.Add(await _localizationService.GetResourceAsync("Products.QuantityShouldBePositive"));
attributesXml = AddProductAttribute(attributesXml,
attribute, selectedAttributeId.ToString(), quantity > 1 ? (int?)quantity : null);
}
}
}
}
break;
case AttributeControlType.ReadonlyCheckboxes:
{
//load read-only (already server-side selected) values
var attributeValues = await _productAttributeService.GetProductAttributeValuesAsync(attribute.Id);
foreach (var selectedAttributeId in attributeValues
.Where(v => v.IsPreSelected)
.Select(v => v.Id)
.ToList())
{
//get quantity entered by customer
var quantity = 1;
var quantityStr = form[$"{NopCatalogDefaults.ProductAttributePrefix}{attribute.Id}_{selectedAttributeId}_qty"];
if (!StringValues.IsNullOrEmpty(quantityStr) &&
(!int.TryParse(quantityStr, out quantity) || quantity < 1))
errors.Add(await _localizationService.GetResourceAsync("Products.QuantityShouldBePositive"));
attributesXml = AddProductAttribute(attributesXml,
attribute, selectedAttributeId.ToString(), quantity > 1 ? (int?)quantity : null);
}
}
break;
case AttributeControlType.TextBox:
case AttributeControlType.MultilineTextbox:
{
var ctrlAttributes = form[controlId];
if (!StringValues.IsNullOrEmpty(ctrlAttributes))
{
var enteredText = ctrlAttributes.ToString().Trim();
attributesXml = AddProductAttribute(attributesXml, attribute, enteredText);
}
}
break;
case AttributeControlType.Datepicker:
{
var day = form[controlId + "_day"];
var month = form[controlId + "_month"];
var year = form[controlId + "_year"];
DateTime? selectedDate = null;
try
{
selectedDate = new DateTime(int.Parse(year), int.Parse(month), int.Parse(day));
}
catch
{
// ignored
}
if (selectedDate.HasValue)
attributesXml = AddProductAttribute(attributesXml, attribute, selectedDate.Value.ToString("D"));
}
break;
case AttributeControlType.FileUpload:
{
_ = Guid.TryParse(form[controlId], out var downloadGuid);
var download = await _downloadService.GetDownloadByGuidAsync(downloadGuid);
if (download != null)
attributesXml = AddProductAttribute(attributesXml,
attribute, download.DownloadGuid.ToString());
}
break;
default:
break;
}
}
//validate conditional attributes (if specified)
foreach (var attribute in productAttributes)
{
var conditionMet = await IsConditionMetAsync(attribute, attributesXml);
if (conditionMet.HasValue && !conditionMet.Value)
{
attributesXml = RemoveProductAttribute(attributesXml, attribute);
}
}
return attributesXml;
}
///
/// Remove an attribute
///
/// Attributes in XML format
/// Attribute value id
/// Updated result (XML format)
protected virtual string RemoveAttribute(string attributesXml, int attributeValueId)
{
var result = string.Empty;
if (string.IsNullOrEmpty(attributesXml))
return string.Empty;
try
{
var xmlDoc = new XmlDocument();
xmlDoc.LoadXml(attributesXml);
var rootElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes");
if (rootElement == null)
return string.Empty;
XmlElement attributeElement = null;
//find existing
var childNodes = xmlDoc.SelectNodes($@"//Attributes/{ChildElementName}");
if (childNodes == null)
return string.Empty;
var count = childNodes.Count;
foreach (XmlElement childNode in childNodes)
{
if (!int.TryParse(childNode.Attributes["ID"]?.InnerText.Trim(), out var id))
continue;
if (id != attributeValueId)
continue;
attributeElement = childNode;
break;
}
//found
if (attributeElement != null)
{
rootElement.RemoveChild(attributeElement);
count -= 1;
}
result = count == 0 ? string.Empty : xmlDoc.OuterXml;
}
catch
{
//ignore
}
return result;
}
///
/// Gets selected attribute identifiers
///
/// Attributes in XML format
/// Selected attribute identifiers
protected virtual IList ParseAttributeIds(string attributesXml)
{
var ids = new List();
if (string.IsNullOrEmpty(attributesXml))
return ids;
try
{
var xmlDoc = new XmlDocument();
xmlDoc.LoadXml(attributesXml);
var elements = xmlDoc.SelectNodes(@$"//Attributes/{ChildElementName}");
if (elements == null)
return Array.Empty();
foreach (XmlNode node in elements)
{
if (node.Attributes?["ID"] == null)
continue;
var attributeValue = node.Attributes["ID"].InnerText.Trim();
if (int.TryParse(attributeValue, out var id))
ids.Add(id);
}
}
catch
{
//ignore
}
return ids;
}
#endregion
#region Product attributes
///
/// Gets selected product attribute mappings
///
/// Attributes in XML format
///
/// A task that represents the asynchronous operation
/// The task result contains the selected product attribute mappings
///
public virtual async Task> ParseProductAttributeMappingsAsync(string attributesXml)
{
var result = new List();
if (string.IsNullOrEmpty(attributesXml))
return result;
var ids = ParseAttributeIds(attributesXml);
foreach (var id in ids)
{
var attribute = await _productAttributeService.GetProductAttributeMappingByIdAsync(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
///
/// A task that represents the asynchronous operation
/// The task result contains the product attribute values
///
public virtual async Task> ParseProductAttributeValuesAsync(string attributesXml, int productAttributeMappingId = 0)
{
var values = new List();
if (string.IsNullOrEmpty(attributesXml))
return values;
var attributes = await ParseProductAttributeMappingsAsync(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))
{
if (string.IsNullOrEmpty(attributeValue.Item1) || !int.TryParse(attributeValue.Item1, out var attributeValueId))
continue;
var value = await _productAttributeService.GetProductAttributeValueByIdAsync(attributeValueId);
if (value == null)
continue;
if (!string.IsNullOrEmpty(attributeValue.Item2) && int.TryParse(attributeValue.Item2, out var quantity) && quantity != value.Quantity)
{
//if customer enters quantity, use new entity with new quantity
var oldValue = await _productAttributeValueRepository.LoadOriginalCopyAsync(value);
oldValue.ProductAttributeMappingId = attribute.Id;
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?["ID"] == null)
continue;
var str1 = node1.Attributes["ID"].InnerText.Trim();
if (!int.TryParse(str1, out var id))
continue;
if (id != productAttributeMappingId)
continue;
var nodeList2 = node1.SelectNodes(@"ProductAttributeValue/Value");
foreach (XmlNode node2 in nodeList2)
{
var value = node2.InnerText.Trim();
selectedValues.Add(value);
}
}
}
catch
{
//ignore
}
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)
{
var 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?["ID"] == null)
continue;
var str1 = node1.Attributes["ID"].InnerText.Trim();
if (!int.TryParse(str1, out var id))
continue;
if (id != productAttributeMapping.Id)
continue;
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
{
//ignore
}
return result;
}
///
/// Remove an attribute
///
/// Attributes in XML format
/// Product attribute mapping
/// Updated result (XML format)
public virtual string RemoveProductAttribute(string attributesXml, ProductAttributeMapping productAttributeMapping)
{
return RemoveAttribute(attributesXml, productAttributeMapping.Id);
}
///
/// 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
///
/// A task that represents the asynchronous operation
/// The task result contains the result
///
public virtual async Task AreProductAttributesEqualAsync(string attributesXml1, string attributesXml2, bool ignoreNonCombinableAttributes, bool ignoreQuantity = true)
{
var attributes1 = await ParseProductAttributeMappingsAsync(attributesXml1);
if (ignoreNonCombinableAttributes)
attributes1 = attributes1.Where(x => !x.IsNonCombinable()).ToList();
var attributes2 = await ParseProductAttributeMappingsAsync(attributesXml2);
if (ignoreNonCombinableAttributes)
attributes2 = attributes2.Where(x => !x.IsNonCombinable()).ToList();
if (attributes1.Count != attributes2.Count)
return false;
var attributesEqual = true;
foreach (var a1 in attributes1)
{
var hasAttribute = false;
foreach (var a2 in attributes2)
{
if (a1.Id != a2.Id)
continue;
hasAttribute = true;
var values1Str = ParseValuesWithQuantity(attributesXml1, a1.Id);
var values2Str = ParseValuesWithQuantity(attributesXml2, a2.Id);
if (values1Str.Count == values2Str.Count)
{
foreach (var str1 in values1Str)
{
var hasValue = false;
foreach (var str2 in values2Str)
{
//case insensitive?
//if (str1.Trim().ToLowerInvariant() == str2.Trim().ToLowerInvariant())
if (str1.Item1.Trim() != str2.Item1.Trim())
continue;
hasValue = ignoreQuantity || str1.Item2.Trim() == str2.Item2.Trim();
break;
}
if (hasValue)
continue;
attributesEqual = false;
break;
}
}
else
{
attributesEqual = false;
break;
}
}
if (hasAttribute)
continue;
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)
///
/// A task that represents the asynchronous operation
/// The task result contains the result
///
public virtual async Task IsConditionMetAsync(ProductAttributeMapping pam, string selectedAttributesXml)
{
ArgumentNullException.ThrowIfNull(pam);
var conditionAttributeXml = pam.ConditionAttributeXml;
if (string.IsNullOrEmpty(conditionAttributeXml))
//no condition
return null;
//load an attribute this one depends on
var dependOnAttribute = (await ParseProductAttributeMappingsAsync(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)
{
var 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
///
/// A task that represents the asynchronous operation
/// The task result contains the found product attribute combination
///
public virtual async Task FindProductAttributeCombinationAsync(Product product,
string attributesXml, bool ignoreNonCombinableAttributes = true)
{
ArgumentNullException.ThrowIfNull(product);
//anyway combination cannot contains non combinable attributes
if (string.IsNullOrEmpty(attributesXml))
return null;
var combinations = await _productAttributeService.GetAllProductAttributeCombinationsAsync(product.Id);
return await combinations.FirstOrDefaultAwaitAsync(async x =>
await AreProductAttributesEqualAsync(x.AttributesXml, attributesXml, ignoreNonCombinableAttributes));
}
///
/// Generate all combinations
///
/// Product
/// A value indicating whether we should ignore non-combinable attributes
/// List of allowed attribute identifiers. If null or empty then all attributes would be used.
///
/// A task that represents the asynchronous operation
/// The task result contains the attribute combinations in XML format
///
public virtual async Task> GenerateAllCombinationsAsync(Product product, bool ignoreNonCombinableAttributes = false, IList allowedAttributeIds = null)
{
ArgumentNullException.ThrowIfNull(product);
var allProductAttributeMappings = await _productAttributeService.GetProductAttributeMappingsByProductIdAsync(product.Id);
if (ignoreNonCombinableAttributes)
allProductAttributeMappings = allProductAttributeMappings.Where(x => !x.IsNonCombinable()).ToList();
//get all possible attribute combinations
var allPossibleAttributeCombinations = CreateCombination(allProductAttributeMappings);
var allAttributesXml = new List();
foreach (var combination in allPossibleAttributeCombinations)
{
var attributesXml = new List();
foreach (var productAttributeMapping in combination)
{
if (!productAttributeMapping.ShouldHaveValues())
continue;
//get product attribute values
var attributeValues = await _productAttributeService.GetProductAttributeValuesAsync(productAttributeMapping.Id);
//filter product attribute values
if (allowedAttributeIds?.Any() ?? false)
attributeValues = attributeValues.Where(attributeValue => allowedAttributeIds.Contains(attributeValue.Id)).ToList();
if (!attributeValues.Any())
continue;
var isCheckbox = productAttributeMapping.AttributeControlType == AttributeControlType.Checkboxes ||
productAttributeMapping.AttributeControlType ==
AttributeControlType.ReadonlyCheckboxes;
var currentAttributesXml = new List();
if (isCheckbox)
{
//add several values attribute types (checkboxes)
//checkboxes could have several values ticked
foreach (var oldXml in attributesXml.Any() ? attributesXml : [string.Empty])
{
foreach (var checkboxCombination in CreateCombination(attributeValues))
{
var newXml = oldXml;
foreach (var checkboxValue in checkboxCombination)
newXml = AddProductAttribute(newXml, productAttributeMapping, checkboxValue.Id.ToString());
if (!string.IsNullOrEmpty(newXml))
currentAttributesXml.Add(newXml);
}
}
}
else
{
//add one value attribute types (dropdownlist, radiobutton, color squares)
foreach (var oldXml in attributesXml.Any() ? attributesXml : [string.Empty])
{
currentAttributesXml.AddRange(attributeValues.Select(attributeValue =>
AddProductAttribute(oldXml, productAttributeMapping, attributeValue.Id.ToString())));
}
}
attributesXml.Clear();
attributesXml.AddRange(currentAttributesXml);
}
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 (var i = 0; i < allAttributesXml.Count; i++)
{
var attributesXml = allAttributesXml[i];
foreach (var attribute in allProductAttributeMappings)
{
var conditionMet = await IsConditionMetAsync(attribute, attributesXml);
if (conditionMet.HasValue && !conditionMet.Value)
allAttributesXml[i] = RemoveProductAttribute(attributesXml, attribute);
}
}
return allAttributesXml;
}
///
/// Parse a customer entered price of the product
///
/// Product
/// Form
///
/// A task that represents the asynchronous operation
/// The task result contains the customer entered price of the product
///
public virtual async Task ParseCustomerEnteredPriceAsync(Product product, IFormCollection form)
{
ArgumentNullException.ThrowIfNull(product);
ArgumentNullException.ThrowIfNull(form);
var customerEnteredPriceConverted = decimal.Zero;
if (product.CustomerEntersPrice)
foreach (var formKey in form.Keys)
{
if (formKey.Equals($"addtocart_{product.Id}.CustomerEnteredPrice", StringComparison.InvariantCultureIgnoreCase))
{
if (decimal.TryParse(form[formKey], out var customerEnteredPrice))
customerEnteredPriceConverted = await _currencyService.ConvertToPrimaryStoreCurrencyAsync(customerEnteredPrice, await _workContext.GetWorkingCurrencyAsync());
break;
}
}
return customerEnteredPriceConverted;
}
///
/// Parse a entered quantity of the product
///
/// Product
/// Form
/// Customer entered price of the product
public virtual int ParseEnteredQuantity(Product product, IFormCollection form)
{
ArgumentNullException.ThrowIfNull(product);
ArgumentNullException.ThrowIfNull(form);
var quantity = 1;
foreach (var formKey in form.Keys)
if (formKey.Equals($"addtocart_{product.Id}.EnteredQuantity", StringComparison.InvariantCultureIgnoreCase))
{
_ = int.TryParse(form[formKey], out quantity);
break;
}
return quantity;
}
///
/// Parse product rental dates on the product details page
///
/// Product
/// Form
/// Start date
/// End date
public virtual void ParseRentalDates(Product product, IFormCollection form, out DateTime? startDate, out DateTime? endDate)
{
ArgumentNullException.ThrowIfNull(product);
ArgumentNullException.ThrowIfNull(form);
startDate = null;
endDate = null;
if (product.IsRental)
{
var ctrlStartDate = form[$"rental_start_date_{product.Id}"];
var ctrlEndDate = form[$"rental_end_date_{product.Id}"];
try
{
startDate = DateTime.ParseExact(ctrlStartDate,
CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern,
CultureInfo.InvariantCulture);
endDate = DateTime.ParseExact(ctrlEndDate,
CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern,
CultureInfo.InvariantCulture);
}
catch
{
// ignored
}
}
}
///
/// Get product attributes from the passed form
///
/// Product
/// Form values
/// Errors
///
/// A task that represents the asynchronous operation
/// The task result contains the attributes in XML format
///
public virtual async Task ParseProductAttributesAsync(Product product, IFormCollection form, List errors)
{
ArgumentNullException.ThrowIfNull(product);
ArgumentNullException.ThrowIfNull(form);
//product attributes
var attributesXml = await GetProductAttributesXmlAsync(product, form, errors);
//gift cards
AddGiftCardsAttributesXml(product, form, ref attributesXml);
return attributesXml;
}
#endregion
#region Gift card attributes
///
/// Add gift card attributes
///
/// 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)
{
var 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
{
//ignore
}
return result;
}
///
/// Get gift card attributes
///
/// 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
{
//ignore
}
}
#endregion
#region Properties
protected string ChildElementName { get; set; } = "ProductAttribute";
#endregion
}